.setFromObject() only for Object and not its children

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.

1 Like

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.

2 Likes

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 :slight_smile:

1 Like

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?

Yes, but since the scene node itself has no geometry nothing would happen.

1 Like

@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:

.setFromObject( object, [optional] traversal: Bool )

A optional bool to set whether traversal is to be done or not, with default to allow traversal.

1 Like

I personally think this setup is too complicated. You can still use the AABB directly from the geometry if you need a “local” bounding box, see

https://threejs.org/docs/index.html#api/en/core/BufferGeometry.boundingBox

If you copy this AABB and then multiply it with the current world matrix, you have the AABB in world space just for this object.

1 Like

Yep, got it. Will try the bounding box method, and if it works well and is less complex, will go for that.

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.

2 Likes

Yes I did.

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:

mesh.setSize = function ( object ) {
	object.updateMatrixWorld();
	var sizeFromObject = getSizeFromObj( object );
	var maxScale = ( sizeFromObject.x <= sizeFromObject.z ? sizeFromObject.z : sizeFromObject.x );
	mesh.scale.set( maxScale, maxScale, maxScale );
}
...
// 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.
ezgif-5-ebbf6a9bfe11

@Mugen87 I will resolve it by my own and post when done, please don’t help out since I’m sure there should be an issue somewhere in my code. Tx.

1 Like

Have you resolved it @apraka16 ?

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;
1 Like

Thank you very much for the solution, you helped me a lot! :slightly_smiling_face: