问题
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:
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?
回答1:
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.Vector3
s. 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.
回答2:
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.
来源:https://stackoverflow.com/questions/43871636/accurate-bounding-box-of-an-object