Capping clipped planes using stencil on a BufferGeometry()

Hi. I am a bit of a novice when it comes to coding on three.js so I wanted some help on this:

I am working on a project that requires clipping planes on a geometry and then capping up the cut portion. I saw many examples and questions and found this to be the closest to what I need: https://threejs.org/examples/?q=stenc#webgl_clipping_stencil . However I tried repeating the same for my project using the code below (but it does not create the cap) . Am I missing something ?

  this.partGroup = new THREE.Group();
  var object = new THREE.Group();
  var poGroup = new THREE.Group();
  this.renderer.localClippingEnabled = true;
for(var i = 0; i < parts.length; i++){
       var geometry = new THREE.BufferGeometry();
        
        geometry.setIndex(new THREE.BufferAttribute(indices, 1));
        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3, true));
        geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3, true));


        var planes = [
            new THREE.Plane(new THREE.Vector3(1, 0, 0), -10),
            new THREE.Plane(new THREE.Vector3(0, 1, 0), -1)
        ];

        var plane = planes[0];
        var stencilGroup = this.createPlaneStencilGroup(geometry, plane, 1);

        var planeMat =
            new THREE.MeshStandardMaterial({
                color: 0xE91E63,
                metalness: 0.1,
                roughness: 0.75,
                clippingPlanes: planes.filter(p => p !== plane),

                stencilWrite: true,
                stencilRef: 0,
                stencilFunc: THREE.NotEqualStencilFunc,
                stencilFail: THREE.ReplaceStencilOp,
                stencilZFail: THREE.ReplaceStencilOp,
                stencilZPass: THREE.ReplaceStencilOp,

            });
        var planeGeom = new THREE.PlaneBufferGeometry(40, 40);
        var po = new THREE.Mesh(planeGeom, planeMat);
        po.onAfterRender = function (renderer) {

            renderer.clearStencil();

        };
        poGroup.add(po);
        object.add(stencilGroup);

        var material = new THREE.MeshPhongMaterial({
            vertexColors: THREE.VertexColors,
            side: THREE.DoubleSide,
            transparent: transparent,
            opacity: opacity,
            shininess: 30,
            reflectivity: 0.5,
            clippingPlanes: planes,
            clipIntersection: false
        });
        var mesh = new THREE.Mesh(geometry, material);
        mesh.partID = part.id;
        mesh.faces = faceList;
        mesh.color = (!randomColors ? null : color.getHex());
        mesh.visible = false;

     this.partGroup.add(mesh);
}
    this.scene.add(object);
    this.scene.add(poGroup);
    this.scene.add(this.partGroup);
    this.updateGL();

