three.jsautodesk-forgeautodesk-viewercsgthreecsg

3D Boolean operations with Three CSG


Following the example here:

http://learningthreejs.com/blog/2011/12/10/constructive-solid-geometry-with-csg-js/

And using Three.js with https://github.com/chandlerprall/ThreeCSG, I'm trying to do 3D boolean operations on nodes from the model. Like for example if I have a wall with a window, I want to do invert() on that to get just the window.

I have a function that returns all the vertices of the polygons of a node, here's an example of vertices of an object without holes https://pastebin.com/8dhYzPwE.

I'm using ThreeCSG like this:

    const geometryThree = new THREE.Geometry();

    geometryThree.vertices.push(
        ...vertices
    );

    const geometryCsg = new ThreeBSP(geometryThree);

But that's what I'm getting in geometryCsg:

"{
    "matrix": {
        "elements": {
            "0": 1,
            "1": 0,
            "2": 0,
            "3": 0,
            "4": 0,
            "5": 1,
            "6": 0,
            "7": 0,
            "8": 0,
            "9": 0,
            "10": 1,
            "11": 0,
            "12": 0,
            "13": 0,
            "14": 0,
            "15": 1
        }
    },
    "tree": {
        "polygons": []
    }
}"

I think it's because the geometry.faces.length is 0.

How can I make the vertices array to be a proper Three.Geometry such that the faces won't be empty? Geometry.elementsNeedsUpdate doesn't work...

Is there an example that uses polygons of a shape as an array of Vector3s and transforms that to csg?


Solution

  • I just worked on a demo using THREE csg: the Viewer meshes have an indexed array of vertices so you cannot create a BSP directly out of it. Also my code is using a web worker to process the meshes in order to keep the UI responsive with large models, so I need first to send the mesh data to the worker and reconstruct a simple THREE.Mesh on the worker side, the code looks like below:

    // Sends component geometry to the web worker  
    postComponent (dbId) {
    
      const geometry = this.getComponentGeometry(dbId)
    
      const msg = {
        boundingBox: this.getComponentBoundingBox(dbId),
        matrixWorld: geometry.matrixWorld,
        nbMeshes: geometry.meshes.length,
        msgId: 'MSG_ID_COMPONENT',
        dbId
      }
    
      geometry.meshes.forEach((mesh, idx) => {
    
        msg['positions' + idx] = mesh.positions
        msg['indices' + idx] = mesh.indices
        msg['stride' + idx] = mesh.stride
      })
    
      this.worker.postMessage(msg)
    }
    
    // get geometry for all fragments in a component
    getComponentGeometry (dbId) {
    
      const fragIds = Toolkit.getLeafFragIds(
        this.viewer.model, dbId)
    
      let matrixWorld = null
    
      const meshes = fragIds.map((fragId) => {
    
        const renderProxy = this.viewer.impl.getRenderProxy(
          this.viewer.model,
          fragId)
    
        const geometry = renderProxy.geometry
    
        const attributes = geometry.attributes
    
        const positions = geometry.vb
          ? geometry.vb
          : attributes.position.array
    
        const indices = attributes.index.array || geometry.ib
    
        const stride = geometry.vb ? geometry.vbstride : 3
    
        const offsets = geometry.offsets
    
        matrixWorld = matrixWorld ||
        renderProxy.matrixWorld.elements
    
        return {
          positions,
          indices,
          offsets,
          stride
        }
      })
    
      return {
        matrixWorld,
        meshes
      }
    }
    
    
    // On the worker side reconstruct THREE.Mesh
    // from received data and create ThreeBSP
    function buildComponentMesh (data) {
    
      const vertexArray = []
    
      for (let idx=0; idx < data.nbMeshes; ++idx) {
    
        const meshData = {
          positions: data['positions' + idx],
          indices: data['indices' + idx],
          stride: data['stride' + idx]
        }
    
        getMeshGeometry (meshData, vertexArray)
      }
    
      const geometry = new THREE.Geometry()
    
      for (var i = 0; i < vertexArray.length; i += 3) {
    
        geometry.vertices.push(vertexArray[i])
        geometry.vertices.push(vertexArray[i + 1])
        geometry.vertices.push(vertexArray[i + 2])
    
        const face = new THREE.Face3(i, i + 1, i + 2)
    
        geometry.faces.push(face)
      }
    
      const matrixWorld = new THREE.Matrix4()
    
      matrixWorld.fromArray(data.matrixWorld)
    
      const mesh = new THREE.Mesh(geometry)
    
      mesh.applyMatrix(matrixWorld)
    
      mesh.boundingBox = data.boundingBox
    
      mesh.bsp = new ThreeBSP(mesh)
    
      mesh.dbId = data.dbId
    
      return mesh
    }
    
    function getMeshGeometry (data, vertexArray) {
    
      const offsets = [{
        count: data.indices.length,
        index: 0,
        start: 0}
      ]
    
      for (var oi = 0, ol = offsets.length; oi < ol; ++oi) {
    
        var start = offsets[oi].start
        var count = offsets[oi].count
        var index = offsets[oi].index
    
        for (var i = start, il = start + count; i < il; i += 3) {
    
          const a = index + data.indices[i]
          const b = index + data.indices[i + 1]
          const c = index + data.indices[i + 2]
    
          const vA = new THREE.Vector3()
          const vB = new THREE.Vector3()
          const vC = new THREE.Vector3()
    
          vA.fromArray(data.positions, a * data.stride)
          vB.fromArray(data.positions, b * data.stride)
          vC.fromArray(data.positions, c * data.stride)
    
          vertexArray.push(vA)
          vertexArray.push(vB)
          vertexArray.push(vC)
        }
      }
    }
    

    The complete code of my sample is there: Wall Analyzer and the live demo there.