How to add loaded gltf to scene dynamically (after loading it)?

Is there a way to dynamically add and/or remove a loaded gltf (model from blender, for example) into the scene later on (based on user/game events or anything like that)? I’ve tried the following already:

let pizzaMonster = new THREE.Object3D();
loader.load( 'assets/pizzaMonster.glb', function ( gltf ) {
		pizzaMonster = gltf.scene;
		//scene.add( pizzaMonster );
	}, undefined, function ( error ) {
		console.error( error );
	} );

And then later in the code, maybe when the camera controls intersects some area, I can run:

scene.add(pizzaMonster);

This does not work - my scene remains blank, but it also does not throw an error in the browser. Any ideas?

Ideally, I can load all of my assets into different variables and then add/remove them at any point based on other events. How is this done?

pizzaMonster.scene.visible = false / true;
pizzaMonster.scene.children[0].visible = false / true; (many models)
Those are probably the easiest ways

This would work to simply hide it, but my ultimate goal is to update the map of my scene by adding a new room to the other side of an existing room once the user (camera controls) gets to a certain mark in the existing room. So this would require (I think) dynamic loading and positioning. Just making it not visible might not be enough.

instead of doing this:

pizzaMonster = gltf.scene;

in the load callback…

try

pizzaMonster.add( gltf.scene );

what you have there is called a race condition. you load something and hope it’s there at a later point. threejs is inherently async and trying to counter that with hopes and good luck is futile — it will all come down and crash. javascript has async/await and promises, you should not rely on function callbacks.

this code for instance is an anti pattern, it was once was named “callback hell”, i would suggest you don’t do that unless you want everything to be accidental and random.

loader.load(url, (gltf) => {
  foo.add(gltf)
  ...

instead load your assets first, then you can mount them whenever you want

async function app() {
  const [font, hdri, level1, level2] = await Promise.all([
    fontLoader.loadAsync("/font.json")),
    rgbeLoader.loadAsync("/warehouse.hdr")),
    gltfLoader.loadAsync("/level1.glb")),
    gltfLoader.loadAsync("/level2.glb")),
    // ...
  ])
  scene.add(level1)
  scene.background = hdri
  const font = new TextGeometry('hello', { font })
  // ...  

you could in theory make sections of your app async and await them, for instance a game with 3 levels should only load level 1 initially and level 2 and 3 come after and await their own critical assets.

2 Likes

That looks chaotic wouldn’t it make more sense to use…

pizzaMonster = await loader.loadAsync('url') 

In the case of going an async await route like you suggest?

Three has a LoadingManager that you can pass into the gltf loader…

const manager = new THREE.LoadingManager()

manager.onLoad = () => {
  init() 
} 

const loader = new GLTFLoader(manager) 

let pizzaMonster

loader.load( 'assets/pizzaMonster.glb', function ( gltf ) {
    pizzaMonster = gltf.scene;
		
    }, undefined, function ( error ) {
		console.error( error );
} );


this way the manager will wait till any and all models are loaded through gltf loader and call init once loading is complete meaning at this point you can add and remove models in the way you’re doing already because you’ve waited for them to load…

same thing, but not all loaders have that (i think). but it still cannot be

const foo = await loader.loadAsync(url1) 
const bar = await loader.loadAsync(url2)

it would cause a waterfall, foo loads first, then bar. this would delay the site. the browser can load assets in parallel.

const [foo, bar] = await Promise.all([
  loader.loadAsync(url1), 
  loader.loadAsync(url2)
])

as for loadingmanager, imo that’s useful for loading indications but coding like that seems indirect, maybe even fragile. you don’t have to know threejs specifics to handle async, vanilla javascript async/await imo is more important to know when starting with threejs than how meshes and shaders work since three is fundamentally async and race conditions will will always come back to bite unless there’s a solid foundation.

2 Likes

loadAsync is a method of the base Loader class so it should be accessible through all loaders that extend it, I think it’s all loaders?

1 Like

if that’s the case that’s of course better than promises, i’ll change the code above.

1 Like