Unexpected result using ThreeCSG

混江龙づ霸主 提交于 2021-02-10 06:42:26

问题


I'm experimenting with the ThreeCSG library and am attempting to swap out the sphere or normal geometry for a custom made Shape, in this case the heart shape from the 3js examples.

Unfortunately I'm getting an odd result on the side that faces the camera. Here's a code sample:

(function onLoad() {
  var container, camera, scene, renderer;
  var grey = 0xD3D3D3;
  
  init();
  animate();

  function init() {
    container = document.getElementById('container');
    initScene();
    addGridHelper();
    addCamera();
    addLighting()
    addRenderer();
    addOrbitControls();

		createHeartMesh();
    createDieCutHandle();
    
    // Creates a CSG cut on the cube based on the passed mesh
    createCSGDiecutHandle();
  }

  function createDieCutHandle() {
    var cubeGeo = new THREE.CubeGeometry(250, 250, 250);
    var mesh = new THREE.Mesh(cubeGeo)
    mesh.position.x = -500;
    scene.add(mesh);
  }
  
  function createCSGDiecutHandle() {
  	var cubeGeo = new THREE.CubeGeometry(250, 250, 250);
    var cubeMesh = new THREE.Mesh(cubeGeo);
    var cubeBSP = new ThreeBSP(cubeMesh);
   
    // Need geometry of the mesh to cut
    var heartShape = getHeartShape();
    var heartGeo = getHeartGeometry(heartShape);
    var meshToCut =  new THREE.Mesh( heartGeo );
    meshToCut.scale.set(10,10,10);
    meshToCut.position.z -= 80;
    var meshToCutBsp = new ThreeBSP(meshToCut)
   
    // var resultBsp = cubeBSP.union(meshToCutBsp);
    var resultBsp = cubeBSP.subtract(meshToCutBsp);
    // var resultBsp = cubeBSP.intersect(meshToCutBsp);
		
    var resultMesh = resultBsp.toMesh(new THREE.MeshLambertMaterial({flatShading: true}));
    resultMesh.geometry.computeVertexNormals();
    scene.add(resultMesh);
  }

	function getHeartShape() {
  	var x = 5, y = 10;
    var heartShape = new THREE.Shape();
    heartShape.moveTo(x - 5, y - 5);
    heartShape.bezierCurveTo(x - 5, y - 5, x - 4, y, x, y);
    heartShape.bezierCurveTo(x + 6, y, x + 6, y - 7, x + 6, y - 7);
    heartShape.bezierCurveTo(x + 6, y - 11, x + 3, y - 15.4, x - 5, y - 19);
    heartShape.bezierCurveTo(x - 12, y - 15.4, x - 16, y - 11, x - 16, y - 7);
    heartShape.bezierCurveTo(x - 16, y - 7, x - 16, y, x - 10, y);
    heartShape.bezierCurveTo(x - 7, y, x - 5, y - 5, x - 5, y - 5);
    
    return heartShape;
  }
  
  function getHeartGeometry(heartShape) {
  	var extrudeSettings = {
      steps: 2,
      amount: 16,
      bevelEnabled: true,
      bevelThickness: 5,
      bevelSize: 1,
      bevelSegments: 1
    };
		return new THREE.ExtrudeBufferGeometry( heartShape, extrudeSettings );
  }

  function createHeartMesh() {
    var heartShape = getHeartShape();
		var geometry = getHeartGeometry(heartShape);
    var material = new THREE.MeshNormalMaterial();
    var mesh = new THREE.Mesh(geometry, material);
    mesh.position.x = -500;
    mesh.position.z = -80;
    mesh.scale.set(10, 10, 10);
    
    scene.add(mesh);
  }
  
  /**** Helper functions ****/
  function computeBoundingBox(mesh) {
    var box = new THREE.Box3().setFromObject(mesh);
    var size = box.getSize();

    return {
      width: size.x,
      height: size.y,
      size: size
    }
  }

  /**** Basic Scene Setup ****/
  function initScene() {
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xffffff);
  }

  function addCamera() {
    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.set(349.11334070460066, 405.44010726325604, 359.3111192889029);
    scene.add(camera);
  }

  function addGridHelper() {
    var planeGeometry = new THREE.PlaneGeometry(2000, 2000);
    planeGeometry.rotateX(-Math.PI / 2);

    var planeMaterial = new THREE.ShadowMaterial({
      opacity: 0.2
    });
    var plane = new THREE.Mesh(planeGeometry, planeMaterial);
    plane.position.y = -200;
    plane.receiveShadow = true;
    scene.add(plane);

    var helper = new THREE.GridHelper(2000, 100);
    helper.material.opacity = 0.25;
    helper.material.transparent = true;
    scene.add(helper);

    var axis = new THREE.AxesHelper(1000);
    scene.add(axis);
  }

  function addLighting() {
    addHemisphereLight();
  }

  function addSpotLighting() {
    var light = new THREE.SpotLight(0xffffff, 1.5);
    light.position.set(0, 1500, 200);
    light.castShadow = true;
    light.shadow = new THREE.LightShadow(new THREE.PerspectiveCamera(70, 1, 200, 2000));
    light.shadow.bias = -0.000222;
    light.shadow.mapSize.width = 1024;
    light.shadow.mapSize.height = 1024;
    scene.add(light);
  }

  function addAmbientLight() {
    var ambientLight = new THREE.AmbientLight(0x404040);
    scene.add(ambientLight);
  }

  function addHemisphereLight() {
    var hemisphereLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);
    scene.add(hemisphereLight);
  }

  function addRenderer() {
    renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    container.appendChild(renderer.domElement);
  }

  function addOrbitControls() {
    var controls = new THREE.OrbitControls(camera, renderer.domElement);
  }

  function animate() {
    requestAnimationFrame(animate);
    render();
  }

  function render() {
    renderer.render(scene, camera);
  }
})();
body {
  background: transparent;
  padding: 0;
  margin: 0;
  font-family: sans-serif;
}

