I am working on a project with models loaded into a scene from several *.obj files.
I test cutting models in 6 planes (front, back, left, right, top, bottom).
I’ve been struggling for days to get the incisions filled with plane. I’ve looked at various solutions, but I’m not quite able to implement them in my project.
Eventually I went back to the standard example: three.js examples
I don’t quite understand the truncation principle in this example, but I’m trying to apply it to my project.
I’m testing this with two objects (in a group).
Unfortunately, intersection fills are not created in the model position.
When I added copying the mesh position:
stencilGroup.position.copy( mesh.position );
works ok but only with one model (first or second moved object).
Unfortunately, with two (or more) models, this does not work.
Clipping/stencil is a tricky topic but where is the error?
<!DOCTYPE html>
<html lang="en">
<head>
<title>stencil-mytest</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - solid geometry with clip cutplanes and stencil materials<br>according to the original: <a href="https://threejs.org/examples/?q=stencil#webgl_clipping_stencil" target="_blank" rel="noopener">examples/webgl_clipping_stencil.html</a></div>
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
// import Stats from 'three/addons/libs/stats.module.js';
let camera, scene, renderer;
// let stats;
let controls;
let q,x,y,z,n,m,e;
let obj,geom,mater,mesh;
// CUTPLANEs - predef
let planeObjects, cropmodels;
let cutparams = {
cuthelpers: false, // display/hide cuthelpers
cutclipcol: 0xE91E63,
// 6 cutplanes/constant
cutx: 0.4, cuty: 0.4, cutz: 2,
cutx2:0.4, cuty2:0.4, cutz2:2,
};
let cutplanes = [
Object.assign(new THREE.Plane( new THREE.Vector3( -1, 0, 0 ), cutparams.cutx ),{name: "cutx"} ),
Object.assign(new THREE.Plane( new THREE.Vector3( 0, -1, 0 ), cutparams.cuty ),{name: "cuty"} ),
Object.assign(new THREE.Plane( new THREE.Vector3( 0, 0, -1 ), cutparams.cutz ),{name: "cutz"} ),
Object.assign(new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), cutparams.cutx2 ),{name: "cutx2"} ),
Object.assign(new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), cutparams.cuty2 ),{name: "cuty2"} ),
Object.assign(new THREE.Plane( new THREE.Vector3( 0, 0, 1 ), cutparams.cutz2 ),{name: "cutz2"} ),
]
// console.log(cutplanes)
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 36, window.innerWidth / window.innerHeight, 1, 100 );
camera.position.set( 5.5, 3.6, 3.2 );
// #############
scene.add( new THREE.AmbientLight( 0xffffff, 1.5 ) );
let dirLight = new THREE.DirectionalLight( 0xffffff, 3 );
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;
scene.add( dirLight );
// #############
let planeHelpers = cutplanes.map( p => new THREE.PlaneHelper( p, 2, 0xffffff ) );
planeHelpers.forEach( ph => {
ph.visible = cutparams.cuthelpers, // poka/ukryj cuthelpers;
scene.add( ph );
} );
// #############
let axesHelper = new THREE.AxesHelper( 2 );
scene.add( axesHelper );
// #############
// MODELs
// GROUP of all loaded models (e.g. *.obj)
let allmodels = new THREE.Group();
scene.add( allmodels );
allmodels.visible=false; // hide group
// Object 1
geom = new THREE.TorusKnotGeometry( 0.4, 0.15, 220, 60 );
mater = new THREE.MeshStandardMaterial( {
color: 0xFFC107,
// clippingPlanes: cutplanes,
// clipShadows: true,
shadowSide: THREE.DoubleSide,
} );
obj = new THREE.Mesh( geom, mater );
obj.castShadow = true;
// obj.side = THREE.DoubleSide;
// obj.material.side = THREE.DoubleSide;
// scene.add( obj );
allmodels.add(obj)
// Object 2
geom = new THREE.TorusKnotGeometry( 0.4, 0.15, 220, 60 );
mater = new THREE.MeshStandardMaterial( {
color: 0x00ff00,
// clippingPlanes: cutplanes,
// clipShadows: true,
shadowSide: THREE.DoubleSide,
} );
obj = new THREE.Mesh( geom, mater );
obj.castShadow = true;
// obj.side = THREE.DoubleSide;
// obj.material.side = THREE.DoubleSide;
obj.position.z= -1.5
// scene.add( obj );
allmodels.add(obj)
// GROUP for cropped models
let cropmodels = new THREE.Group();
scene.add( cropmodels );
// for ( let i = 0; i <= 0; i ++ ) { // test only first object from group allmodels
// for ( let i = 1; i <= 1; i ++ ) { // test only second object from group allmodels
for ( let i = 0; i < allmodels.children.length; i ++ ) { // test ALL objects
mesh = allmodels.children[i] // mesh
geom = mesh.geometry // geometry
// Set up clip plane rendering
planeObjects = [];
// let planeGeom = new THREE.PlaneGeometry( 4, 4 );
let planeGeom = new THREE.PlaneGeometry( 50, 50 );
for ( let i = 0; i < cutplanes.length; i ++ ) {
let poGroup = new THREE.Group();
let iplane = cutplanes[ i ];
let stencilGroup = createPlaneStencilGroup( geom, iplane, i + 1 );
// iplane is clipped by the other clipping cutplanes
let planeMat =
new THREE.MeshStandardMaterial( {
color: cutparams.cutclipcol, // mycolor
metalness: 0.1,
roughness: 0.75,
clippingPlanes: cutplanes.filter( p => p !== iplane ),
stencilWrite: true,
stencilRef: 0,
stencilFunc: THREE.NotEqualStencilFunc,
stencilFail: THREE.ReplaceStencilOp,
stencilZFail: THREE.ReplaceStencilOp,
stencilZPass: THREE.ReplaceStencilOp,
} );
let po = new THREE.Mesh( planeGeom, planeMat );
po.onAfterRender = function ( renderer ) { renderer.clearStencil(); };
po.renderOrder = i + 1.1;
// test OK only for first or second object from group allmodels
stencilGroup.position.copy( mesh.position );
cropmodels.add( stencilGroup );
poGroup.add( po );
planeObjects.push( po );
scene.add( poGroup );
} // for cutplanes.length
// add the color
let clippedColorFront = new THREE.Mesh( geom, mesh.material ); // apply source mesh material
clippedColorFront.castShadow = true;
clippedColorFront.renderOrder = 6;
clippedColorFront.material.clippingPlanes = cutplanes;
clippedColorFront.material.clipShadows = true;
clippedColorFront.material.shadowSide = THREE.DoubleSide;
clippedColorFront.position.copy( mesh.position ); // apply source mesh position
cropmodels.add( clippedColorFront );
} // for allmodels.children
// Stats ???
// stats = new Stats();
// document.body.appendChild( stats.dom );
// Renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.shadowMap.enabled = true;
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x263238 );
window.addEventListener( 'resize', onWindowResize );
document.body.appendChild( renderer.domElement );
renderer.localClippingEnabled = true; // !!!
// Controls
controls = new OrbitControls( camera, renderer.domElement );
controls.minDistance = 2;
controls.maxDistance = 20;
controls.target.set(0.3,-0.1,-0.6)
// ???
controls.addEventListener( 'change', render ); // use only if there is no animation loop
controls.update();
// #####################################################
// GUI
let gui = new GUI();
gui.add ( cutparams, 'cuthelpers' ).listen()
.onChange( function (val) {
cutparams.cuthelpers = val;
planeHelpers.forEach( ph => {
ph.visible = val
} );
})
gui.add( cutparams, 'cutx' , 0, 2, 0.001).onChange( function (val) {
cutparams['cutx'] = val;
x = cutplanes.find (x => x.name === 'cutx'); x.constant = val;
})
gui.add( cutparams, 'cutx2' , 0, 2, 0.001).onChange( function (val) {
cutparams['cutx2'] = val;
x = cutplanes.find (x => x.name === 'cutx2'); x.constant = val;
})
gui.add( cutparams, 'cuty' , 0, 2, 0.001).onChange( function (val) {
cutparams['cuty'] = val;
x = cutplanes.find (x => x.name === 'cuty'); x.constant = val;
})
gui.add( cutparams, 'cuty2' , 0, 2, 0.001).onChange( function (val) {
cutparams['cuty2'] = val;
x = cutplanes.find (x => x.name === 'cuty2'); x.constant = val;
})
gui.add( cutparams, 'cutz' , 0, 2, 0.001).onChange( function (val) {
cutparams['cutz'] = val;
x = cutplanes.find (x => x.name === 'cutz'); x.constant = val;
})
gui.add( cutparams, 'cutz2' , 0, 2, 0.001).onChange( function (val) {
cutparams['cutz2'] = val;
x = cutplanes.find (x => x.name === 'cutz2'); x.constant = val;
})
} // init
function createPlaneStencilGroup( geom, iplane, renderOrder ) {
const group = new THREE.Group();
const baseMat = new THREE.MeshBasicMaterial();
baseMat.depthWrite = false;
baseMat.depthTest = false;
baseMat.colorWrite = false;
baseMat.stencilWrite = true;
baseMat.stencilFunc = THREE.AlwaysStencilFunc;
// back faces
const mat0 = baseMat.clone();
mat0.side = THREE.BackSide;
mat0.clippingPlanes = [ iplane ];
mat0.stencilFail = THREE.IncrementWrapStencilOp;
mat0.stencilZFail = THREE.IncrementWrapStencilOp;
mat0.stencilZPass = THREE.IncrementWrapStencilOp;
const mesh0 = new THREE.Mesh( geom, mat0 );
mesh0.renderOrder = renderOrder;
group.add( mesh0 );
// front faces
const mat1 = baseMat.clone();
mat1.side = THREE.FrontSide;
mat1.clippingPlanes = [ iplane ];
mat1.stencilFail = THREE.DecrementWrapStencilOp;
mat1.stencilZFail = THREE.DecrementWrapStencilOp;
mat1.stencilZPass = THREE.DecrementWrapStencilOp;
const mesh1 = new THREE.Mesh( geom, mat1 );
mesh1.renderOrder = renderOrder;
group.add( mesh1 );
return group;
} // createPlaneStencilGroup
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
} // onWindowResize
function animate() {
requestAnimationFrame( animate ); // ???
if (planeObjects && planeObjects.length > 0){
for ( let i = 0; i < planeObjects.length; i ++ ) {
const iplane = cutplanes[ i ];
const po = planeObjects[ i ];
iplane.coplanarPoint( po.position );
po.lookAt(
po.position.x - iplane.normal.x,
po.position.y - iplane.normal.y,
po.position.z - iplane.normal.z,
);
}
}
render();
} // animate
function render() {
renderer.render(scene, camera);
}
// ================= START =====================
init();
animate();
</script>
</body>
</html>