2

I'm trying to create an accurate bounding box of an object, but it appears that if the object isn't aligned with the axis (I think) the box is not aligned with the object.

For example:

enter image description here

The pink and closer orange vertices are the Box3.min, Box3.max of this wall, but you see the red and green and blue are not on that wall. You can ignore the aqua vertices.

This is the code that creates the bounding box (returns Box3):

  static getWorldBoundingBox(model, dbId) {

    return new Promise(async(resolve, reject)=>{

      try{

        var fragIds = await ViewerToolkit.getFragIds(
          model, dbId);

        if(!fragIds.length){

          return reject('No geometry, invalid dbId?');
        }

        var fragList = model.getFragmentList();

        var fragbBox = new THREE.Box3();
        var nodebBox = new THREE.Box3();

        fragIds.forEach(function(fragId) {

          fragList.getWorldBounds(fragId, fragbBox);
          nodebBox.union(fragbBox);
        });

        return resolve(nodebBox);
      }
      catch(ex){

        return reject(ex);
      }
    });
  }

And that's how I create the box from the min, max:

    let ddd = new THREE.Vector3(min.x, min.y, min.z);
    let ddu = new THREE.Vector3(min.x, min.y, max.z);
    let dud = new THREE.Vector3(min.x, max.y, min.z);
    let udd = new THREE.Vector3(max.x, min.y, min.z);

    let duu = new THREE.Vector3(min.x, max.y, max.z);
    let uud = new THREE.Vector3(max.x, max.y, min.z);
    let udu = new THREE.Vector3(max.x, min.y, max.z);
    let uuu = new THREE.Vector3(max.x, max.y, max.z);

    this.drawVertices([ddd,ddu,dud,udd,duu,uud,udu,uuu]);

    let facesPoints = [
        {
            BL: ddd.clone(),
            UL: ddu.clone(),
            UR: udu.clone(),
            BR: udd.clone()
        },
        {
            BL: udd.clone(),
            UL: udu.clone(),
            UR: uuu.clone(),
            BR: uud.clone()
        },
        {
            BL: uud.clone(),
            UL: uuu.clone(),
            UR: duu.clone(),
            BR: dud.clone()
        },
        {
            BL: dud.clone(),
            UL: duu.clone(),
            UR: ddu.clone(),
            BR: ddd.clone()
        }
    ];

I want to avoid a brute force approach of sorting all distances of all pairs of vertices and taking the first two.

Is there another data structure will expose 8 points of a cube instead of just 2 that I could give to it polygons to build it just like in the above function?

shinzou
  • 4,689
  • 6
  • 48
  • 103

2 Answers2

2

Bounding boxes are world-axis-aligned. If your shape is rotated in space, just apply the shape's world matrix to (a copy of) its bounding box. That should give you the world bounding box for the shape.

In the example below, the red cube has its bounding box calculated from local space, and I apply the red cube's matrix to the bounding box. The green cube has its bounding box recalculated each frame, resulting in a world-axis-aligned box that grows and shrinks as the box rotates.

var renderer, scene, camera, controls, stats, rotationMatrix, tmpPos, cube1, cube2, cube1BBox, cube2BBox;

var WIDTH = window.innerWidth,
 HEIGHT = window.innerHeight,
 FOV = 35,
 NEAR = 1,
 FAR = 1000;
  
function populateExample(){
  rotationMatrix = new THREE.Matrix4().makeRotationY(0.5 * (Math.PI / 180));
 var cubeGeo = new THREE.BoxBufferGeometry(10, 10, 10),
  cube1Mat = new THREE.MeshPhongMaterial({ color: "red" }),
    cube2Mat = new THREE.MeshPhongMaterial({ color: "green" });
  cube1Mat.polygonOffset = true;
  cube1Mat.polygonOffsetFactor = 1;
  cube1Mat.polygonOffsetUnits = 0.5;
  cube2Mat.polygonOffset = true;
  cube2Mat.polygonOffsetFactor = 1;
  cube2Mat.polygonOffsetUnits = 0.5;
  
 cube1 = new THREE.Mesh(cubeGeo, cube1Mat);
 scene.add(cube1);
  
  cube2 = new THREE.Mesh(cubeGeo, cube2Mat);
 scene.add(cube2);
  
  cube1BBox = new THREE.BoxHelper(cube1, 0xffffff);
  scene.add(cube1BBox);
  
  cube2BBox = new THREE.BoxHelper(cube2, 0xffffff);
  scene.add(cube2BBox);
  
  cube1.position.set(-10, 0, 0);
  cube2.position.set(10, 0, 0);
  
  cube1BBox.position.set(-10, 0, 0);
}

