mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.81k stars 35.38k forks source link

No way to set onLoad callbacks for assets in ColladaLoader #16318

Closed telramund closed 5 years ago

telramund commented 5 years ago
Description of the problem

Currently there is no official way to specify an onLoad callback for assets associated with a Collada model. Many models cannot be rendered properly until assets (like textures) are loaded. It is not a problem for applications that do continuous rendering on a timer. But in my use case (a CAD application) it is, as I only render the scene when it changes.

I ended up with a hack temporarily globally overriding THREE.TextureLoader.prototype.load(). But it would make things a lot easier if TextureLoader (or ImageLoader) instances used internally by ColladaLoader were exposed as properties rather that being sealed inside lambdas, so callbacks could have been set up normally on them.

Or, ideally, the ColladaLoader onLoad callback was called when all assets are loaded and the model is ready to use. Here's my workaround to illustrate the issue:

// Extend the Collada loader to wait for all textures to load before running
// the onLoad callback.

class ColladaLoader extends THREE.ColladaLoader {

  onTextureLoadOrError(url, onLoad, isOK) {
    // Do steps required on both success or failure of loading a texture.

    DEBUG(`Texture "${url}"`, isOK ? "loaded" : "failed to load");
    delete this.pendingTexUrls[url];
    this.tryCompletingModeLoad(onLoad);
  }

  tryCompletingModeLoad(onLoad) {
    // If there're no loads pending, complete model loading and call "onLoad".

    if (Object.keys(this.pendingTexUrls).length)
      return;
    THREE.TextureLoader.prototype.load = this.origTexLoad;
    onLoad(this.colladaMesh);
  }

  load(url, onLoad, onProgress, onError) {
    // Overriden.

    var loader = new THREE.FileLoader(this.manager);
    loader.setPath(this.path);
    var path = this.path ? this.path : THREE.LoaderUtils.extractUrlBase(url);

    this.pendingTexUrls = {};
    this.origTexLoad = THREE.TextureLoader.prototype.load;

    var that = this;
    loader.load(url, function (text) {

      THREE.TextureLoader.prototype.load = (texUrl, onTexLoad, onTexProgress,
                                            onTexError) => {
        let fullUrl = path + texUrl;
        that.pendingTexUrls[fullUrl] = true;

        function onLoadWrapper(texture) {
          let retVal = onTexLoad ? onTexLoad(texture) : undefined;
          that.onTextureLoadOrError(fullUrl, onLoad, true);
          return retVal;
        }

        function onErrorWrapper(texture) {
          let retVal = onTexError ? onTexError(texture) : undefined;
          that.onTextureLoadOrError(fullUrl, onError false);
          return retVal;
        };

        DEBUG(`Loading texture "${fullUrl}", path: "${path}"`);
        return that.origTexLoad.call(this, fullUrl, onLoadWrapper, onTexProgress,
                                     onErrorWrapper);
      };

      that.colladaMesh = that.parse(text, path);
      that.tryCompletingModeLoad(onLoad);
    }, onProgress, onError);
  }
}
Mugen87 commented 5 years ago

The idea is to use THREE.LoadingManager for this use case like in the following example:

https://threejs.org/examples/webgl_loader_collada

Or, ideally, the ColladaLoader onLoad callback was called when all assets are loaded and the model is ready to use.

The current situation in the library is not consistent since other loaders like GLTFLoader or ObjectLoader behave exactly like that. I know this issue was discussed before but no change was made since the usage of THREE.LoadingManager is an easy workaround.

telramund commented 5 years ago

Thanks, I'll try that and post an update soon.

telramund commented 5 years ago

Using a dedicated instance of THREE.LoadingManager works fine, thanks.