I followed the exact code from the examples (except the creation of my original mesh is done using BufferGeometry(). I still end up getting the part without any cap (however the cutting planes cut through fine). I’d really appreciate if I could get help on this. Note that the createPlaneStencilGroup() function is unchanged from the examples:

@Mugen87 . Your expertise would be highly appreciated :slight_smile:

Sorry, I’m afraid I’m not a big help here since this a very specific part of the library I’m not familiar with. I personally think the material stencil API and the respective example is a bit complicated. I’m not surprised that devs struggle with it.

Maybe it’s easier to investigate this issue if you share a link to your application. Or maybe a reduced test case demonstrating the issue.

2 Likes

The idea is to be able to cap a cut 3-D geometry. In the case of the cylinder I’d expect to have the cut plane to be filled similar to the way that the example fills the TorusKnot. The only major difference between my project and the example is that mine is a BufferGeometry created using vertex positions and vertex colors while the example uses the TorusKnotBufferGeometry.

@rahul.pillai93

If you provide a jsfiddle I can try to update it to properly clip your geometry.

@Mugen87

I personally think the material stencil API and the respective example is a bit complicated.

I think stencil operations are pretty complicated in general because they’re very abstract and order dependent – I see people struggle to understand them in other contexts, as well. I’m curious what improvements you would suggest. I do agree that the stencil example could be simplified a bit to be more clear. I do have a PR in for that but it depends on updates to the group render order. Possibly there should be an extra simplified example that just demonstrates how to clip using one plane, as well.

1 Like

I think we need an API that completely hides the stencil interface from the user. I have not something ready to share but it should be something that covers the most common masking use cases.

3 Likes

This is my first three.js fiddle. I hope it works :slight_smile::
https://jsfiddle.net/0n56pjm2/

Note that I need the cylinder to be capped entirely by the cutting plane so it looks as if it is solid (and not hollow). For simple geometries the API might be useful but I am working with BufferGeometries that are more complex and are created using points , normals and vertex colors for the material. I cannot recreate this as I use a huge database of points so in the example above I’ve just created a simple cylinderGeometry and cut it using a cuttingPlane on the material.

Hey rahul.
I’ve been researching for months on this topic and thought I’d save you some time and toss some links I found on my searches your way re: capping clipped planes…(which I think should be incorporated together succinctly)…

1st, this incredibly satisfying example:
http://daign.github.io/clipping-with-caps/

And etc…

And probably one of the most valuable scenarios…
More easily described:

1 Like

@rahul.pillai93 I’ve gone ahead and updated your fiddle to clip the cylinder with caps – hopefully this is what you’re looking for:

https://jsfiddle.net/dokev2hy/1/

7 Likes

Thanks so much @gkjohnson and @GlifTek . These should work great for my use. I found the stencil example good but couldn’t simplify things from it.

Also @gkjohnson. Any reason why when I run the updated fiddle, it still shows up as hollow ? :

Looks like I’d posted an outdated version of the fiddle. I updated my post above but here’s the updated link, as well:

https://jsfiddle.net/dokev2hy/1/

Sorry about that!

1 Like

Thanks so much for this fiddle. Sorry for the late reply, I’ve been busy with some stuff. Just a quick question on your fiddle -
Is there anyway for the geometry to highlight boundaries between different parts being cut by the same plane ?

For instance check out this updated fiddle from the code that you sent me (note I have commented out line 74 to hide the stencil plane):Edit fiddle - JSFiddle - Code Playground
If the plane is added then there is no way to tell that there is a smaller cylinder in the geometry. Is there any way to add boundary lines to show the difference ? Something like the image below (please note the image is just edited on MS paint):
Untitled

Once again thanks so much for the fiddle! It really helped me get through my project. Some other the cool stuff I’d like to do in the future is to be able to highlight different stencil colors based on different parts cut (as in the example above).

1 Like

I don’t have any simple ways off the top of my head but I’m sure it’s doable – it would likely take custom effects and shaders to make it, though, without making clipping the geometry itself.

Glad the fiddle was helpful!

2 Likes

Possibly make TWO clipping planes, each terminating at the inner edge, where the second inner cylinder (cavity?) Is?

I agree it’s complicated.
I’ve been wrestling with it to certain degrees both successful and not.
After a bit more toying, I’m going to attempt to make a mini library that simplifies the process for others.
I’ve become VERY familiar with all the necessary functions needed in the process.
Would be nice for others to not have to repeat the arduous process and just BE productive with it off the bat.
…it’s a HEADACHE lol.

2 Likes

@GlifTek. With regards to the performance of the method in @gkjohnson’s fiddle, anyway that it can be improved for a large number of meshes ? I am working on a software that uses mouse controls to move a clipping plane along an axis chosen by the user and each time the user drags the clipping plane, I have to create the clip plane and stencils all over which is slowing down the webgl interface.

@rahul.pillai93

the user and each time the user drags the clipping plane, I have to create the clip plane and stencils all over which is slowing down the webgl interface.

Recreating all the materials and planes is unnecessary. You just have to update the plane position that is referenced by all the materials. The stencil clipping example in three.js demonstrates moving clipping planes but does not require recreating any of that.

Here’s an updated fiddle with an animated plane (Edit fiddle - JSFiddle - Code Playground). You can see that we’re just updating the plane position in the render loop:

plane.constant = Math.sin( window.performance.now() * 0.005 ) * 10;
plane.coplanarPoint(planeMesh.position);
1 Like

Hey gk,
I’ve a question.
(i’ll try to get a fiddle for it in a new post which may be quite difficult because the setup is long and involved)
i’ll try to make it simple…

what’s the best way to have multiple instances of what’s happening in the example code in one scene?
i have that working, as the code is run for every object(part) of my main group.
(i’m testing with just TWO right now)

only problem is the generated ‘clipped Geometry’ (sliced side) (not the main mesh) from the second gets put into the group from the first, so the dat_GUI (2 are made) controls from the first move parts from the second.

I’ve given each gui, gui folder, controller, plane, clipped plane geometry, object, all their own names according to the object they are used with.

where should i be looking to specifically make sure the new ones created are placed in their unique groups?
or how to direct them to be specific to the proper ones?

if this needs a fiddle or it’s own thread just let me know.
ill try. (part is in google blockly soo… hard to show)

mostly I’m talking about the code from line 134 to 173

and how that can be made unique for each object’s run through the code.

thanks.

@GlifTek

If I’m understanding right you want to create two separate objects that are clipped by two separate clip planes and therefore require two separate sets of stencil passes. Stencil passes are very sensitive to render order so it’s important to finish the rendering of the first object entirely before starting to render the other or you’ll get artifacts due to how the stencil passes are set up. Unfortunately right now render order in three.js is global so setting up what your suggesting is very context / application specific and the render order values need to be selected in such a way that they don’t conflict with other passes.

In this case what that means is that the 5 render calls required to render the first objects clip surface (4 stencil passes, 1 pass for the plane surface) must occur together before the second set. You can achieve this by setting the render order starting at renderOrder=1 for the stencils and renderOrder=2 for the plane surface. Then for the second object you can set renderOrder=3 for the second set of stencils and renderOrder=4 for the second clip surface.

I have PR #19526 in that would make it possible to sort objects and render them together in a sub hierarchy so this delicate handling of global render order isn’t necessary but it hasn’t been merged yet. Then you could have self contained objects with an internal render order that don’t have to be aware of the rest of the scene.

2 Likes

Wow man.
Thanks for the quick response!
…Exactly what I was needing to hear.
And that PR! How invaluable that would/will be!
Nice work.

So, is the naming of all these items necessary as I’ve been doing?

Or, if I reverted and kept them as-is, and just designate iterations of their render order enough?

Will the animate() function know which it’s supposed to be controlling via the gui?
It DOES work properly with my setup re the main geometry, not the planeGeo obv.

I’ll test it out.

I’m also most likely to nest all their dat-gui’s into one. (Clogging up the screen).
I’ll keep you updated.
Thanks!

2 Likes