Clipping geometry made from merged faces. Stenciled cap face not aligning properly

My project uses geometry where each face is its own mesh. I need to clip the geometry to cut away a portion of it and have a stenciled cap face cover the clipped edges. I examined and tinkered with the Three.js clipping stencil example and I understand how to use a stencil to cap trimmed solid geometry, but when I try it on collections of face geometries it doesn’t work. Below is some code I have been tinkering with, based on the example. Here is a fiddle

It contains 2 clipping planes, a cube made from 6 separate PlaneGeometries, and a solid sphere for comparison. I made the stencil for the cube using an additional BufferGeometry made from merging the planes together into a single geometry object. The stencil for the cube appears to be the right shape and size, but only one cap face is drawn and it is not at the location of either of the clipping planes. Is there anything else I’m supposed to do with the stencil or the clipping plane beyond what the example already does to make it work on geometry of this type?

import { OrbitControls } from 'https://unpkg.com/three@0.120.1/examples/jsm/controls/OrbitControls.js';
import { BufferGeometryUtils } from 'https://unpkg.com/three@0.120.1/examples/jsm/utils/BufferGeometryUtils.js';

var camera, scene, renderer;
var planes, planeObjects;

init();
animate();

function createPlaneStencilGroup( geometry, plane, renderOrder )
{
   var group = new THREE.Group();
   var baseMat = new THREE.MeshBasicMaterial();
   baseMat.depthWrite = false;
   baseMat.depthTest = false;
   baseMat.colorWrite = false;
   baseMat.stencilWrite = true;
   baseMat.stencilFunc = THREE.AlwaysStencilFunc;

   // back faces
   var mat0 = baseMat.clone();
   mat0.side = THREE.BackSide;
   mat0.clippingPlanes = [ plane ];
   mat0.stencilFail = THREE.IncrementWrapStencilOp;
   mat0.stencilZFail = THREE.IncrementWrapStencilOp;
   mat0.stencilZPass = THREE.IncrementWrapStencilOp;

   var mesh0 = new THREE.Mesh( geometry, mat0 );
   mesh0.renderOrder = renderOrder;
   group.add( mesh0 );

   // front faces
   var mat1 = baseMat.clone();
   mat1.side = THREE.FrontSide;
   mat1.clippingPlanes = [ plane ];
   mat1.stencilFail = THREE.DecrementWrapStencilOp;
   mat1.stencilZFail = THREE.DecrementWrapStencilOp;
   mat1.stencilZPass = THREE.DecrementWrapStencilOp;

   var mesh1 = new THREE.Mesh( geometry, mat1 );
   mesh1.renderOrder = renderOrder;

   group.add( mesh1 );

   return group;
}