#canvas {
  margin: 10px auto;
  width: 800px;
  height: 350px;
  margin-top: -44px;
}
<body>
  <div id="container"></div>
  <script src="https://threejs.org/build/three.js"></script>
  <script src="https://threejs.org/examples/js/libs/dat.gui.min.js"></script>
  <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
  <script src="https://rawgit.com/Wilt/ThreeCSG/develop/ThreeCSG.js"></script>
  <script src="https://threejs.org/examples/js/loaders/MTLLoader.js"></script>
  <script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/loaders/LoaderSupport.js"></script>
  <script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/loaders/OBJLoader2.js"></script>
</body>

The cube on the left is just an extruded heart geometry protruding from inside a cube - they aren't joined, they just share the same position.

The one on the right is the CSG version of the left but I'm attempting to subtract the heart from the cube. The side that faces the camera is wrong in that that side of the mesh seems to be oozing down whereas the one at the back, if you look through, is perfectly fine.

What's causing this behaviour? I tried adding the property side: THREE.DoubleSide but it didn't fix the issue.

I also looked at this question here and tried calling updateMatrix() on the heart mesh before but it had no affect.

Thanks


回答1:


ThreeCSG cannot subtract a concave mesh correctly. But there is a workaround, since both halves of a heart or convex.

You can subtract the right half of the heart from a cuboid:

function getHeartShapeRight() {
    var x = 5, y = 10;
    var heartShape = new THREE.Shape();
    heartShape.moveTo(x - 5, y - 5);
    heartShape.bezierCurveTo(x - 5, y - 5, x - 4, y, x, y);
    heartShape.bezierCurveTo(x + 6, y, x + 6, y - 7, x + 6, y - 7);
    heartShape.bezierCurveTo(x + 6, y - 11, x + 3, y - 15.4, x - 5, y - 19);
    return heartShape;
}
// right cuboid
var cubeGeoR = new THREE.CubeGeometry(125, 250, 250);
var cubeMeshR = new THREE.Mesh(cubeGeoR);
cubeMeshR.position.x += 62.5;
var cubeBSPR = new ThreeBSP(cubeMeshR);

// right part of the heart
var heartShapeR = getHeartShapeRight();
var heartGeoR = getHeartGeometry(heartShapeR);
var meshToCutR = new THREE.Mesh( heartGeoR );
meshToCutR.scale.set(10,10,10);
meshToCutR.position.z -= 80;

// right subtract
var meshToCutBspR = new ThreeBSP(meshToCutR);
var resultBspR = cubeBSPR.subtract(meshToCutBspR);
var resultMeshR = resultBspR.toMesh(new THREE.MeshLambertMaterial({flatShading: true}));

