简体   繁体   中英

How do I make my gltf loader synchronous in three.js?

Here's my code, I'm trying to make the loadCone() synchronous with await/async but it isn't working.

import * as THREE from "three";

import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

import conePath from "../../static/cone.glb";
import coneBaked from "../../static/coneBake.png";

import "regenerator-runtime/runtime";

export default class Cone {
    constructor() {
        this.cone;
        const textureLoader = new THREE.TextureLoader();

        const bakedTexture = textureLoader.load(coneBaked);
        const bakedMaterial = new THREE.MeshBasicMaterial({
            map: bakedTexture,
        });

        this.loadCone();
    }

    async loadCone() {
        // GLTF loader
        const gltfLoader = new GLTFLoader();
        const cone = await gltfLoader.load(
            conePath,
            (gltf) => {
                console.log("success");
                console.log(gltf.scene);
                this.cone = gltf.scene;
            },
            (progress) => {
                // console.log("progress");
                // console.log(progress);
            },
            (error) => {
                console.log("error");
                console.log(error);
            }
        );
    }

    getCone() {
        return this.cone;
    }
}

In another file I have this:

const cone = new Cone();
this.scene.add(cone.getCone());

However, because getCone() is synchronous, it executes before my loadCone() is done loading the cone, so I get undefined back instead of the cone.

How do I make my function asynch loadCone() be synchronous? I tried *.loadAsync()` but it didn't change anything.

Thanks in advance. I am getting no errors other than the fact it says "undefined is not instance of THREE.Object.3d" which I expected it to say.

I can't quite understand what you are doing.

I use this successfully:

  // Load a glTF resource
  async loadGltf(url) {
    // console.log('===== start loadGltf async')
    let urlString = url.toString()  // load doesn't like the url structure; it requires a string and then builds it up again
    let gltf = await gltfLoader.loadAsync(urlString)
    // console.log('========== end loadGltf')
    return gltf
  }

From the result, access result.scene

I don't know three.js, but new needs to synchronously return an initialized object, so it cannot be (or invoke something else that is) asynchronous.

// ...
constructor() {
    this.cone;
    const textureLoader = new THREE.TextureLoader();

    const bakedTexture = textureLoader.load(coneBaked);
    const bakedMaterial = new THREE.MeshBasicMaterial({
        map: bakedTexture,
    });

    // don't call an async method here
    // this.loadCone();
}
//...

On the caller side...

import Cone from 'path to js that defines the class'

// inside an async function
async function doConeStuff() {
  const cone = new Cone();
  await cone.loadCone();
  this.scene.add(cone.getCone());
}

// or, at the top level
const cone = new Cone();
cone.loadCone().then(() => {
  this.scene.add(cone.getCone())
});

Noticing that loadCone is mixing up promises and callbacks, we can either promisify the version you're using or take @Leon.Z's suggestion about the promise-returning version. If that one works (again, I don't know three.js), then the cone class can be simplified like this...

export default class Cone {
    constructor() {
        const textureLoader = new THREE.TextureLoader();

        const bakedTexture = textureLoader.load(coneBaked);
        const bakedMaterial = new THREE.MeshBasicMaterial({
            map: bakedTexture,
        });
        this.cone = null;
    }

    async loadCone() {
        // GLTF loader
        if (this.cone) Promise.resolve(this.cone);
        const giltf = await gltfLoader.loadAsync( 'model.gltf' ),
        this.cone = gilt.scene;
        return this.cone;
    }
    
    getCone() { return this.cone; }
}

Finally, if for some reason, @Leon.Z is incorrect about the promise method, here's how you'd promisify the loader...

async loadCone() {
  const gltfLoader = new GLTFLoader();
  return new Promise((resolve, reject) => {
    gltfLoader.load(
      conePath,
      gltf => resolve(gilt.scene),
      error => reject(error)
    );
  });
}

Based on the documentation, there is a loadAsync method from threejs loaders that you can use as following.

await gltfLoader.loadAsync( 'model.gltf' ),

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM