Hi!
I’m trying to run a function over the entire scene once all objects have been added. However, since scene.add() appears to return immediately, calling my function right away results in it running over an empty or incomplete scene.
Is there a way to wait for scene.add() to finish? I have not found a callback and so far the only “solution” seems to be to use a promise with setTimeout and wait for an arbitrary amount of time before I trigger the function - which is hardly ideal
Any better way to do this?
Cheers
Michael
Can you please demonstrate this issue with a live example? That sounds like a bug in your application.
Object3D.add()
is synchronous. When it returns, the given object(s) was/were added to the instance (in your case the scene).
Here’s the code. It loads a mesh into the scene, then attempts to move the camera to frame all objects. Running it manually or with a delay after scene.add works correctly. Running it immediately after (as it is here) does not. What am I missing here?
const reallyThis = this;
....
this.zoomCameraToSelection = ( camera, controls, selection, fitRatio = 1.2 ) => {
const box = new THREE.Box3();
scene.traverse((child)=>{
if (child instanceof THREE.Mesh) {
box.expandByObject(child);
}
});
const size = box.getSize( new THREE.Vector3() );
const center = box.getCenter( new THREE.Vector3() );
const maxSize = Math.max( size.x, size.y, size.z );
const fitHeightDistance = maxSize / ( 2 * Math.atan( Math.PI * camera.fov / 360 ) );
const fitWidthDistance = fitHeightDistance / camera.aspect;
const distance = fitRatio * Math.max( fitHeightDistance, fitWidthDistance );
const direction = controls.target.clone()
.sub( camera.position )
.normalize()
.multiplyScalar( distance );
controls.maxDistance = distance * 10;
controls.target.copy( center );
camera.near = distance / 100;
camera.far = distance * 100;
camera.updateProjectionMatrix();
camera.position.copy( controls.target ).sub(direction);
controls.update();
}
this.loadMesh = (meshPath, frameOnLoad=false) => {
var loader = new GLTFLoader();
// Load a glTF resource
loader.load(
meshPath,
(gltf) => {
gltf.scene.traverse(child => {
if (child instanceof THREE.Mesh) {
child.material = new THREE.MeshStandardMaterial({
color: 0xffffff,
flatShading: false,
envMap: environmentMap,
roughness: 0.2
});
}
});
scene.add(gltf.scene);
reallyThis.zoomCameraToSelection( camera, controls, gltf.scene, 1.2 ) ;
},
// called while loading is progressing
function(xhr) {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
// called when loading has errors
function(error) {
console.log("An error happened" + error);
}
);
};
1 Like
Box3.expandByObject
is recursive, so you don’t need to traverse the scene (or selection
? Wasn’t that intended?) with it.
Note that Box3.expandByObject
calls object.updateWorldMatrix
with updateParents=false
and updateChildren=false
: three.js/src/math/Box3.js at 7e0a78beb9317e580d7fa4da9b5b12be051c6feb · mrdoob/three.js · GitHub three.js/src/core/Object3D.js at 7e0a78beb9317e580d7fa4da9b5b12be051c6feb · mrdoob/three.js · GitHub
World matrices are updated during rendering, and out-of-date matrices may explain why you don’t get the right bounding box when you call your method immediately. I wonder if this is really the intended behavior of Box3.expandByObject
. Object3D.updateWorldMatrix
was introduced recently, and is not even documented yet.
Ha! Thanks Elias, me calling expandByObject recursively was the issue! Now that I just run it on “selection” it works Thanks
1 Like
Good to hear! And also a relief that expandByObject
works as intended when used right.