The important thing is that all relevant stencil passes happen together and that the clip cap rendering happen last in those sets of stencil rendering. That demo has 3 clip caps that have to get rendered and in this order:
Clip Plane Surface 1
4x 2x stencil passes with render order 1
1x clip surface as with render order 1.1
Clip Plane Surface 2
4x 2x stencil passes with render order 2
1x clip surface as with render order 2.1
Clip Plane Surface 3
4x 2x stencil passes with render order 3
1x clip surface as with render order 3.1
Geometry surface color
1x geometry rendered with render order 6
After all the stencil passes we render the normal surface of the geometry. TBH the fact that that is rendered with render order 6 is not so important because it does not do anything with the stencil buffer. The geometry surface material is only rendered once though.
Should have the “1” be also iterated like you suggest.
I’m not sure I follow what you mean. That line adds the stencil material meshes with render order i + 1.
Also I’ve looked more closely at that example again and it looks like there are actually only two stencil passes rather than four like I wrote before. There are other approaches that require four stencil passes but it doesn’t look like that’s what I’d used in this case. Everything else still applies though.
yup… THIS … is exactly what made for absolute success with this issue…
line 141
Where var stencilGroup = createPlaneStencilGroup( geometry, plane, i + 1 );
Having the “1” be also iterated like you suggest.
i just had a var “Object_Number” be raised 1 each time through the code, having…
var stencilGroup = createPlaneStencilGroup(mesh.geometry, plane, i + 1 + Object_Number);
po.renderOrder = (i + 1.1) + Object_Number;
respectively. and it works…
thank you so much for your advice on things.
i’ll def be back soon with something else hah!
EDIT: var “Object_Number” was treated as such… for (var i_index in LIST_Objects_In_loaded_SCENE) { i = LIST_Objects_In_loaded_SCENE[i_index]; if (Object_Number == 0) { Object_Number = 1; } else { Object_Number = Object_Number + 3;
@gkjohnson Thanks for the response. One last question - How would I modify the stencils incase of rotation of the clipping plane about an axis by a certain angle? I tried
var oldPlaneNormal = plane.normal.clone();
plane.normal.applyAxisAngle(rotAxis, changeAngle);
planeMesh.quaternion.setFromUnitVectors(oldPlaneNormal, plane.normal);
This successfully rotates the clipping plane but does not cap the hollow areas properly as seen in the fiddle ( I also recreated the stencils to the scene but to no avail): https://jsfiddle.net/czm2nLa4/
I should really post this as a separate topic but it’s too small a problem (either that or I am too much of a novice to not know how small or big it is) . Why does THREE.Raycaster.intersectObjects() not work on the mesh for the cap ? I noticed that the geometry is actually the geometry given by the combination of the stencil and the plane but I need mouse intersection on the capped surfaces only.
Figured it! Never thought it made sense but this was the easiest solution I could think of: Use raycaster.intersectObjects() on the mesh that is being clipped itself and find the number of intersections. I then filter all intersections that lie on the unclipped side of the plane (as intersectObjects() interacts even with the invisible clipped part). I did this by checking the signed distance of the points where the intersections were found with the clipped plane and used the sign of this distance to check if it was on the clipped side or not. From the filtered intersections I then did this:
If the no. of intersections filtered is Odd, the clicked area on the screen is the cap itself and if it is even then it is somewhere outside the cap. This holds true only if the mesh material has the property side = THREE.DoubleSide. This makes sense now since a ray has to come out of every mesh it enters in a double sided mesh
You can also give each plane mesh part that is generated a name, and name the group they are added to. Then traverse the group for each descendant to do your ray casting on.
Im referring to the po. Objects and the mesh0 mesh1 objects that are in the for loops.
I know I come to this late however I have done something similar to create a cross selection. I use CSG to create the cross section with the library https://github.com/oathihs/ThreeCSG.
Hey Thomas,
I had looked into CSG before, to achieve this effect. Do you think you could possibly share some of your code you’ve made using it in an example?
I’m very interested to see how it’s implemented!
Looking at the github, and it’s example demo I’m wondering if it provides capped ends to the geometry at the intersections.
Thanks!
I have similar question. Unfortunaly I cant figure out how to apply this technique to gltf model. This is my code: clipping gltf
I merge all gltf geometries into one and trying to cap the “floor”.
p.s. updated link with fixed stencil plane rotation
Hello @GlifTek i am continuously trying to achieve the capping feature over the combination of three different geometry entities using this example: three.js examples
This example uses three sides clipping planes for a single geometry whereas in my project i have 5 planes cutting the geometry (combination of three geometries ) from 5 different sides.
I tried to acheive everything as described in the example and able to cut the geometry as required but the cap that is applied is only visible when you rotate the model over a certain area of the scene and if i try to zoom in and out or use pan it just the cropped area gets transparent or void spaces visualization over the capped surface.
I am pretty much sure it has something the do with the render order . Do you have any ideas on this or what specific area i should look into.
Below is the code that i am currently using.
document.getElementById("Clip").onclick = function (e) {
//filles global plane array with planes
createPlanesusingPts();
var stencilGroup = null;
var poGroup = null;
var planeGeom = null;
for (let j = 0; j < 5; j++) {
let pplane = Planearray[j];
planeGeom = new THREE.PlaneGeometry(200, 200);
for (let i = 0; i < group.children.length; i++) {
// group.children.length has 3 different mesh object
const poGroup = new THREE.Group();
const stencilGroup = createPlaneStencilGroup(
group.children[i].geometry,
pplane,
j + 1
);
}
// add the color
const planeMat = new THREE.MeshStandardMaterial({
color: 0xe91e63,
metalness: 0.1,
roughness: 0.75,
clippingPlanes: Planearray.filter((p) => p !== pplane),
stencilWrite: true,
stencilRef: 0,
stencilFunc: THREE.NotEqualStencilFunc,
stencilFail: THREE.ReplaceStencilOp,
stencilZFail: THREE.ReplaceStencilOp,
stencilZPass: THREE.ReplaceStencilOp,
});
const po = new THREE.Mesh(planeGeom, planeMat);
po.onAfterRender = function (renderer) {
renderer.clearStencil();
};
po.renderOrder = j + 1.1;
objectss.add(stencilGroup);
poGroup.add(po);
planeObjects.push(po);
scene.add(poGroup);
}
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
metalness: 1,
roughness: 1,
clippingPlanes: Planearray,
clipShadows: true,
shadowSide: THREE.DoubleSide,
});
for (let i = 0; i < group.children.length; i++) {
const clippedColorFront = new THREE.Mesh(
group.children[i].geometry,
material
);
clippedColorFront.renderOrder = 6;
objectss.add(clippedColorFront);
}
}
function animate() {
requestAnimationFrame(animate);
for ( let i = 0; i < planeobjectss.length; i ++ ) {
const plane = Planearray[ i ];
const po = planeobjectss[ i ];
plane.coplanarPoint( po.position );
po.lookAt(
po.position.x - plane.normal.x,
po.position.y - plane.normal.y,
po.position.z - plane.normal.z,
);
}
camera2.position.copy(camera.position);
camera2.position.sub(controls.target);
camera2.position.setLength(300);
camera2.lookAt(scene2.position);
render();
}
createPlaneStencilGroup and is similar as implemented in the three.js stencil example.