function init()
{
   scene = new THREE.Scene();

   camera = new THREE.PerspectiveCamera( 36, window.innerWidth / window.innerHeight, 1, 100 );
   camera.position.set( 2, 2, 2 );

   initLights();

   planes = [
      new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0.42 ),
      new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0.25 )
   ];
   
   var material = new THREE.MeshStandardMaterial( {
      color: 0x00ff00,
      metalness: 0.1,
      roughness: 0.75,
      side: THREE.DoubleSide,
      clippingPlanes: planes,
      clipShadows: true,
      shadowSide: THREE.DoubleSide,
   } );
   
   // Simple sphere geometry. Something I know works, for comparison.
   var sphereGeom = new THREE.SphereBufferGeometry( 0.5, 32, 32 );
   sphereGeom.translate( -1.1, 0, 0 );
   
   // Make a cube out of 6 planes and merge them together
   var planeGeoms = [];
   for(var i = 0; i < 6; i++)
   {
      planeGeoms.push( new THREE.PlaneBufferGeometry( 1, 1 ) );
   }
   var mergedBufferGeom = BufferGeometryUtils.mergeBufferGeometries( planeGeoms );

   // Set up clip plane rendering
   planeObjects = [];
   var planeGeom = new THREE.PlaneBufferGeometry( 4, 4 );
   for ( var i = 0; i < 2; i ++ )
   {
      var poGroup = new THREE.Group();
      var plane = planes[ i ];
      var stencilGroup_sphere = createPlaneStencilGroup( sphereGeom, plane, i + 1 );
      var stencilGroup_Box = createPlaneStencilGroup( mergedBufferGeom, plane, i + 1 )

      // plane is clipped by the other clipping planes
      var planeMat = new THREE.MeshStandardMaterial( {
            color: 0x0000ff,
            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 po = new THREE.Mesh( planeGeom, planeMat );
      po.onAfterRender = function ( renderer ) {
         renderer.clearStencil();
      };
      po.renderOrder = i + 1.1;
      
      plane.coplanarPoint( po.position );
      po.lookAt(
         po.position.x - plane.normal.x,
         po.position.y - plane.normal.y,
         po.position.z - plane.normal.z,
      );

      scene.add( stencilGroup_sphere );
      scene.add( stencilGroup_Box );
      poGroup.add( po );
      planeObjects.push( po );
      scene.add( poGroup );
   }
   
   var sphereMesh = new THREE.Mesh( sphereGeom, material );
   sphereMesh.renderOrder = 6;
   scene.add( sphereMesh );
   
   var planeMeshes = [];
   for(var i = 0; i < 6; i++)
   {
      planeMeshes.push( new THREE.Mesh(planeGeoms[i], material) );
   }
   planeMeshes[0].position.copy(new THREE.Vector3(.5, 0, 0));
   planeMeshes[1].position.copy(new THREE.Vector3(0, .5, 0));
   planeMeshes[2].position.copy(new THREE.Vector3(0, 0, .5));
   planeMeshes[3].position.copy(new THREE.Vector3(-.5, 0, 0));
   planeMeshes[4].position.copy(new THREE.Vector3(0, -.5, 0));
   planeMeshes[5].position.copy(new THREE.Vector3(0, 0, -.5));
   planeMeshes[0].lookAt(new THREE.Vector3(2, 0, 0));
   planeMeshes[1].lookAt(new THREE.Vector3(0, 2, 0));
   planeMeshes[2].lookAt(new THREE.Vector3(0, 0, 2));
   planeMeshes[3].lookAt(new THREE.Vector3(-2, 0, 0));
   planeMeshes[4].lookAt(new THREE.Vector3(0, -2, 0));
   planeMeshes[5].lookAt(new THREE.Vector3(0, 0, -2));
   
   for(var i = 0; i < 6; i++)
      scene.add( planeMeshes[i] );

   // Renderer
   renderer = new THREE.WebGLRenderer( { antialias: true } );
   renderer.shadowMap.enabled = true;
   renderer.setPixelRatio( window.devicePixelRatio );
   renderer.setSize( window.innerWidth, window.innerHeight );
   renderer.setClearColor( 0x263238 );
   renderer.localClippingEnabled = true;
   window.addEventListener( 'resize', onWindowResize, false );
   document.body.appendChild( renderer.domElement );
   
   // Controls
   var controls = new OrbitControls( camera, renderer.domElement );
   controls.minDistance = 2;
   controls.maxDistance = 20;
   controls.update();
}

function initLights()
{
   scene.add( new THREE.AmbientLight( 0xffffff, 0.5 ) );

   var dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
   dirLight.position.set( 5, 10, 7.5 );
   dirLight.castShadow = true;
   dirLight.shadow.camera.right = 2;
   dirLight.shadow.camera.left = - 2;
   dirLight.shadow.camera.top   = 2;
   dirLight.shadow.camera.bottom = - 2;

   dirLight.shadow.mapSize.width = 1024;
   dirLight.shadow.mapSize.height = 1024;
   scene.add( dirLight );
}

function onWindowResize()
{
   camera.aspect = window.innerWidth / window.innerHeight;
   camera.updateProjectionMatrix();
   renderer.setSize( window.innerWidth, window.innerHeight );
}

function animate()
{
   requestAnimationFrame( animate );
   renderer.render( scene, camera );
}```

I made the stencil for the cube using an additional BufferGeometry made from merging the planes together into a single geometry object.

It doesn’t appear you’ve made a cube for the stencil from those planes. Here’s the scene from your fiddle but with the stencil objects rendered as white:

image

I think it’s because your first planes geometry matrix is not 0,0,0. It’s offsetting the others?
Don’t count me on this though, I’m still figuring out a lot of the same thing (working on clipping planes in my project as well).

Huh… I somehow completely forgot to check that. So now I applied the transforms of the plane meshes to the plane geometries that get merged for the stencil and it works now. Thanks!

1 Like

Aww cool! Glad I could help!
…lol now do I get the solution badge too? Hehe.

Also can you possibly post a fiddle or codepen of your working code?

I think it’s good for posterity, as this subject has very limited custom working examples available online since the THREE.JS ECS6 changeover has negated most resource links from the past.

1 Like

Sure. Here is the same fiddle as above, revised with the solution. See comments at line 102.

1 Like

Awesome. Good luck w your clipping planes project!
Plz remember I am here to discuss progress with development of such.

1 Like