And you can subtract the left half of the heart from a cuboid:

function getHeartShapeLeft() {
    var x = 5, y = 10;
    var heartShape = new THREE.Shape();
    heartShape.moveTo(x - 5, y - 19);
    heartShape.bezierCurveTo(x - 12, y - 15.4, x - 16, y - 11, x - 16, y - 7);
    heartShape.bezierCurveTo(x - 16, y - 7, x - 16, y, x - 10, y);
    heartShape.bezierCurveTo(x - 7, y, x - 5, y - 5, x - 5, y - 5);
    return heartShape;
}
// left cuboid
var cubeGeoL = new THREE.CubeGeometry(125, 250, 250);
var cubeMeshL = new THREE.Mesh(cubeGeoL);
cubeMeshL.position.x -= 62.5;
var cubeBSPL = new ThreeBSP(cubeMeshL);

// left part of the heart
var heartShapeL = getHeartShapeLeft();
var heartGeoL = getHeartGeometry(heartShapeL);
var meshToCutL = new THREE.Mesh( heartGeoL );
meshToCutL.scale.set(10,10,10);
meshToCutL.position.z -= 80;
var meshToCutBspL = new ThreeBSP(meshToCutL);

// left subtract
var meshToCutBspL = new ThreeBSP(meshToCutL);
var resultBspL = cubeBSPL.subtract(meshToCutBspL);
var resultMeshL = resultBspL.toMesh(new THREE.MeshLambertMaterial({flatShading: true}));

Finally you can create a union of both halves:

// union of left an right half
var unionBsp = resultBspL.union(resultBspR);
var unionMesh = unionBsp.toMesh(new THREE.MeshLambertMaterial({flatShading: true}));
scene.add(unionMesh);

See the code snippet:

