3

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?

shinzou
  • 5,850
  • 10
  • 60
  • 124

1 Answers1

4

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.

Felipe
  • 4,325
  • 1
  • 14
  • 19
  • Does this example work on all of the nodes in the model? Is that why you needed a web worker? – shinzou Jun 07 '17 at 14:28
  • Not all nodes, only Walls and Floors but it requires a lot of CPU if you deal with a large model. Using a web worker keeps the browser from crashing and the UI responsive. The part that converts a single node to ThreeBSP is the whole code I just posted. Take a close look at what it is doing. You can see it creates a new ThreeBSP at the end of "buildComponentMesh" method. It should be rather straightforward to adapt the logic if you don't want to use a worker. – Felipe Jun 07 '17 at 14:37
  • I see, thanks a lot. Have you tried to use the csg subtract method? I'm getting an error here: https://github.com/chandlerprall/ThreeCSG/blob/master/threeCSG.es6#L528 that the divider is undefined when I try to invert the geometry like so: `let a = new ThreeBSP(new THREE.Geometry()); a.subtract(geometryCsg)` – shinzou Jun 07 '17 at 14:53
  • Are you adding some vertices/faces to this new geometry or is that all the code? You are subtracting something from an empty geometry, I'm not sure if that's supposed to work. The 3 examples at https://github.com/chandlerprall/ThreeCSG/blob/master/examples.html are working fine, so the problem must be your inputs. – Felipe Jun 07 '17 at 15:34
  • I want to inverse the shape, they have an inverse method in the original CSG: http://evanw.github.io/csg.js/docs/ but it looks like ThreeCSG doesn't have this method. – shinzou Jun 08 '17 at 07:41
  • This sounds like a question for the author of the library, sorry my knowledge of the CSG is limited to the simple examples that are provided, so your question is a bit off-topic as far as Forge and the Viewer are concerned. – Felipe Jun 08 '17 at 08:46
  • Yes I realized that. The library looks abandoned though. Thanks anyway. – shinzou Jun 08 '17 at 08:47
  • I can see it has an invert method: https://github.com/chandlerprall/ThreeCSG/blob/master/ThreeCSG.js#L508 – Felipe Jun 08 '17 at 09:18
  • I think it's private because it's not in the scope when doing `const geometryCsg = new ThreeBSP(geometryThree); geometryCsg.invert();` – shinzou Jun 08 '17 at 09:41
  • Seems to be on the Node object, should be rather straightforward to use it, although I haven't tried: var inv = bsp.tree.invert(); new ThreeBSP(inv): https://github.com/chandlerprall/ThreeCSG/blob/master/ThreeCSG.js#L91 – Felipe Jun 08 '17 at 12:37