Scene.add - some way to see if it finished? (callback, promise....)

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

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? :thinking:

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

1 Like

Good to hear! And also a relief that expandByObject works as intended when used right.