Home > Software design >  async issues regarding model import using promises and three.js
async issues regarding model import using promises and three.js

Time:07-17

Im having a problem resolving a promise.

on load my script has a promise that i wish to resolve when a function that contains 3D file imports finishes importing.

the problem im facing is how to make the promises resolve() execute when a model has loaded.

is there a way to get data or any sort of signal from the browser when a model has finished loading?

the following is the promise. i wish to execute res() when generateContent() has finished importing objects.

const myGeneralAsyncPromise = new Promise((res, rej) => {
generateContent()
if(some condition) res()
// else rej()
})


myGeneralAsyncPromise.then(allIsReady, notReadyYet)

the following envokes a class that creates an object within generateContent().

    var arrow3 = body(scene, world, 'static', 'arrow', { hx: 0.2, hy: 0.2, hz: 0.2 }, {  x: 34.5, y: 3.35, z: 6 }, { x: 0, y:11, z: 0});
bodys.push(arrow3);

var phone = body(scene, world, 'static', 'phone', { hx: 1.3, hy: 1.3, hz: 1.3 }, {  x: 35.35, y:1.8, z: 6.5 }, { x: 0, y:0, z: 0});
bodys.push(phone);

var pencil = body(scene, world, 'static', 'pencil', { hx: 2, hy: 2, hz: 2 }, {  x: 35.5, y:1.8, z: 14 }, { x: 0, y:11, z: 0});
bodys.push(pencil);

the following is the actual import of each object.

function body(scene, world, bodyType, colliderType, dimension, translation, rotation) {
new GLTFLoader_js_1.GLTFLoader().load(`src/models/${colliderType}.glb`, function (gltf) {
            var model = gltf.scene;
            collider = gltf.scene
            model.scale.x = dimension.hx
            model.scale.y = dimension.hy
            model.scale.z = dimension.hz
            model.traverse(function (object) {
                if (object.isMesh)
                    object.castShadow = true;
            });
            model.position.x = translation.x
            model.position.y = translation.y
            model.position.z = translation.z

            model.rotation.x = rotation.x
            model.rotation.y = rotation.y
            model.rotation.z = rotation.z
            scene.add(model);

            var gltfAnimations = gltf.animations;
            var mixer = new THREE.AnimationMixer(model);
            var animationsMap = new Map();
            gltfAnimations.filter(function (a) { return a.name != 'TPose'; }).forEach(function (a) {
                animationsMap.set(a.name, mixer.clipAction(a));
            });
          });
    }

for the record - generateContent() has more processes that take time besides the import - but the import is by far the longest.

bottom line: in my main promise im missing a condition that will set res() when the models has finished loading.

CodePudding user response:

You should make a promise that resolves when the load callback is called. In other words, you should promisify .load():

const promiseGLTFLoad = url =>
    new Promise(resolve => new GLTFLoader_js_1.GLTFLoader().load(url, resolve));

With this function you can now build the asynchronous processing:

async function body(scene, world, bodyType, colliderType, dimension, translation, rotation) {
    const gltf = await promiseGLTFLoad(`src/models/${colliderType}.glb`);
    var model = gltf.scene;
    collider = gltf.scene
    model.scale.x = dimension.hx
    /* ... etc ... */
    scene.add(model);

    var gltfAnimations = gltf.animations;
    var mixer = new THREE.AnimationMixer(model);
    var animationsMap = new Map();
    gltfAnimations.filter(a => a.name != 'TPose')
                  .forEach(a => animationsMap.set(a.name, mixer.clipAction(a)));
    // Need to return the result!!
    return animationsMap;
}

Now to where you call body --- I suppose in generateContent: that function should collect the promises you get and then await them:

async function generateContent() {
    // ...
    var arrow3Promise = body(scene, world, 'static', 'arrow', { hx: 0.2, hy: 0.2, hz: 0.2 }, {  x: 34.5, y: 3.35, z: 6 }, { x: 0, y:11, z: 0});
    var phonePromise = body(scene, world, 'static', 'phone', { hx: 1.3, hy: 1.3, hz: 1.3 }, {  x: 35.35, y:1.8, z: 6.5 }, { x: 0, y:0, z: 0});
    var pencilPromise = body(scene, world, 'static', 'pencil', { hx: 2, hy: 2, hz: 2 }, {  x: 35.5, y:1.8, z: 14 }, { x: 0, y:11, z: 0});

    // Wait for all promises to resolve
    var bodys = await Promise.all(arrow3Promise, phonePromise, pencilPromise);
    // ... etc
    return bodys; // Maybe the caller needs them??
}

Finally, the main driver code would do this:

async function main() {
    const bodys = await generateContent();
    if (something is not right) throw new Error("my error");
    // ...
    return bodys; // Maybe the caller needs them??
}

main().then(allIsReady, failure);

Maybe you don't really need that many layers. You could check if something is not right at the end of generateContent and raise an error there (which translates to a rejected promise). And then it just becomes:

generateContent().then(allIsReady, failure);

Note that there is no "notReadyYet". A promise either resolves or rejects. When it rejects, you should consider it a failure. There is no "not yet" sentiment here.

  • Related