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);
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
}
});
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!