function exampleRenderAction(){
  tmpPos.copy(cube1.position);
  
  cube1.position.sub(tmpPos);
  cube1.updateMatrix();
  cube1.applyMatrix(rotationMatrix);
  cube1.position.add(tmpPos);
  cube1.updateMatrix();
  
  cube1BBox.matrix.copy(cube1.matrix);
  
  tmpPos.copy(cube2.position);
  
  cube2.position.sub(tmpPos);
  cube2.updateMatrix();
  cube2.applyMatrix(rotationMatrix);
  cube2.position.add(tmpPos);
  cube2.updateMatrix();
  
  cube2BBox.update();
}

function init() {
  tmpPos = new THREE.Vector3();
  rotation = 0;
  rotationSpeed = 0.5;
 document.body.style.backgroundColor = "slateGray";

 renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });

 document.body.appendChild(renderer.domElement);
 document.body.style.overflow = "hidden";
 document.body.style.margin = "0";
 document.body.style.padding = "0";

 scene = new THREE.Scene();

 camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
 camera.position.set(0, 40, 40);
 scene.add(camera);

 controls = new THREE.TrackballControls(camera, renderer.domElement);
 controls.dynamicDampingFactor = 0.5;
 controls.rotateSpeed = 3;

 var light = new THREE.PointLight(0xffffff, 1, Infinity);
 camera.add(light);

 stats = new Stats();
 stats.domElement.style.position = 'absolute';
 stats.domElement.style.top = '0';
 document.body.appendChild(stats.domElement);

 resize();
 window.onresize = resize;

 populateExample();

 animate();
}

function resize() {
 WIDTH = window.innerWidth;
 HEIGHT = window.innerHeight;
 if (renderer && camera && controls) {
  renderer.setSize(WIDTH, HEIGHT);
  camera.aspect = WIDTH / HEIGHT;
  camera.updateProjectionMatrix();
  controls.handleResize();
 }
}

function render() {
 renderer.render(scene, camera);
}

function animate() {
 requestAnimationFrame(animate);
 render();
  exampleRenderAction();
 controls.update();
 stats.update();
}

function threeReady() {
 init();
}

(function () {
 function addScript(url, callback) {
  callback = callback || function () { };
  var script = document.createElement("script");
  script.addEventListener("load", callback);
  script.setAttribute("src", url);
  document.head.appendChild(script);
 }

 addScript("https://threejs.org/build/three.js", function () {
  addScript("https://threejs.org/examples/js/controls/TrackballControls.js", function () {
   addScript("https://threejs.org/examples/js/libs/stats.min.js", function () {
    threeReady();
   })
  })
 })
})();

Extended answer based on clarifications in the comments:

A bounding box is a THREE.Box3, which contains min and max THREE.Vector3s. So to get the 8 corners of the bounding box like you already do:

var corners = [
    new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.min.z),
    new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.max.z),
    new THREE.Vector3(bbox.min.x, bbox.max.y, bbox.max.z),
    new THREE.Vector3(bbox.min.x, bbox.max.y, bbox.min.z),
    new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.max.z),
    new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.max.z),
    new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.min.z),
    new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.min.z)
];

You can arrange these how you like. To convert these to world coordinates, You'll need to perform one more step. Note that the following step is destructive, so if you need to keep the original corner values, you'll need to save off a copy of them.

The vertices are currently local to the object, so you need to update them with the object's matrix:

for(var i = 0, len = corners.length; i < len; ++0){
    // this will apply all transformations from all parents
    corners[i].applyMatrix4(myObj.matrixWorld);
}

Alternately, you could convert the points into world coordinates using localToWorld.

for(var i = 0, len = corners.length; i < len; ++0){
    // this literally does the same thing as the code above
    myObj.localToWorld(corners[i]);
}

But don't do both, or you'll end up with incorrect values.

