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 );
}```