(function onLoad() {
  var container, camera, scene, renderer;
  var grey = 0xD3D3D3;
  
  init();
  animate();

  function init() {
    container = document.getElementById('container');
    initScene();
    addGridHelper();
    addCamera();
    addLighting()
    addRenderer();
    addOrbitControls();

        // Creates a CSG cut on the cube based on the passed mesh
    createCSGDiecutHandle();
  }

  function createCSGDiecutHandle() {

    // left cuboid
    var cubeGeoL = new THREE.CubeGeometry(125, 250, 250);
    var cubeMeshL = new THREE.Mesh(cubeGeoL);
    cubeMeshL.position.x -= 62.5;
    var cubeBSPL = new ThreeBSP(cubeMeshL);
   
    // left part of the heart
    var heartShapeL = getHeartShapeLeft();
    var heartGeoL = getHeartGeometry(heartShapeL);
    var meshToCutL = new THREE.Mesh( heartGeoL );
    meshToCutL.scale.set(10,10,10);
    meshToCutL.position.z -= 80;
    var meshToCutBspL = new ThreeBSP(meshToCutL);

    // left subtract
    var meshToCutBspL = new ThreeBSP(meshToCutL);
    var resultBspL = cubeBSPL.subtract(meshToCutBspL);
    var resultMeshL = resultBspL.toMesh(new THREE.MeshLambertMaterial({flatShading: true}));

    // right cuboid
    var cubeGeoR = new THREE.CubeGeometry(125, 250, 250);
    var cubeMeshR = new THREE.Mesh(cubeGeoR);
    cubeMeshR.position.x += 62.5;
    var cubeBSPR = new ThreeBSP(cubeMeshR);

    // right part of the heart
    var heartShapeR = getHeartShapeRight();
    var heartGeoR = getHeartGeometry(heartShapeR);
    var meshToCutR = new THREE.Mesh( heartGeoR );
    meshToCutR.scale.set(10,10,10);
    meshToCutR.position.z -= 80;
    
    // right subtract
    var meshToCutBspR = new ThreeBSP(meshToCutR);
    var resultBspR = cubeBSPR.subtract(meshToCutBspR);
    var resultMeshR = resultBspR.toMesh(new THREE.MeshLambertMaterial({flatShading: true}));
    
    // union of left an right half
    var unionBsp = resultBspL.union(resultBspR);
    var unionMesh = unionBsp.toMesh(new THREE.MeshLambertMaterial({flatShading: true}));
     scene.add(unionMesh);
  }

  function getHeartShapeRight() {
    var x = 5, y = 10;
    var heartShape = new THREE.Shape();
    heartShape.moveTo(x - 5, y - 5);
    heartShape.bezierCurveTo(x - 5, y - 5, x - 4, y, x, y);
    heartShape.bezierCurveTo(x + 6, y, x + 6, y - 7, x + 6, y - 7);
    heartShape.bezierCurveTo(x + 6, y - 11, x + 3, y - 15.4, x - 5, y - 19);
    return heartShape;
  }

  function getHeartShapeLeft() {
    var x = 5, y = 10;
    var heartShape = new THREE.Shape();
    heartShape.moveTo(x - 5, y - 19);
    heartShape.bezierCurveTo(x - 12, y - 15.4, x - 16, y - 11, x - 16, y - 7);
    heartShape.bezierCurveTo(x - 16, y - 7, x - 16, y, x - 10, y);
    heartShape.bezierCurveTo(x - 7, y, x - 5, y - 5, x - 5, y - 5);
    return heartShape;
  }

  function getHeartGeometry(heartShape) {
    var extrudeSettings = {
      steps: 2,
      amount: 16,
      bevelEnabled: true,
      bevelThickness: 5,
      bevelSize: 1,
      bevelSegments: 1
    };
        return new THREE.ExtrudeBufferGeometry( heartShape, extrudeSettings );
  }

  /**** Helper functions ****/
  function computeBoundingBox(mesh) {
    var box = new THREE.Box3().setFromObject(mesh);
    var size = box.getSize();

    return {
      width: size.x,
      height: size.y,
      size: size
    }
  }

  /**** Basic Scene Setup ****/
  function initScene() {
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xffffff);
  }

  function addCamera() {
    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.set(349.11334070460066, 405.44010726325604, 359.3111192889029);
    scene.add(camera);
  }

  function addGridHelper() {
    var planeGeometry = new THREE.PlaneGeometry(2000, 2000);
    planeGeometry.rotateX(-Math.PI / 2);

    var planeMaterial = new THREE.ShadowMaterial({
      opacity: 0.2
    });
    var plane = new THREE.Mesh(planeGeometry, planeMaterial);
    plane.position.y = -200;
    plane.receiveShadow = true;
    scene.add(plane);

    var helper = new THREE.GridHelper(2000, 100);
    helper.material.opacity = 0.25;
    helper.material.transparent = true;
    scene.add(helper);

    var axis = new THREE.AxesHelper(1000);
    scene.add(axis);
  }

  function addLighting() {
    addHemisphereLight();
  }

  function addSpotLighting() {
    var light = new THREE.SpotLight(0xffffff, 1.5);
    light.position.set(0, 1500, 200);
    light.castShadow = true;
    light.shadow = new THREE.LightShadow(new THREE.PerspectiveCamera(70, 1, 200, 2000));
    light.shadow.bias = -0.000222;
    light.shadow.mapSize.width = 1024;
    light.shadow.mapSize.height = 1024;
    scene.add(light);
  }

  function addAmbientLight() {
    var ambientLight = new THREE.AmbientLight(0x404040);
    scene.add(ambientLight);
  }

  function addHemisphereLight() {
    var hemisphereLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);
    scene.add(hemisphereLight);
  }

  function addRenderer() {
    renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    container.appendChild(renderer.domElement);
    window.onresize = resize;
  }

  function addOrbitControls() {
    var controls = new THREE.OrbitControls(camera, renderer.domElement);
  }

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

  function animate() {
    requestAnimationFrame(animate);
    render();
  }

  function render() {
    renderer.render(scene, camera);
  }
})();
body {
  background: transparent;
  padding: 0;
  margin: 0;
  font-family: sans-serif;
}

#canvas {
  margin: 10px auto;
  width: 800px;
  height: 350px;
  margin-top: -44px;
}
<div id="container"></div>
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/libs/dat.gui.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://rawgit.com/Wilt/ThreeCSG/develop/ThreeCSG.js"></script>
<script src="https://threejs.org/examples/js/loaders/MTLLoader.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/loaders/LoaderSupport.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/loaders/OBJLoader2.js"></script>


来源:https://stackoverflow.com/questions/47819331/unexpected-result-using-threecsg

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!