Skip to content


 Zabbix
 Grafana
 Prometheus
 React Three Fiber
 Threejs and TypeScript
 SocketIO and TypeScript
 Blender Topological Earth
 Sweet Home 3D
 Design Patterns Python
 Design Patterns TypeScript
   
 Course Coupon Codes
Three.js and TypeScript
Kindle Edition
$6.99 $9.99 Paperback 
$22.99 $29.99




Design Patterns in TypeScript
Kindle Edition
$6.99 $9.99 Paperback
$11.99 $19.99




Design Patterns in Python
Kindle Edition
$6.99 $9.99 Paperback
$11.99 $19.99




Loading Multiple Assets

Description

You may want to load multiple models, textures, animations, fonts, sounds or other assets in your application.

All Three.js loaders at some point in their hierarchy, extend from the THREE.Loader base class.

The THREE.Loader base class has a default THREE.LoadingManager object under the hood to keep track of loaded and pending objects and data.

The THREE.LoadingManager knows when a resource has started loading, has finished loading, the progress of a download (in most browsers), and if an error occurred while attempting to download the resource.

When you instantiate any Threejs loaders, e.g., GLTFLoader, OBJLoader, TextureLoader, etc..., you can pass in a callback function to use when the resource has loaded (onLoad), is downloading (onProgress) and if there was any error (onError) during the download.

E.g., the GLTFLoader

new GLTFLoader().load(
    'model.glb',
    // onLoad
    function (gltf) {
        scene.add(gltf)
    },
    // onProgress (Optional)
    function (xhr) {
        console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
    },
    // onError (Optional)
    function (err) {
        console.error('An error happened')
    }
)

It is ok to use the same loader instance to load many models or assets at the same time.

E.g.,

const loader = new GLTFLoader()
loader.load('model1.glb', function (gltf) {
    scene.add(gltf.scene)
})
loader.load('model2.glb', function (gltf) {
    scene.add(gltf.scene)
})
loader.load('model3.glb', function (gltf) {
    scene.add(gltf.scene)
})

This is fine. All models will be downloaded asynchronously (in parallel), independently of each other, and added to the scene in order of whichever onLoad callback finished first/next.

Loading Dependent Objects

Loading multiple resources asynchronously is normally not a problem if each of your models or assets are capable of being treated independently of each other.

In some situations, you may be downloading other models or assets that are dependent on a resource that has already been fully downloaded and setup in memory first.

Example, you need to add a wheel to a car chassis.

In this case you can load the wheel model in the car chassis onLoad callback. Now when you add the wheel to the chassis, you can be sure that the chassis has fully downloaded and is ready in memory for use.

E.g.,

const loader = new GLTFLoader()
loader.load('chassis.glb', function (gltf) {
    // chassis.glb has downloaded and is ready in memory for use. Lets continue,
    const chassis = gltf.scene
    loader.load('wheel.glb', function (gltf) {
        chassis.add(gltf.scene)
    })
    scene.add(chassis)
})

In the above example code, the wheel object is now a child component of the chassis object. The chassis needed to be setup in memory first before adding the wheel to it.

If both models where instead loaded asynchronously, as shown earlier, and the wheels onLoad callback happened to execute before the chassis onload callback, then your program would crash and show an undefined error in the browsers JavaScript console.

Uncaught TypeError: Cannot read property 'add' of undefined

The chassis object wouldn't yet have been fully instantiated, so wouldn't exist in memory yet. This situation is also known as a race condition. Race conditions can be a problem if you are asynchronously loading several objects in parallel and one of them is dependent on the other.

Also, a side note, since the wheel was added as a child of the chassis object, any changes to the chassis transforms, such as position, scale or rotation will automatically be reflected in the wheel object. Read more about Object3D Hierarchy.

Now, you probably want to see 4 wheels on this chassis and positioned accordingly, so this code below is one way to achieve that.

const loader = new GLTFLoader()
loader.load('chassis.glb', function (gltf) {
    const chassis = gltf.scene
    loader.load('wheel.glb', function (gltf) {
        const wheels = [
            gltf.scene,
            gltf.scene.clone(),
            gltf.scene.clone(),
            gltf.scene.clone(),
        ]
        wheels[0].position.set(-1, 0, 1)
        wheels[1].position.set(1, 0, 1)
        wheels[2].position.set(-1, 0, -1)
        wheels[3].position.set(1, 0, -1)
        chassis.add(...wheels)
    })
    scene.add(chassis)
})

LoadAsync

Now, you may not like to use nested callbacks for whatever reason. In this case, another option you have is to load your dependent assets synchronously using await and the loaders loadAsync method. loadAsync is equivalent to the load method, except that it returns a promise that can be awaited.

In the example below, loadAsync will finish loading the dependent chassis model before continuing to the next load method.

const loader = new GLTFLoader()
let chassis: Object3D
await loader.loadAsync('chassis.glb').then((gltf) => {
    chassis = gltf.scene
})
loader.load('wheel.glb', function (gltf) {
    const wheels = [
        gltf.scene,
        gltf.scene.clone(),
        gltf.scene.clone(),
        gltf.scene.clone(),
    ]
    wheels[0].position.set(-1, 0, 1)
    wheels[1].position.set(1, 0, 1)
    wheels[2].position.set(-1, 0, -1)
    wheels[3].position.set(1, 0, -1)
    chassis.add(...wheels)
    scene.add(chassis)
})

In the above example code, the load method will still run asynchronously, but start only after the awaited loadAsync has finished.

You also have the option to run that second load method using loadAsync instead. In that case, your script would wait for the second model to be fully downloaded before continuing.

Async/Await, Promise.All() & LoadAsync

If you want to load multiple assets at the same time, but wait for them all to be fully downloaded before continuing, then you also have the option to wrap multiple loadAsync() methods in a Promise.all(). You will also need to wrap this in an Async/Await

async function loadCar() {
    const loader = new GLTFLoader()
    const [...model] = await Promise.all([
        loader.loadAsync('chassis.glb'),
        loader.loadAsync('wheel.glb'),
    ])
    const chassis = model[0].scene
    const wheels = [
        model[1].scene,
        model[1].scene.clone(),
        model[1].scene.clone(),
        model[1].scene.clone(),
    ]
    wheels[0].position.set(-1, 0, 1)
    wheels[1].position.set(1, 0, 1)
    wheels[2].position.set(-1, 0, -1)
    wheels[3].position.set(1, 0, -1)
    chassis.add(...wheels)
    scene.add(chassis)
}

loadCar()

In the above code example, the chassis and wheel objects will be instantiated only after all the loadAsync calls within the Promise.all() where completed.

THREE.Loader

Async/Await

Promise.all()

loadAsync

First Car Shooter

Straight Car