Background: Getting the size vector of a mesh is easy with .setFromObject(...) and .getSize(...), such as in
new THREE.Box3().setFromObject( object ).getSize( new THREE.Vector3() );
In the code where it is implemented (https://github.com/mrdoob/three.js/blob/master/src/math/Box3.js) it shows that setFromObject(...) accounts for object and all its children as well, since it considers vertices for calculations. I was wondering how can this method be overridden/ new method can be generated which takes account of the object itself only. Currently, since all vertices are pushed into a single array, I am at loss of ways to think how to go about that. Any help/ pointers would be appreciated. My target it to get only the size of parent object and not the children, howmanyever or wherever they are.
A custom method for this could look like so: Given a 3D object, you could use Object3D.traverseAncestors() and then use a similar code like in Box3.expandByObject() in order to expand the AABB for each parent. You can’t use Box3.expandByObject() directly since the method also includes child objects.
Thanks @Mugen87, I actually got it just now this way. I was going to update the question with this, just when you sent out your suggestion.
function setSizeFromParent ( object ) {
object.updateMatrixWorld( true );
var min = new THREE.Vector3( + Infinity, + Infinity, + Infinity);
var max = new THREE.Vector3( - Infinity, - Infinity, - Infinity);
var scope, i, l;
var v1 = new THREE.Vector3();
var geometry = object.geometry;
if ( geometry !== undefined ) {
if ( geometry.isGeometry ) {
var vertices = geometry.vertices;
for ( i = 0, l = vertices.length; i < l; i ++ ) {
v1.copy( vertices[i] );
v1.applyMatrix4( object.matrixWorld );
min.min( v1 );
max.max( v1 );
}
} else if ( geometry.isBufferGeometry ) {
var attribute = geometry.attributes.position;
if ( attributes !== undefined ) {
for ( i = 0, l = attributes.count; i < l; i ++ ) {
v1.fromBufferAttribute( attribute, i ).applyMatrix4( object.matrixWorld );
min.min ( v1 );
max.max( v1 );
}
}
}
}
return { min, max };
}
And when I wanted to use it:
var sizeFromParent = setSizeFromParent( object );
const acquiredScale = new THREE.Box3( sizeFromParent.min, sizeFromParent.max ).getSize( new THREE.Vector3() );
This was my use case. The arrows which are connected to the labels (HTML elements) were actually added as children to the object, which is why when I was using the “dotted” ring anchor (in light cyan) to scale an object up, the size of the anchor since was based on the logic above was taking in the vertices of the child nodes as well. And this is why the anchor was being scaled erratically.
I will check out traverseAncestors() as you have suggested, and see if it can be done easily with that.
PS: Vars can be named better. I truly am thankful tons for you guys making yourselves available to take questions. I started with threejs around 2 weeks back and it is a time of wonder
Um, your method does not traverse the hierarchy up. I also do not see where you work with the Object3D.parent reference. It seems you just compute the AABB for the current object.
Yeah, that actually was what I needed. I noticed that I actually simply needed to work with the vertices of current object (selected object) and nothing more. Hence traverse was not helping me. Plus, I do not need to reference either the parent or a child. I had to reference an object which was clicked upon, that’s all.
Oh I see that I worded the question a bit wrong. My apologies, I will edit it. I meant “the object” and not its parent.
Question: Wouldn’t .traverseAncestors() take into consideration the scene as well, if an object is added in a scene?
@Mugen87 If I have an object, its children, and grandchildren, and I want to apply .setFromObject() only for a child and neither the object, nor the child’s children, do you think there can be a case for having a method in the library, as in:
I thought it I got it but apparently not. On setting an object to a different scale, using .scale.set( x, y, z ) the boundingBox of the geometry doesn’t seem to change. Any reflection here?
Are you sure the respective world matrix is up to date and contains the new sacle setting? Ensure to use Object3D.updateMatrixWorld() before you compute the AABB.
function getSizeFromObj( object ) {
if ( object instanceof THREE.Mesh ) {
// If geometry can be obtained from object, do not traverse the children
var geometry = object.geometry;
geometry.computeBoundingBox();
object.updateMatrixWorld();
return geometry.boundingBox.getSize( new THREE.Vector3() );
} else {
// Fallback method as default
object.updateMatrixWorld();
console.log(new THREE.Box3().setFromObject( object ));
return new THREE.Box3().setFromObject( object ).getSize( new THREE.Vector3() );
}
}
I tried updating that wherever I am using this in the code actually, such as the below:
...
// objectControls.controlledObject is the same mesh as earlier
objectControls.controlledObject.updateMatrixWorld();
objectControls.scaler.setSize( objectControls.controlledObject );
...
but to no avail. It’s not getting updated. The scaler is that ring shape thing which I am using to scale up/ down my objects. Once mouseup event is called, it should use the size of my object it set the size of the scaler, but scaler retain its original size. If I use usual .setFromObject() method directly with object, it works fine but then it considers the child objects as well, which I don’t want.
Don’t know if that’s the correct way to get only the object bounding box, just skipping the expandByObject with child objects.
It’s working for me:
import { Box3 } from 'three';
class Box3Extension {}
Box3Extension.setFromObject = (box3, object, traverse = true) => {
if (!box3) box3 = new Box3();
// if including children then return the normal method
if (traverse) return box3.setFromObject(object);
// if NOT including children use the modified method
box3.makeEmpty();
return Box3Extension.expandByObject(box3, object, traverse);
};
Box3Extension.expandByObject = (box3, object) => {
const _box = new Box3();
object.updateWorldMatrix(false, false);
const geometry = object.geometry;
if (geometry !== undefined) {
if (geometry.boundingBox === null) {
geometry.computeBoundingBox();
}
_box.copy(geometry.boundingBox);
_box.applyMatrix4(object.matrixWorld);
box3.union(_box);
}
return box3;
};
export default Box3Extension;