Waiting for 3D Models to Load

I need help with implementing a method to wait for 3D models to be completely loaded.
For now this is all I have, these two methods in a class called World:

  _loadModelAndWait(gltfModelName) {
    return new Promise((resolve, reject) => {
      const loader = new GLTFLoader();
      let model;
      loader.load(`../models/${gltfModelName}.gltf`, (gltf) => {
        model = gltf.scene.children[0];
        this.Objects.add(model);
        resolve(model);
      }, undefined, (error) => {
        console.error(error);
        reject(error);
      });
    });
  }

  loadModel(gltfModelName) {
    this._loadModelAndWait(gltfModelName)
        .then((model) => {
          if (model) {
            console.log('Model loaded successfully!');
            model.scale.set(0.1,0.1,0.1); // I want to execute this outside the class
          }
        })
        .catch((error) => {
          console.error(error);
        });
  }

And it works, but I want to apply transformations to the objects outside the class, just like this:

const modelNames = ["3DModel1", "3DModel2"];
modelNames.forEach((model) => {myWorld.loadModel(model)});
console.log(myWorld.Objects.children[1]); //This should print the Object3D and not undefined
if(myWorld.Objects.children[1]) myWorld.Objects.children[1].scale.set(0.1, 0.1, 0.1);

Is there a clean way to handle this problem?

There’s like at least 100000 ways - but depends on the circumstances. If you just want to start applying transformations to the model, and not care if it’s loaded or not - just substitute it with a group:

const loadModel = (url) => {
  const group = new Three.Group();

  new Three.GLTFLoader().load(url, gltf => {
    group.add(gltf.scene);
  });

  return group;
};

const dolphinModel = loadModel('https://pbs.twimg.com/media/FFOjIvAUcAE9A5j.jpg');

// NOTE You can transform the model right away, let it load in the background in the meantime
scene.add(dolphinModel);

dolphinModel.position.set(1.0, 1.0, 1.0);

This is a very simple and gentle solution for small projects - just remember you cannot access any contents of the actual model - so with the group approach, you can’t change materials, apply animations etc. until you’re sure the model is loaded.


If you’re looking for a production-ready solution for giant model-heavy projects - consider creating a sort of asynchronous preloading wrapper for models. Such wrapper should only start executing logic related to the model after the file is loaded and ready - that’ll also help you separate code responsibility nicely in the long term, ex.:

const gltfLoader = new Three.GLTFLoader();

class AsyncModelComponent {
  loop = null;

  constructor(props) {
    this.init(props);
  }

  async init({ modelUrl, onFrame }) {
    if (modelUrl) {
      await this.loadModel(modelUrl);
    }

    this.run({ onFrame });
  }

  loadModel({ modelUrl }) {
    return new Promise(resolve => {
      gltfLoader.load(modelUrl, gltf => {
        this.model = gltf.scene;

        scene.add(this.model);

        resolve();
      });
    });
  }

  run({ onFrame }) {
    if (!onFrame) {
      return;
    }

    const loopFn = () => {
      this.loop = requestAnimationFrame(() => loopFn());

      onFrame({ model: this.model });
    };

    this.loop = loopFn();
  }

  dispose() {
    // NOTE Dispose model and assets

    if (this.loop) {
      cancelAnimationFrame(this.loop);
    }
  }
}

// NOTE Then separately implement the logic for each model
new AsyncModelComponent({
  modelUrl: 'dolphin.glb',
  onFrame: (model) => {
    // NOTE You can be 100% sure the model is ready here
  }
});

new AsyncModelComponent({
  modelUrl: 'dolphin2.glb',
  onFrame: (model) => {
    // NOTE Separate instance with independent logic
  }
});
1 Like

Wow, that’s very helpful, thank you.
I don’t understand fully what the AsycModelComponent class is doing, since many of the things used are pretty new to me, but I get the general idea. I guess callback functions are problaly the solution I’m looking for. I’ll try my best to find an implementation that works best for my project!