TheJim01
  • 6,362
  • 1
  • 17
  • 44
  • Where do you get the corners of the cube? (8 Vectors3s) – shinzou May 09 '17 at 15:08
  • Which cube? The geometry, or the bounding box? And do you mean the world coordinates of the vertices of a transformed cube? – TheJim01 May 09 '17 at 16:33
  • World coords, the positions of the cube in the world, say I wanted to draw lines between those points to make a wireframe of the cube. – shinzou May 09 '17 at 19:18
  • Getting the vertices depends on whether you're using [`THREE.Geometry`](https://threejs.org/docs/#api/core/Geometry) or [`THREE.BufferGeometry`](https://threejs.org/docs/#api/core/BufferGeometry). The former has an array of vertices (`myGeometry.vertices`) in `THREE.Vector3` format, while the latter has an attribute which contains the vertex position information (`myBufferGeometry.attributes.position`) in a TypedArray (each vertex is the sub-set of every three values). – TheJim01 May 09 '17 at 19:26
  • That said, if you want a wire frame, I suggest creating it using the vertices of your base cube (you don't need world coordinates), and applying the cube's matrix to the wireframe mesh's matrix. – TheJim01 May 09 '17 at 19:26
  • I need the actual world coordinates (Vector3) of the corners of the node (a node is a part of the model, like that wall in the picture). I can get the vertices of the polygons that make that node, but I want just the corners. – shinzou May 09 '17 at 19:32
  • I have updated my answer. Let me know if I misunderstood your question. – TheJim01 May 10 '17 at 15:13
  • This will result in the same problem in my question, the 8 corners won't be on the object if it's not aligned with the axis. In the mean time I found a way to get those 8 corners from all the vertices of the polygons of the object using a graph diameter algorithm, I might post it later if I'll have the time. – shinzou May 11 '17 at 12:29
0

I found a way to do this, first I collected all the vertices to a general set using this function from Autodesk's viewer extensions (Mesh data):

  static getMeshVertices(viewer, fragId) {

    var fragProxy = viewer.impl.getFragmentProxy(
      viewer.model,
      fragId);

    var renderProxy = viewer.impl.getRenderProxy(
      viewer.model,
      fragId);

    fragProxy.updateAnimTransform();

    var matrix = new THREE.Matrix4();
    fragProxy.getWorldMatrix(matrix);

    const verticesSet = new GeneralSet();
    const geometry = renderProxy.geometry;
    const attributes = geometry.attributes;

    if (attributes && attributes.index !== undefined) {

      const indices = attributes.index.array || geometry.ib;
      const positions = geometry.vb ? geometry.vb : attributes.position.array;
      const stride = geometry.vb ? geometry.vbstride : 3;
      let offsets = geometry.offsets;

      if (!offsets || offsets.length === 0) {

        offsets = [{ start: 0, count: indices.length, index: 0 }];
      }

      for (var oi = 0, ol = offsets.length; oi < ol; ++oi) {

        const start = offsets[oi].start;
        const count = offsets[oi].count;
        const index = offsets[oi].index;

        for (var i = start, il = start + count; i < il; i += 3) {

          const vA = new THREE.Vector3();
          const vB = new THREE.Vector3();
          const vC = new THREE.Vector3();
          const a = index + indices[i];
          const b = index + indices[i + 1];
          const c = index + indices[i + 2];

          vA.fromArray(positions, a * stride);
          vB.fromArray(positions, b * stride);
          vC.fromArray(positions, c * stride);

          vA.applyMatrix4(matrix);
          vB.applyMatrix4(matrix);
          vC.applyMatrix4(matrix);

          verticesSet.add(vA);
          verticesSet.add(vB);
          verticesSet.add(vC);
        }
      }

      return verticesSet;
    }
    else {

      var positions = geometry.vb ? geometry.vb : attributes.position.array;
      var stride = geometry.vb ? geometry.vbstride : 3;

      for (var i = 0, j = 0, il = positions.length; i < il; i += 3, j += 9) {
        let vA = new THREE.Vector3();
        let vB = new THREE.Vector3();
        let vC = new THREE.Vector3();
        var a = i;
        var b = i + 1;
        var c = i + 2;

        vA.fromArray(positions, a * stride);
        vB.fromArray(positions, b * stride);
        vC.fromArray(positions, c * stride);

        vA.applyMatrix4(matrix);
        vB.applyMatrix4(matrix);
        vC.applyMatrix4(matrix);

        verticesSet.add(vA);
        verticesSet.add(vB);
        verticesSet.add(vC);
      }

      return verticesSet;
    }
  }

Then I used a graph diameter algorithm from here: https://cs.stackexchange.com/a/213/43035 once on the set of vertices (as if the set represent a complete graph) to get two opposite corners, lets call them u, w.

Then I removed u, w from the set of vertices and ran the graph diameter again, getting the two other corners.

Now with four corners it's possible to generate all the rest, and to sort them, there are 3 conditions to check for each of the 4 corners (which will reveal the 4 other corners), distance from camera (closer or further), height (upper or lower corner) and left or right from the middle of the object (using a cross and dot, like this https://forum.unity3d.com/threads/left-right-test-function.31420/ (they have js code)).

This will give you 8 corners such that you'll know which corner is where, and the corners are on the object, it doesn't matter how the object is aligned with the axes.

Community
  • 1
  • 1
shinzou
  • 4,689
  • 6
  • 48
  • 103