DRACOLoader: Simplify decoder source loading. (#315)

* DRACOLoader: Simplify decoder source loading.
This commit is contained in:
Don McCurdy 2018-01-10 10:05:19 -08:00 committed by Ondrej Stava
parent 64e9192d43
commit 3ea3cd8b20
4 changed files with 150 additions and 160 deletions

View File

@ -14,26 +14,16 @@
// //
'use strict'; 'use strict';
// |dracoPath| sets the path for the Draco decoder source files. The default /**
// path is "./". If |dracoDecoderType|.type is set to "js", then DRACOLoader * @param {THREE.LoadingManager} manager
// will load the Draco JavaScript decoder. */
THREE.DRACOLoader = function(dracoPath, dracoDecoderType, manager) { THREE.DRACOLoader = function(manager) {
this.timeLoaded = 0; this.timeLoaded = 0;
this.manager = (manager !== undefined) ? manager : this.manager = manager || THREE.DefaultLoadingManager;
THREE.DefaultLoadingManager;
this.materials = null; this.materials = null;
this.verbosity = 0; this.verbosity = 0;
this.attributeOptions = {}; this.attributeOptions = {};
if (dracoDecoderType !== undefined) {
THREE.DRACOLoader.dracoDecoderType = dracoDecoderType;
}
this.drawMode = THREE.TrianglesDrawMode; this.drawMode = THREE.TrianglesDrawMode;
this.dracoSrcPath = (dracoPath !== undefined) ? dracoPath : './';
// If draco_decoder.js or wasm code is already loaded/included, then do
// not dynamically load the decoder.
if (typeof DracoDecoderModule === 'undefined') {
THREE.DRACOLoader.loadDracoDecoder(this);
}
// User defined unique id for attributes. // User defined unique id for attributes.
this.attributeUniqueIdMap = {}; this.attributeUniqueIdMap = {};
// Native Draco attribute type to Three.JS attribute type. // Native Draco attribute type to Three.JS attribute type.
@ -45,8 +35,6 @@ THREE.DRACOLoader = function(dracoPath, dracoDecoderType, manager) {
}; };
}; };
THREE.DRACOLoader.dracoDecoderType = {};
THREE.DRACOLoader.prototype = { THREE.DRACOLoader.prototype = {
constructor: THREE.DRACOLoader, constructor: THREE.DRACOLoader,
@ -116,12 +104,11 @@ THREE.DRACOLoader.prototype = {
*/ */
decodeDracoFile: function(rawBuffer, callback, attributeUniqueIdMap) { decodeDracoFile: function(rawBuffer, callback, attributeUniqueIdMap) {
var scope = this; var scope = this;
this.attributeUniqueIdMap = (attributeUniqueIdMap !== undefined) ? this.attributeUniqueIdMap = attributeUniqueIdMap || {};
attributeUniqueIdMap : {}; THREE.DRACOLoader.getDecoderModule()
THREE.DRACOLoader.getDecoderModule( .then( function ( module ) {
function(dracoDecoder) { scope.decodeDracoFileInternal( rawBuffer, module.decoder, callback );
scope.decodeDracoFileInternal(rawBuffer, dracoDecoder, callback); });
});
}, },
decodeDracoFileInternal: function(rawBuffer, dracoDecoder, callback) { decodeDracoFileInternal: function(rawBuffer, dracoDecoder, callback) {
@ -333,9 +320,9 @@ THREE.DRACOLoader.prototype = {
}, },
isVersionSupported: function(version, callback) { isVersionSupported: function(version, callback) {
THREE.DRACOLoader.getDecoderModule( THREE.DRACOLoader.getDecoderModule()
function(decoder) { .then( function ( module ) {
callback(decoder.isVersionSupported(version)); callback( module.decoder.isVersionSupported( version ) );
}); });
}, },
@ -346,131 +333,117 @@ THREE.DRACOLoader.prototype = {
} }
}; };
// This function loads a JavaScript file and adds it to the page. "path" THREE.DRACOLoader.decoderPath = './';
// is the path to the JavaScript file. "onLoadFunc" is the function to be THREE.DRACOLoader.decoderConfig = {};
// called when the JavaScript file has been loaded. THREE.DRACOLoader.decoderModulePromise = null;
THREE.DRACOLoader.loadJavaScriptFile = function(path, onLoadFunc,
dracoDecoder) {
var previous_decoder_script = document.getElementById("decoder_script");
if (previous_decoder_script !== null) {
return;
}
var head = document.getElementsByTagName('head')[0];
var element = document.createElement('script');
element.id = "decoder_script";
element.type = 'text/javascript';
element.src = path;
if (onLoadFunc !== null) {
element.onload = onLoadFunc(dracoDecoder);
} else {
element.onload = function(dracoDecoder) {
THREE.DRACOLoader.timeLoaded = performance.now();
};
}
head.appendChild(element);
}
THREE.DRACOLoader.loadWebAssemblyDecoder = function(dracoDecoder) {
THREE.DRACOLoader.dracoDecoderType['wasmBinaryFile'] =
dracoDecoder.dracoSrcPath + 'draco_decoder.wasm';
var xhr = new XMLHttpRequest();
xhr.open('GET', dracoDecoder.dracoSrcPath + 'draco_decoder.wasm', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
// draco_wasm_wrapper.js must be loaded before DracoDecoderModule is
// created. The object passed into DracoDecoderModule() must contain a
// property with the name of wasmBinary and the value must be an
// ArrayBuffer containing the contents of the .wasm file.
THREE.DRACOLoader.dracoDecoderType['wasmBinary'] = xhr.response;
THREE.DRACOLoader.timeLoaded = performance.now();
};
xhr.send(null)
}
// This function will test if the browser has support for WebAssembly. If
// it does it will download the WebAssembly Draco decoder, if not it will
// download the asmjs Draco decoder.
THREE.DRACOLoader.loadDracoDecoder = function(dracoDecoder) {
if (typeof WebAssembly !== 'object' ||
THREE.DRACOLoader.dracoDecoderType.type === 'js') {
// No WebAssembly support
THREE.DRACOLoader.loadJavaScriptFile(dracoDecoder.dracoSrcPath +
'draco_decoder.js', null, dracoDecoder);
} else {
THREE.DRACOLoader.loadJavaScriptFile(dracoDecoder.dracoSrcPath +
'draco_wasm_wrapper.js',
function (dracoDecoder) {
THREE.DRACOLoader.loadWebAssemblyDecoder(dracoDecoder);
}, dracoDecoder);
}
}
THREE.DRACOLoader.decoderCreationCalled = false;
/** /**
* Creates and returns a singleton instance of the DracoDecoderModule. * Sets the base path for decoder source files.
* The module loading is done asynchronously for WebAssembly. Initialized module * @param {string} path
* can be accessed through the callback function
* |onDracoDecoderModuleLoadedCallback|.
*/ */
THREE.DRACOLoader.getDecoderModule = (function() { THREE.DRACOLoader.setDecoderPath = function ( path ) {
return function(onDracoDecoderModuleLoadedCallback) { THREE.DRACOLoader.decoderPath = path;
if (typeof THREE.DRACOLoader.decoderModule !== 'undefined') { };
// Module already initialized.
if (typeof onDracoDecoderModuleLoadedCallback !== 'undefined') {
onDracoDecoderModuleLoadedCallback(THREE.DRACOLoader.decoderModule);
}
} else {
if (typeof DracoDecoderModule === 'undefined') {
// Wait until the Draco decoder is loaded before starting the error
// timer.
if (THREE.DRACOLoader.timeLoaded > 0) {
var waitMs = performance.now() - THREE.DRACOLoader.timeLoaded;
// After loading the Draco JavaScript decoder file, there is still /**
// some time before the DracoDecoderModule is defined. So start a * Sets decoder configuration and releases singleton decoder module. Module
// loop to check when the DracoDecoderModule gets defined. If the * will be recreated with the next decoding call.
// time is hit throw an error. * @param {Object} config
if (waitMs > 5000) { */
throw new Error( THREE.DRACOLoader.setDecoderConfig = function ( config ) {
'THREE.DRACOLoader: DracoDecoderModule not found.'); var wasmBinary = THREE.DRACOLoader.decoderConfig.wasmBinary;
} THREE.DRACOLoader.decoderConfig = config || {};
} THREE.DRACOLoader.releaseDecoderModule();
} else {
if (!THREE.DRACOLoader.decoderCreationCalled) {
THREE.DRACOLoader.decoderCreationCalled = true;
THREE.DRACOLoader.dracoDecoderType['onModuleLoaded'] =
function(module) {
THREE.DRACOLoader.decoderModule = module;
};
DracoDecoderModule(THREE.DRACOLoader.dracoDecoderType);
}
}
// Either the DracoDecoderModule has not been defined or the decoder // Reuse WASM binary.
// has not been created yet. Call getDecoderModule() again. if ( wasmBinary ) THREE.DRACOLoader.decoderConfig.wasmBinary = wasmBinary;
setTimeout(function() { };
THREE.DRACOLoader.getDecoderModule(
onDracoDecoderModuleLoadedCallback);
}, 10);
}
};
})(); /**
* Releases the singleton DracoDecoderModule instance. Module will be recreated
* with the next decoding call.
*/
THREE.DRACOLoader.releaseDecoderModule = function () {
THREE.DRACOLoader.decoderModulePromise = null;
};
// Releases the DracoDecoderModule instance associated with the draco loader. /**
// The module will be automatically re-created the next time a new geometry is * Gets WebAssembly or asm.js singleton instance of DracoDecoderModule
// loaded by THREE.DRACOLoader. * after testing for browser support. Returns Promise that resolves when
THREE.DRACOLoader.releaseDecoderModule = function() { * module is available.
THREE.DRACOLoader.decoderModule = undefined; * @return {Promise<{decoder: DracoDecoderModule}>}
THREE.DRACOLoader.decoderCreationCalled = false; */
if (THREE.DRACOLoader.dracoDecoderType !== undefined && THREE.DRACOLoader.getDecoderModule = function () {
THREE.DRACOLoader.dracoDecoderType.wasmBinary !== undefined) { var scope = this;
// For WASM build we need to preserve the wasmBinary for future use. var path = THREE.DRACOLoader.decoderPath;
var wasmBinary = THREE.DRACOLoader.dracoDecoderType.wasmBinary; var config = THREE.DRACOLoader.decoderConfig;
THREE.DRACOLoader.dracoDecoderType = {}; var promise = THREE.DRACOLoader.decoderModulePromise;
THREE.DRACOLoader.dracoDecoderType.wasmBinary = wasmBinary;
if ( promise ) return promise;
// Load source files.
if ( typeof DracoDecoderModule !== 'undefined' ) {
// Loaded externally.
promise = Promise.resolve();
} else if ( typeof WebAssembly !== 'object' || config.type === 'js' ) {
// Load with asm.js.
promise = THREE.DRACOLoader._loadScript( path + 'draco_decoder.js' );
} else { } else {
THREE.DRACOLoader.dracoDecoderType = {}; // Load with WebAssembly.
config.wasmBinaryFile = path + 'draco_decoder.wasm';
promise = THREE.DRACOLoader._loadScript( path + 'draco_wasm_wrapper.js' )
.then( function () {
return THREE.DRACOLoader._loadArrayBuffer( config.wasmBinaryFile );
} )
.then( function ( wasmBinary ) {
config.wasmBinary = wasmBinary;
} );
} }
}
// Wait for source files, then create and return a decoder.
promise = promise.then( function () {
return new Promise( function ( resolve ) {
config.onModuleLoaded = function ( decoder ) {
scope.timeLoaded = performance.now();
// Module is Promise-like. Wrap before resolving to avoid loop.
resolve( { decoder: decoder } );
};
DracoDecoderModule( config );
} );
} );
THREE.DRACOLoader.decoderModulePromise = promise;
return promise;
};
/**
* @param {string} src
* @return {Promise}
*/
THREE.DRACOLoader._loadScript = function ( src ) {
var prevScript = document.getElementById( 'decoder_script' );
if ( prevScript !== null ) {
prevScript.parentNode.removeChild( prevScript );
}
var head = document.getElementsByTagName( 'head' )[ 0 ];
var script = document.createElement( 'script' );
script.id = 'decoder_script';
script.type = 'text/javascript';
script.src = src;
return new Promise( function ( resolve ) {
script.onload = resolve;
head.appendChild( script );
});
};
/**
* @param {string} src
* @return {Promise}
*/
THREE.DRACOLoader._loadArrayBuffer = function ( src ) {
var loader = new THREE.FileLoader();
loader.setResponseType( 'arraybuffer' );
return new Promise( function( resolve, reject ) {
loader.load( src, resolve, undefined, reject );
});
};

View File

@ -38,7 +38,23 @@ Include Javascript decoder:
Create DracoLoader by setting the decoder type: Create DracoLoader by setting the decoder type:
~~~~~ js ~~~~~ js
var dracoDecoderType = {}; // (Optional) Change decoder source directory (defaults to './').
dracoDecoderType.type = 'js'; THREE.DRACOLoader.setDecoderPath('./path/to/decoder/');
var dracoLoader = new THREE.DRACOLoader('path_to_decoder', dracoDecoderType);
// (Optional) Use JS decoder (defaults to WebAssembly if supported).
THREE.DRACOLoader.setDecoderConfig({type: 'js'});
// (Optional) Pre-fetch decoder source files (defaults to load on demand).
THREE.DRACOLoader.getDecoderModule();
var dracoLoader = new THREE.DRACOLoader();
dracoLoader.load( 'model.drc', function ( geometry ) {
scene.add( new THREE.Mesh( geometry ) );
// (Optional) Release the cached decoder module.
THREE.DRACOLoader.releaseDecoderModule();
} );
~~~~~ ~~~~~

View File

@ -37,17 +37,17 @@
<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - <a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> -
<a href="https://github.com/google/draco" target="_blank" rel="noopener">DRACO</a> loader <a href="https://github.com/google/draco" target="_blank" rel="noopener">DRACO</a> loader
</div> </div>
<script src="https://cdn.rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script> <script src="https://cdn.rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script>
<script src="DRACOLoader.js"></script> <script src="DRACOLoader.js"></script>
<script src="geometry_helper.js"></script> <script src="geometry_helper.js"></script>
<script> <script>
var camera, scene, renderer; var camera, scene, renderer;
// Global Draco decoder type. // Configure decoder and create loader.
var dracoDecoderType = {}; THREE.DRACOLoader.setDecoderPath( '../' );
dracoDecoderType.type = 'js'; THREE.DRACOLoader.setDecoderConfig( { type: 'js' } );
var dracoLoader = new THREE.DRACOLoader('../', dracoDecoderType); var dracoLoader = new THREE.DRACOLoader();
init(); init();
animate(); animate();
@ -92,6 +92,9 @@
mesh.receiveShadow = true; mesh.receiveShadow = true;
scene.add( mesh ); scene.add( mesh );
// Release the cached decoder module.
THREE.DRACOLoader.releaseDecoderModule();
} ); } );
// renderer // renderer

View File

@ -33,14 +33,12 @@
<script> <script>
'use strict'; 'use strict';
// Global Draco decoder type. // Configure decoder and create loader.
var dracoLoader; THREE.DRACOLoader.setDecoderPath( '../' );
var dracoLoader = new THREE.DRACOLoader();
createDracoDecoder(); // (Optional) Pre-fetch decoder source files.
THREE.DRACOLoader.getDecoderModule();
function createDracoDecoder() {
dracoLoader = new THREE.DRACOLoader('../');
}
var camera, cameraTarget, scene, renderer; var camera, cameraTarget, scene, renderer;