2
0
Эх сурвалжийг харах

Merge pull request #15249 from donmccurdy/feat-gltfdracoloader

DRACOLoader: Web Worker support
Mr.doob 6 жил өмнө
parent
commit
fd500fdfbc

+ 32 - 27
docs/examples/en/loaders/DRACOLoader.html

@@ -34,10 +34,7 @@
 		var loader = new THREE.DRACOLoader();
 		var loader = new THREE.DRACOLoader();
 
 
 		// Specify path to a folder containing WASM/JS decoding libraries.
 		// Specify path to a folder containing WASM/JS decoding libraries.
-		THREE.DRACOLoader.setDecoderPath( '/examples/js/libs/draco' );
-
-		// Optional: Pre-fetch Draco WASM/JS module.
-		THREE.DRACOLoader.getDecoderModule();
+		loader.setDecoderPath( '/examples/js/libs/draco' );
 
 
 		// Load a Draco geometry
 		// Load a Draco geometry
 		loader.load(
 		loader.load(
@@ -90,52 +87,60 @@
 		Creates a new [name].
 		Creates a new [name].
 		</p>
 		</p>
 
 
-		<h2>Static Methods</h2>
+		<h2>Methods</h2>
 
 
-		<h3>[method:null setDecoderPath]( [param:String value] )</h3>
+		<h3>[method:null load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
 		<p>
 		<p>
-		[page:String value] — Path to folder containing the JS and WASM decoder libraries.
+		[page:String url] — A string containing the path/URL of the <em>.drc</em> file.<br />
+		[page:Function onLoad] — A function to be called after the loading is successfully completed.<br />
+		[page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, that contains .[page:Integer total] and .[page:Integer loaded] bytes.<br />
+		[page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives error as an argument.<br />
+		</p>
+		<p>
+		Begin loading from url and call the <em>onLoad</em> function with the decompressed geometry.
 		</p>
 		</p>
 
 
-		<h3>[method:null setDecoderConfig]( [param:Object config] )</h3>
+		<h3>[method:this setPath]( [param:String path] )</h3>
 		<p>
 		<p>
-			[page:String config.type] - (Optional) <em>"js"</em> or <em>"wasm"</em>.<br />
+		[page:String path] — Base path.
 		</p>
 		</p>
 		<p>
 		<p>
-		Provides configuration for the decoder libraries. Configuration cannot be changed
-		after loading the decoders.
+		Set the base path for the <em>.drc</em> file.
 		</p>
 		</p>
 
 
-		<h3>[method:Promise getDecoderModule]()</h3>
+		<h3>[method:this setCrossOrigin]( [param:String value] )</h3>
 		<p>
 		<p>
-		Requests the decoder libraries, if not already loaded.
+		[page:String value] — The crossOrigin string to implement CORS for loading the url from a different domain that allows CORS.
 		</p>
 		</p>
 
 
-		<h3>[method:null releaseDecoderModule]()</h3>
+		<h3>[method:this setDecoderPath]( [param:String value] )</h3>
 		<p>
 		<p>
-		Disposes of the decoder library and deallocates memory. The decoder
-		[link:https://github.com/google/draco/issues/349 cannot be reloaded afterward].
+		[page:String value] — Path to folder containing the JS and WASM decoder libraries.
 		</p>
 		</p>
 
 
-		<h2>Methods</h2>
-
-		<h3>[method:null load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
+		<h3>[method:this setDecoderConfig]( [param:Object config] )</h3>
 		<p>
 		<p>
-		[page:String url] — A string containing the path/URL of the <em>.drc</em> file.<br />
-		[page:Function onLoad] — A function to be called after the loading is successfully completed.<br />
-		[page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, that contains .[page:Integer total] and .[page:Integer loaded] bytes.<br />
-		[page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives error as an argument.<br />
+			[page:String config.type] - (Optional) <em>"js"</em> or <em>"wasm"</em>.<br />
 		</p>
 		</p>
 		<p>
 		<p>
-		Begin loading from url and call the <em>onLoad</em> function with the decompressed geometry.
+		Provides configuration for the decoder libraries. Configuration cannot be changed
+		after decoding begins.
 		</p>
 		</p>
 
 
-		<h3>[method:DRACOLoader setPath]( [param:String path] )</h3>
+		<h3>[method:this setWorkerLimit]( [param:Number workerLimit] )</h3>
 		<p>
 		<p>
-		[page:String path] — Base path.
+			[page:Number workerLimit] - Maximum number of workers to be allocated. Default is 4.<br />
 		</p>
 		</p>
 		<p>
 		<p>
-		Set the base path for the <em>.drc</em> file.
+		Sets the maximum number of [link:https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers Web Workers]
+		to be used during decoding. A lower limit may be preferable if workers are also for other tasks
+		in the application.
+		</p>
+
+		<h3>[method:this dispose]()</h3>
+		<p>
+		Disposes of the decoder resources and deallocates memory. The decoder
+		[link:https://github.com/google/draco/issues/349 cannot be reloaded afterward].
 		</p>
 		</p>
 
 
 		<h2>Source</h2>
 		<h2>Source</h2>

+ 3 - 5
docs/examples/en/loaders/GLTFLoader.html

@@ -56,11 +56,9 @@
 		var loader = new THREE.GLTFLoader();
 		var loader = new THREE.GLTFLoader();
 
 
 		// Optional: Provide a DRACOLoader instance to decode compressed mesh data
 		// Optional: Provide a DRACOLoader instance to decode compressed mesh data
-		THREE.DRACOLoader.setDecoderPath( '/examples/js/libs/draco' );
-		loader.setDRACOLoader( new THREE.DRACOLoader() );
-
-		// Optional: Pre-fetch Draco WASM/JS module, to save time while parsing.
-		THREE.DRACOLoader.getDecoderModule();
+		var dracoLoader = new THREE.DRACOLoader();
+		dracoLoader.setDecoderPath( '/examples/js/libs/draco' );
+		loader.setDRACOLoader( dracoLoader );
 
 
 		// Load a glTF resource
 		// Load a glTF resource
 		loader.load(
 		loader.load(

+ 32 - 27
docs/examples/zh/loaders/DRACOLoader.html

@@ -34,10 +34,7 @@
 		var loader = new THREE.DRACOLoader();
 		var loader = new THREE.DRACOLoader();
 
 
 		// Specify path to a folder containing WASM/JS decoding libraries.
 		// Specify path to a folder containing WASM/JS decoding libraries.
-		THREE.DRACOLoader.setDecoderPath( '/examples/js/libs/draco' );
-
-		// Optional: Pre-fetch Draco WASM/JS module.
-		THREE.DRACOLoader.getDecoderModule();
+		loader.setDecoderPath( '/examples/js/libs/draco' );
 
 
 		// Load a Draco geometry
 		// Load a Draco geometry
 		loader.load(
 		loader.load(
@@ -90,52 +87,60 @@
 		Creates a new [name].
 		Creates a new [name].
 		</p>
 		</p>
 
 
-		<h2>Static Methods</h2>
+		<h2>Methods</h2>
 
 
-		<h3>[method:null setDecoderPath]( [param:String value] )</h3>
+		<h3>[method:null load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
 		<p>
 		<p>
-		[page:String value] — Path to folder containing the JS and WASM decoder libraries.
+		[page:String url] — A string containing the path/URL of the <em>.drc</em> file.<br />
+		[page:Function onLoad] — A function to be called after the loading is successfully completed.<br />
+		[page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, that contains .[page:Integer total] and .[page:Integer loaded] bytes.<br />
+		[page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives error as an argument.<br />
+		</p>
+		<p>
+		Begin loading from url and call the <em>onLoad</em> function with the decompressed geometry.
 		</p>
 		</p>
 
 
-		<h3>[method:null setDecoderConfig]( [param:Object config] )</h3>
+		<h3>[method:this setPath]( [param:String path] )</h3>
 		<p>
 		<p>
-			[page:String config.type] - (Optional) <em>"js"</em> or <em>"wasm"</em>.<br />
+		[page:String path] — Base path.
 		</p>
 		</p>
 		<p>
 		<p>
-		Provides configuration for the decoder libraries. Configuration cannot be changed
-		after loading the decoders.
+		Set the base path for the <em>.drc</em> file.
 		</p>
 		</p>
 
 
-		<h3>[method:Promise getDecoderModule]()</h3>
+		<h3>[method:this setCrossOrigin]( [param:String value] )</h3>
 		<p>
 		<p>
-		Requests the decoder libraries, if not already loaded.
+		[page:String value] — The crossOrigin string to implement CORS for loading the url from a different domain that allows CORS.
 		</p>
 		</p>
 
 
-		<h3>[method:null releaseDecoderModule]()</h3>
+		<h3>[method:this setDecoderPath]( [param:String value] )</h3>
 		<p>
 		<p>
-		Disposes of the decoder library and deallocates memory. The decoder
-		[link:https://github.com/google/draco/issues/349 cannot be reloaded afterward].
+		[page:String value] — Path to folder containing the JS and WASM decoder libraries.
 		</p>
 		</p>
 
 
-		<h2>Methods</h2>
-
-		<h3>[method:null load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
+		<h3>[method:this setDecoderConfig]( [param:Object config] )</h3>
 		<p>
 		<p>
-		[page:String url] — A string containing the path/URL of the <em>.drc</em> file.<br />
-		[page:Function onLoad] — A function to be called after the loading is successfully completed.<br />
-		[page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, that contains .[page:Integer total] and .[page:Integer loaded] bytes.<br />
-		[page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives error as an argument.<br />
+			[page:String config.type] - (Optional) <em>"js"</em> or <em>"wasm"</em>.<br />
 		</p>
 		</p>
 		<p>
 		<p>
-		Begin loading from url and call the <em>onLoad</em> function with the decompressed geometry.
+		Provides configuration for the decoder libraries. Configuration cannot be changed
+		after decoding begins.
 		</p>
 		</p>
 
 
-		<h3>[method:DRACOLoader setPath]( [param:String path] )</h3>
+		<h3>[method:this setWorkerLimit]( [param:Number workerLimit] )</h3>
 		<p>
 		<p>
-		[page:String path] — Base path.
+			[page:Number workerLimit] - Maximum number of workers to be allocated. Default is 4.<br />
 		</p>
 		</p>
 		<p>
 		<p>
-		Set the base path for the <em>.drc</em> file.
+		Sets the maximum number of [link:https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers Web Workers]
+		to be used during decoding. A lower limit may be preferable if workers are also for other tasks
+		in the application.
+		</p>
+
+		<h3>[method:this dispose]()</h3>
+		<p>
+		Disposes of the decoder resources and deallocates memory. The decoder
+		[link:https://github.com/google/draco/issues/349 cannot be reloaded afterward].
 		</p>
 		</p>
 
 
 		<h2>Source</h2>
 		<h2>Source</h2>

+ 3 - 5
docs/examples/zh/loaders/GLTFLoader.html

@@ -56,11 +56,9 @@
 		var loader = new THREE.GLTFLoader();
 		var loader = new THREE.GLTFLoader();
 
 
 		// Optional: Provide a DRACOLoader instance to decode compressed mesh data
 		// Optional: Provide a DRACOLoader instance to decode compressed mesh data
-		THREE.DRACOLoader.setDecoderPath( '/examples/js/libs/draco' );
-		loader.setDRACOLoader( new THREE.DRACOLoader() );
-
-		// Optional: Pre-fetch Draco WASM/JS module, to save time while parsing.
-		THREE.DRACOLoader.getDecoderModule();
+		var dracoLoader = new THREE.DRACOLoader();
+		dracoLoader.setDecoderPath( '/examples/js/libs/draco' );
+		loader.setDRACOLoader( dracoLoader );
 
 
 		// Load a glTF resource
 		// Load a glTF resource
 		loader.load(
 		loader.load(

+ 428 - 525
examples/js/loaders/DRACOLoader.js

@@ -1,723 +1,626 @@
-/** Copyright 2016 The Draco Authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 /**
 /**
- * @param {THREE.LoadingManager} manager
+ * @author Don McCurdy / https://www.donmccurdy.com
  */
  */
+
 THREE.DRACOLoader = function ( manager ) {
 THREE.DRACOLoader = function ( manager ) {
 
 
-	this.timeLoaded = 0;
 	this.manager = manager || THREE.DefaultLoadingManager;
 	this.manager = manager || THREE.DefaultLoadingManager;
-	this.materials = null;
-	this.verbosity = 0;
-	this.attributeOptions = {};
-	this.drawMode = THREE.TrianglesDrawMode;
-	// Native Draco attribute type to Three.JS attribute type.
-	this.nativeAttributeMap = {
-		position: "POSITION",
-		normal: "NORMAL",
-		color: "COLOR",
-		uv: "TEX_COORD"
+
+	this.path = '';
+	this.crossOrigin = 'anonymous';
+
+	this.decoderPath = '';
+	this.decoderConfig = {};
+	this.decoderBinary = null;
+	this.decoderPending = null;
+
+	this.workerLimit = 4;
+	this.workerPool = [];
+	this.workerNextTaskID = 1;
+	this.workerSourceURL = '';
+
+	this.defaultAttributeIDs = {
+		position: 'POSITION',
+		normal: 'NORMAL',
+		color: 'COLOR',
+		uv: 'TEX_COORD'
+	};
+	this.defaultAttributeTypes = {
+		position: 'Float32Array',
+		normal: 'Float32Array',
+		color: 'Float32Array',
+		uv: 'Float32Array'
 	};
 	};
 
 
 };
 };
 
 
 THREE.DRACOLoader.prototype = {
 THREE.DRACOLoader.prototype = {
-	constructor: THREE.DRACOLoader,
 
 
-	load: function ( url, onLoad, onProgress, onError ) {
+	constructor: THREE.DRACOLoader,
 
 
-		var scope = this;
-		var loader = new THREE.FileLoader( scope.manager );
-		loader.setPath( this.path );
-		loader.setResponseType( "arraybuffer" );
-		loader.load(
-			url,
-			function ( blob ) {
+	setPath: function ( path ) {
 
 
-				scope.decodeDracoFile( blob, onLoad );
+		this.path = path;
 
 
-			},
-			onProgress,
-			onError
-		);
+		return this;
 
 
 	},
 	},
 
 
-	setPath: function ( value ) {
+	setCrossOrigin: function ( crossOrigin ) {
+
+		this.crossOrigin = crossOrigin;
 
 
-		this.path = value;
 		return this;
 		return this;
 
 
 	},
 	},
 
 
-	setVerbosity: function ( level ) {
+	setDecoderPath: function ( path ) {
+
+		this.decoderPath = path;
 
 
-		this.verbosity = level;
 		return this;
 		return this;
 
 
 	},
 	},
 
 
-	/**
-	 *  Sets desired mode for generated geometry indices.
-	 *  Can be either:
-	 *      THREE.TrianglesDrawMode
-	 *      THREE.TriangleStripDrawMode
-	 */
-	setDrawMode: function ( drawMode ) {
+	setDecoderConfig: function ( config ) {
+
+		this.decoderConfig = config;
 
 
-		this.drawMode = drawMode;
 		return this;
 		return this;
 
 
 	},
 	},
 
 
-	/**
-	 * Skips dequantization for a specific attribute.
-	 * |attributeName| is the THREE.js name of the given attribute type.
-	 * The only currently supported |attributeName| is 'position', more may be
-	 * added in future.
-	 */
-	setSkipDequantization: function ( attributeName, skip ) {
-
-		var skipDequantization = true;
-		if ( typeof skip !== "undefined" ) skipDequantization = skip;
-		this.getAttributeOptions(
-			attributeName
-		).skipDequantization = skipDequantization;
+	setWorkerLimit: function ( workerLimit ) {
+
+		this.workerLimit = workerLimit;
+
 		return this;
 		return this;
 
 
 	},
 	},
 
 
-	/**
-	 * Decompresses a Draco buffer. Names of attributes (for ID and type maps)
-	 * must be one of the supported three.js types, including: position, color,
-	 * normal, uv, uv2, skinIndex, skinWeight.
-	 *
-	 * @param {ArrayBuffer} rawBuffer
-	 * @param {Function} callback
-	 * @param {Object|undefined} attributeUniqueIdMap Provides a pre-defined ID
-	 *     for each attribute in the geometry to be decoded. If given,
-	 *     `attributeTypeMap` is required and `nativeAttributeMap` will be
-	 *     ignored.
-	 * @param {Object|undefined} attributeTypeMap Provides a predefined data
-	 *     type (as a typed array constructor) for each attribute in the
-	 *     geometry to be decoded.
-	 */
-	decodeDracoFile: function (
-		rawBuffer,
-		callback,
-		attributeUniqueIdMap,
-		attributeTypeMap
-	) {
-
-		var scope = this;
-		THREE.DRACOLoader.getDecoderModule().then( function ( module ) {
-
-			scope.decodeDracoFileInternal(
-				rawBuffer,
-				module.decoder,
-				callback,
-				attributeUniqueIdMap,
-				attributeTypeMap
-			);
+	/** @deprecated */
+	setVerbosity: function () {
 
 
-		} );
+		console.warn( 'THREE.DRACOLoader: The .setVerbosity() method has been removed.' );
 
 
 	},
 	},
 
 
-	decodeDracoFileInternal: function (
-		rawBuffer,
-		dracoDecoder,
-		callback,
-		attributeUniqueIdMap,
-		attributeTypeMap
-	) {
+	/** @deprecated */
+	setDrawMode: function () {
 
 
-		/*
-		 * Here is how to use Draco Javascript decoder and get the geometry.
-		 */
-		var buffer = new dracoDecoder.DecoderBuffer();
-		buffer.Init( new Int8Array( rawBuffer ), rawBuffer.byteLength );
-		var decoder = new dracoDecoder.Decoder();
+		console.warn( 'THREE.DRACOLoader: The .setDrawMode() method has been removed.' );
 
 
-		/*
-		 * Determine what type is this file: mesh or point cloud.
-		 */
-		var geometryType = decoder.GetEncodedGeometryType( buffer );
-		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
-
-			if ( this.verbosity > 0 ) {
+	},
 
 
-				console.log( "Loaded a mesh." );
+	/** @deprecated */
+	setSkipDequantization: function () {
 
 
-			}
+		console.warn( 'THREE.DRACOLoader: The .setSkipDequantization() method has been removed.' );
 
 
-		} else if ( geometryType == dracoDecoder.POINT_CLOUD ) {
+	},
 
 
-			if ( this.verbosity > 0 ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 
-				console.log( "Loaded a point cloud." );
+		var loader = new THREE.FileLoader( this.manager );
 
 
-			}
+		loader.setPath( this.path );
+		loader.setResponseType( 'arraybuffer' );
 
 
-		} else {
+		if ( this.crossOrigin === 'use-credentials' ) {
 
 
-			var errorMsg = "THREE.DRACOLoader: Unknown geometry type.";
-			console.error( errorMsg );
-			throw new Error( errorMsg );
+			loader.setWithCredentials( true );
 
 
 		}
 		}
-		callback(
-			this.convertDracoGeometryTo3JS(
-				dracoDecoder,
-				decoder,
-				geometryType,
-				buffer,
-				attributeUniqueIdMap,
-				attributeTypeMap
-			)
-		);
+
+		loader.load( url, ( buffer ) => {
+
+			var taskConfig = {
+				attributeIDs: this.defaultAttributeIDs,
+				attributeTypes: this.defaultAttributeTypes
+			};
+
+			this.decodeGeometry( buffer, taskConfig )
+				.then( onLoad )
+				.catch( onError );
+
+		}, onProgress, onError );
+
+	},
+
+	/** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */
+	decodeDracoFile: function ( buffer, callback, attributeIDs, attributeTypes ) {
+
+		var taskConfig = {
+			attributeIDs: attributeIDs || this.defaultAttributeIDs,
+			attributeTypes: attributeTypes || this.defaultAttributeTypes
+		};
+
+		this.decodeGeometry( buffer, taskConfig ).then( callback );
 
 
 	},
 	},
 
 
-	addAttributeToGeometry: function (
-		dracoDecoder,
-		decoder,
-		dracoGeometry,
-		attributeName,
-		attributeType,
-		attribute,
-		geometry,
-		geometryBuffer
-	) {
+	decodeGeometry: function ( buffer, taskConfig ) {
 
 
-		if ( attribute.ptr === 0 ) {
+		var worker;
+		var taskID = this.workerNextTaskID ++;
+		var taskCost = buffer.byteLength;
 
 
-			var errorMsg = "THREE.DRACOLoader: No attribute " + attributeName;
-			console.error( errorMsg );
-			throw new Error( errorMsg );
+		// TODO: For backward-compatibility, support 'attributeTypes' objects containing
+		// references (rather than names) to typed array constructors. These must be
+		// serialized before sending them to the worker.
+		for ( var attribute in taskConfig.attributeTypes ) {
+
+			var type = taskConfig.attributeTypes[ attribute ];
+
+			if ( type.BYTES_PER_ELEMENT !== undefined ) {
+
+				taskConfig.attributeTypes[ attribute ] = type.name;
+
+			}
 
 
 		}
 		}
 
 
-		var numComponents = attribute.num_components();
-		var numPoints = dracoGeometry.num_points();
-		var numValues = numPoints * numComponents;
-		var attributeData;
-		var TypedBufferAttribute;
+		// Obtain a worker and assign a task, and construct a geometry instance
+		// when the task completes.
+		var geometryPending = this._getWorker( taskID, taskCost )
+			.then( ( _worker ) => {
 
 
-		switch ( attributeType ) {
+				worker = _worker;
 
 
-			case Float32Array:
-				attributeData = new dracoDecoder.DracoFloat32Array();
-				decoder.GetAttributeFloatForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Float32Array( numValues );
-				TypedBufferAttribute = THREE.Float32BufferAttribute;
-				break;
+				return new Promise( ( resolve, reject ) => {
 
 
-			case Int8Array:
-				attributeData = new dracoDecoder.DracoInt8Array();
-				decoder.GetAttributeInt8ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Int8Array( numValues );
-				TypedBufferAttribute = THREE.Int8BufferAttribute;
-				break;
+					worker._callbacks[ taskID ] = { resolve, reject };
 
 
-			case Int16Array:
-				attributeData = new dracoDecoder.DracoInt16Array();
-				decoder.GetAttributeInt16ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Int16Array( numValues );
-				TypedBufferAttribute = THREE.Int16BufferAttribute;
-				break;
+					worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] );
 
 
-			case Int32Array:
-				attributeData = new dracoDecoder.DracoInt32Array();
-				decoder.GetAttributeInt32ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Int32Array( numValues );
-				TypedBufferAttribute = THREE.Int32BufferAttribute;
-				break;
+					// this.debug();
 
 
-			case Uint8Array:
-				attributeData = new dracoDecoder.DracoUInt8Array();
-				decoder.GetAttributeUInt8ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Uint8Array( numValues );
-				TypedBufferAttribute = THREE.Uint8BufferAttribute;
-				break;
+				} );
 
 
-			case Uint16Array:
-				attributeData = new dracoDecoder.DracoUInt16Array();
-				decoder.GetAttributeUInt16ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Uint16Array( numValues );
-				TypedBufferAttribute = THREE.Uint16BufferAttribute;
-				break;
+			} )
+			.then( ( message ) => this._createGeometry( message.geometry ) );
 
 
-			case Uint32Array:
-				attributeData = new dracoDecoder.DracoUInt32Array();
-				decoder.GetAttributeUInt32ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Uint32Array( numValues );
-				TypedBufferAttribute = THREE.Uint32BufferAttribute;
-				break;
+		// Remove task from the task list.
+		geometryPending
+			.finally( () => {
 
 
-			default:
-				var errorMsg = "THREE.DRACOLoader: Unexpected attribute type.";
-				console.error( errorMsg );
-				throw new Error( errorMsg );
+				if ( worker && taskID ) {
 
 
-		}
+					this._releaseTask( worker, taskID );
 
 
-		// Copy data from decoder.
-		for ( var i = 0; i < numValues; i ++ ) {
+					// this.debug();
 
 
-			geometryBuffer[ attributeName ][ i ] = attributeData.GetValue( i );
+				}
 
 
-		}
-		// Add attribute to THREEJS geometry for rendering.
-		geometry.addAttribute(
-			attributeName,
-			new TypedBufferAttribute( geometryBuffer[ attributeName ], numComponents )
-		);
-		dracoDecoder.destroy( attributeData );
+			} );
+
+		return geometryPending;
 
 
 	},
 	},
 
 
-	convertDracoGeometryTo3JS: function (
-		dracoDecoder,
-		decoder,
-		geometryType,
-		buffer,
-		attributeUniqueIdMap,
-		attributeTypeMap
-	) {
+	_createGeometry: function ( geometryData ) {
+
+		var geometry = new THREE.BufferGeometry();
 
 
-		// TODO: Should not assume native Draco attribute IDs apply.
-		if ( this.getAttributeOptions( "position" ).skipDequantization === true ) {
+		if ( geometryData.index ) {
 
 
-			decoder.SkipAttributeTransform( dracoDecoder.POSITION );
+			geometry.setIndex( new THREE.BufferAttribute( geometryData.index.array, 1 ) );
 
 
 		}
 		}
-		var dracoGeometry;
-		var decodingStatus;
-		var start_time = performance.now();
-		if ( geometryType === dracoDecoder.TRIANGULAR_MESH ) {
 
 
-			dracoGeometry = new dracoDecoder.Mesh();
-			decodingStatus = decoder.DecodeBufferToMesh( buffer, dracoGeometry );
+		for ( var i = 0; i < geometryData.attributes.length; i++ ) {
 
 
-		} else {
+			var attribute = geometryData.attributes[ i ];
+			var name = attribute.name;
+			var array = attribute.array;
+			var itemSize = attribute.itemSize;
 
 
-			dracoGeometry = new dracoDecoder.PointCloud();
-			decodingStatus = decoder.DecodeBufferToPointCloud( buffer, dracoGeometry );
+			geometry.addAttribute( name, new THREE.BufferAttribute( array, itemSize ) );
 
 
 		}
 		}
-		if ( ! decodingStatus.ok() || dracoGeometry.ptr == 0 ) {
 
 
-			var errorMsg = "THREE.DRACOLoader: Decoding failed: ";
-			errorMsg += decodingStatus.error_msg();
-			console.error( errorMsg );
-			dracoDecoder.destroy( decoder );
-			dracoDecoder.destroy( dracoGeometry );
-			throw new Error( errorMsg );
+		return geometry;
 
 
-		}
+	},
 
 
-		var decode_end = performance.now();
-		dracoDecoder.destroy( buffer );
-		/*
-		 * Example on how to retrieve mesh and attributes.
-		 */
-		var numFaces;
-		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+	_loadLibrary: function ( url, responseType ) {
 
 
-			numFaces = dracoGeometry.num_faces();
-			if ( this.verbosity > 0 ) {
+		var loader = new THREE.FileLoader( this.manager );
+		loader.setPath( this.decoderPath );
+		loader.setResponseType( responseType );
 
 
-				console.log( "Number of faces loaded: " + numFaces.toString() );
+		return new Promise( ( resolve, reject ) => {
 
 
-			}
+			loader.load( url, resolve, undefined, reject );
+
+		} );
+
+	},
+
+	_initDecoder: function () {
+
+		if ( this.decoderPending ) return this.decoderPending;
+
+		var useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
+		var librariesPending = [];
+
+		if ( useJS ) {
+
+			librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) );
 
 
 		} else {
 		} else {
 
 
-			numFaces = 0;
+			librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) );
+			librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) );
 
 
 		}
 		}
 
 
-		var numPoints = dracoGeometry.num_points();
-		var numAttributes = dracoGeometry.num_attributes();
-		if ( this.verbosity > 0 ) {
+		this.decoderPending = Promise.all( librariesPending )
+			.then( ( libraries ) => {
 
 
-			console.log( "Number of points loaded: " + numPoints.toString() );
-			console.log( "Number of attributes loaded: " + numAttributes.toString() );
+				var jsContent = libraries[ 0 ];
 
 
-		}
+				if ( ! useJS ) {
 
 
-		// Verify if there is position attribute.
-		// TODO: Should not assume native Draco attribute IDs apply.
-		var posAttId = decoder.GetAttributeId( dracoGeometry, dracoDecoder.POSITION );
-		if ( posAttId == - 1 ) {
+					this.decoderConfig.wasmBinary = libraries[ 1 ];
 
 
-			var errorMsg = "THREE.DRACOLoader: No position attribute found.";
-			console.error( errorMsg );
-			dracoDecoder.destroy( decoder );
-			dracoDecoder.destroy( dracoGeometry );
-			throw new Error( errorMsg );
+				}
 
 
-		}
-		var posAttribute = decoder.GetAttribute( dracoGeometry, posAttId );
+				var fn = THREE.DRACOLoader.DRACOWorker.toString();
 
 
-		// Structure for converting to THREEJS geometry later.
-		var geometryBuffer = {};
-		// Import data to Three JS geometry.
-		var geometry = new THREE.BufferGeometry();
+				var body = [
+					'/* draco decoder */',
+					jsContent,
+					'',
+					'/* worker */',
+					fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
+				].join( '\n' );
 
 
-		// Do not use both the native attribute map and a provided (e.g. glTF) map.
-		if ( attributeUniqueIdMap ) {
-
-			// Add attributes of user specified unique id. E.g. GLTF models.
-			for ( var attributeName in attributeUniqueIdMap ) {
-
-				var attributeType = attributeTypeMap[ attributeName ];
-				var attributeId = attributeUniqueIdMap[ attributeName ];
-				var attribute = decoder.GetAttributeByUniqueId(
-					dracoGeometry,
-					attributeId
-				);
-				this.addAttributeToGeometry(
-					dracoDecoder,
-					decoder,
-					dracoGeometry,
-					attributeName,
-					attributeType,
-					attribute,
-					geometry,
-					geometryBuffer
-				);
+				this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
 
 
-			}
+			} );
 
 
-		} else {
+		return this.decoderPending;
 
 
-			// Add native Draco attribute type to geometry.
-			for ( var attributeName in this.nativeAttributeMap ) {
+	},
 
 
-				var attId = decoder.GetAttributeId(
-					dracoGeometry,
-					dracoDecoder[ this.nativeAttributeMap[ attributeName ] ]
-				);
-				if ( attId !== - 1 ) {
+	_getWorker: function ( taskID, taskCost ) {
 
 
-					if ( this.verbosity > 0 ) {
+		return this._initDecoder().then( () => {
 
 
-						console.log( "Loaded " + attributeName + " attribute." );
+			if ( this.workerPool.length < this.workerLimit ) {
 
 
-					}
-					var attribute = decoder.GetAttribute( dracoGeometry, attId );
-					this.addAttributeToGeometry(
-						dracoDecoder,
-						decoder,
-						dracoGeometry,
-						attributeName,
-						Float32Array,
-						attribute,
-						geometry,
-						geometryBuffer
-					);
+				var worker = new Worker( this.workerSourceURL );
 
 
-				}
+				worker._callbacks = {};
+				worker._taskCosts = {};
+				worker._taskLoad = 0;
 
 
-			}
+				worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } );
 
 
-		}
+				worker.onmessage = function ( e ) {
 
 
-		// For mesh, we need to generate the faces.
-		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+					var message = e.data;
 
 
-			if ( this.drawMode === THREE.TriangleStripDrawMode ) {
+					switch ( message.type ) {
 
 
-				var stripsArray = new dracoDecoder.DracoInt32Array();
-				decoder.GetTriangleStripsFromMesh(
-					dracoGeometry,
-					stripsArray
-				);
-				geometryBuffer.indices = new Uint32Array( stripsArray.size() );
-				for ( var i = 0; i < stripsArray.size(); ++ i ) {
+						case 'decode':
+							worker._callbacks[ message.id ].resolve( message );
+							break;
 
 
-					geometryBuffer.indices[ i ] = stripsArray.GetValue( i );
+						case 'error':
+							worker._callbacks[ message.id ].reject( message );
+							break;
 
 
-				}
-				dracoDecoder.destroy( stripsArray );
+						default:
+							console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' );
+
+					}
+
+				};
+
+				this.workerPool.push( worker );
 
 
 			} else {
 			} else {
 
 
-				var numIndices = numFaces * 3;
-				geometryBuffer.indices = new Uint32Array( numIndices );
-				var ia = new dracoDecoder.DracoInt32Array();
-				for ( var i = 0; i < numFaces; ++ i ) {
+				this.workerPool.sort( function ( a, b ) {
 
 
-					decoder.GetFaceFromMesh( dracoGeometry, i, ia );
-					var index = i * 3;
-					geometryBuffer.indices[ index ] = ia.GetValue( 0 );
-					geometryBuffer.indices[ index + 1 ] = ia.GetValue( 1 );
-					geometryBuffer.indices[ index + 2 ] = ia.GetValue( 2 );
+					return a._taskLoad > b._taskLoad ? - 1 : 1;
 
 
-				}
-				dracoDecoder.destroy( ia );
+				} );
 
 
 			}
 			}
 
 
-		}
+			var worker = this.workerPool[ this.workerPool.length - 1 ];
+			worker._taskCosts[ taskID ] = taskCost;
+			worker._taskLoad += taskCost;
+			return worker;
+
+		} );
+
+	},
+
+	_releaseTask: function ( worker, taskID ) {
+
+		worker._taskLoad -= worker._taskCosts[ taskID ];
+		delete worker._callbacks[ taskID ];
+		delete worker._taskCosts[ taskID ];
+
+	},
+
+	debug: function () {
 
 
-		geometry.drawMode = this.drawMode;
-		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+		console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) );
 
 
-			geometry.setIndex(
-				new ( geometryBuffer.indices.length > 65535
-					? THREE.Uint32BufferAttribute
-					: THREE.Uint16BufferAttribute )( geometryBuffer.indices, 1 )
-			);
+	},
+
+	dispose: function () {
+
+		for ( var i = 0; i < this.workerPool.length; ++ i ) {
+
+			this.workerPool[ i ].terminate();
 
 
 		}
 		}
 
 
-		// TODO: Should not assume native Draco attribute IDs apply.
-		// TODO: Can other attribute types be quantized?
-		var posTransform = new dracoDecoder.AttributeQuantizationTransform();
-		if ( posTransform.InitFromAttribute( posAttribute ) ) {
-
-			// Quantized attribute. Store the quantization parameters into the
-			// THREE.js attribute.
-			geometry.attributes[ "position" ].isQuantized = true;
-			geometry.attributes[ "position" ].maxRange = posTransform.range();
-			geometry.attributes[
-				"position"
-			].numQuantizationBits = posTransform.quantization_bits();
-			geometry.attributes[ "position" ].minValues = new Float32Array( 3 );
-			for ( var i = 0; i < 3; ++ i ) {
-
-				geometry.attributes[ "position" ].minValues[ i ] = posTransform.min_value(
-					i
-				);
+		this.workerPool.length = 0;
 
 
-			}
+		return this;
+
+	}
+};
+
+/* WEB WORKER */
+
+THREE.DRACOLoader.DRACOWorker = function () {
+
+	var decoderConfig;
+	var decoderPending;
+
+	onmessage = function ( e ) {
+
+		var message = e.data;
+
+		switch ( message.type ) {
+
+			case 'init':
+				decoderConfig = message.decoderConfig;
+				decoderPending = new Promise( function ( resolve, reject ) {
+
+					decoderConfig.onModuleLoaded = function ( draco ) {
+
+						// Module is Promise-like. Wrap before resolving to avoid loop.
+						resolve( { draco: draco } );
+
+					};
+
+					DracoDecoderModule( decoderConfig );
+
+				} );
+				break;
+
+			case 'decode':
+				var buffer = message.buffer;
+				var taskConfig = message.taskConfig;
+				decoderPending.then( ( module ) => {
+
+					var draco = module.draco;
+					var decoder = new draco.Decoder();
+					var decoderBuffer = new draco.DecoderBuffer();
+					decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength );
+
+					try {
+
+						var geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig );
+
+						var buffers = geometry.attributes.map( ( attr ) => attr.array.buffer );
+
+						if ( geometry.index ) buffers.push( geometry.index.array.buffer );
+
+						self.postMessage( { type: 'decode', id: message.id, geometry }, buffers );
+
+					} catch ( error ) {
+
+						console.error( error );
+
+						self.postMessage( { type: 'error', id: message.id, error: error.message } );
+
+					} finally {
+
+						draco.destroy( decoderBuffer );
+						draco.destroy( decoder );
+
+					}
+
+				} );
+				break;
 
 
 		}
 		}
-		dracoDecoder.destroy( posTransform );
-		dracoDecoder.destroy( decoder );
-		dracoDecoder.destroy( dracoGeometry );
 
 
-		this.decode_time = decode_end - start_time;
-		this.import_time = performance.now() - decode_end;
+	};
+
+	function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) {
+
+		var attributeIDs = taskConfig.attributeIDs;
+		var attributeTypes = taskConfig.attributeTypes;
+
+		var dracoGeometry;
+		var decodingStatus;
 
 
-		if ( this.verbosity > 0 ) {
+		var geometryType = decoder.GetEncodedGeometryType( decoderBuffer );
 
 
-			console.log( "Decode time: " + this.decode_time );
-			console.log( "Import time: " + this.import_time );
+		if ( geometryType === draco.TRIANGULAR_MESH ) {
+
+			dracoGeometry = new draco.Mesh();
+			decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry );
+
+		} else if ( geometryType === draco.POINT_CLOUD ) {
+
+			dracoGeometry = new draco.PointCloud();
+			decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry );
+
+		} else {
+
+			throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' );
 
 
 		}
 		}
-		return geometry;
 
 
-	},
+		if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) {
 
 
-	isVersionSupported: function ( version, callback ) {
+			throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() );
 
 
-		THREE.DRACOLoader.getDecoderModule().then( function ( module ) {
+		}
 
 
-			callback( module.decoder.isVersionSupported( version ) );
+		var geometry = { index: null, attributes: [] };
 
 
-		} );
+		var numPoints = dracoGeometry.num_points();
+		var numAttributes = dracoGeometry.num_attributes();
 
 
-	},
+		// Add attributes of user specified unique id.
+		for (var attributeName in attributeIDs) {
 
 
-	getAttributeOptions: function ( attributeName ) {
+			var attributeType = self[ attributeTypes[ attributeName ] ];
+			var attributeId = attributeIDs[ attributeName ];
+			var attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeId );
 
 
-		if ( typeof this.attributeOptions[ attributeName ] === "undefined" )
-			this.attributeOptions[ attributeName ] = {};
-		return this.attributeOptions[ attributeName ];
+			geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) );
 
 
-	}
-};
+		}
 
 
-THREE.DRACOLoader.decoderPath = "./";
-THREE.DRACOLoader.decoderConfig = {};
-THREE.DRACOLoader.decoderModulePromise = null;
+		// Add index.
+		if ( geometryType === draco.TRIANGULAR_MESH ) {
 
 
-/**
- * Sets the base path for decoder source files.
- * @param {string} path
- */
-THREE.DRACOLoader.setDecoderPath = function ( path ) {
+			// Generate mesh faces.
+			var numFaces = dracoGeometry.num_faces();
+			var numIndices = numFaces * 3;
+			var index = new Uint32Array( numIndices );
+			var indexArray = new draco.DracoInt32Array();
 
 
-	THREE.DRACOLoader.decoderPath = path;
+			for ( var i = 0; i < numFaces; ++ i ) {
 
 
-};
+				decoder.GetFaceFromMesh( dracoGeometry, i, indexArray );
 
 
-/**
- * Sets decoder configuration and releases singleton decoder module. Module
- * will be recreated with the next decoding call.
- * @param {Object} config
- */
-THREE.DRACOLoader.setDecoderConfig = function ( config ) {
+				for ( var j = 0; j < 3; ++ j ) {
 
 
-	var wasmBinary = THREE.DRACOLoader.decoderConfig.wasmBinary;
-	THREE.DRACOLoader.decoderConfig = config || {};
-	THREE.DRACOLoader.releaseDecoderModule();
+					index[ i * 3 + j ] = indexArray.GetValue( j );
 
 
-	// Reuse WASM binary.
-	if ( wasmBinary ) THREE.DRACOLoader.decoderConfig.wasmBinary = wasmBinary;
+				}
 
 
-};
+			}
 
 
-/**
- * Releases the singleton DracoDecoderModule instance. Module will be recreated
- * with the next decoding call.
- */
-THREE.DRACOLoader.releaseDecoderModule = function () {
+			geometry.index = { array: index, itemSize: 1 };
 
 
-	THREE.DRACOLoader.decoderModulePromise = null;
+			draco.destroy( indexArray );
 
 
-};
+		}
 
 
-/**
- * Gets WebAssembly or asm.js singleton instance of DracoDecoderModule
- * after testing for browser support. Returns Promise that resolves when
- * module is available.
- * @return {Promise<{decoder: DracoDecoderModule}>}
- */
-THREE.DRACOLoader.getDecoderModule = function () {
+		draco.destroy( dracoGeometry );
 
 
-	var scope = this;
-	var path = THREE.DRACOLoader.decoderPath;
-	var config = THREE.DRACOLoader.decoderConfig;
-	var promise = THREE.DRACOLoader.decoderModulePromise;
+		return geometry;
 
 
-	if ( promise ) return promise;
+	};
 
 
-	// Load source files.
-	if ( typeof DracoDecoderModule !== "undefined" ) {
+	function decodeAttribute ( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) {
 
 
-		// Loaded externally.
-		promise = Promise.resolve();
+		var numComponents = attribute.num_components();
+		var numPoints = dracoGeometry.num_points();
+		var numValues = numPoints * numComponents;
+		var dracoArray;
 
 
-	} else if ( typeof WebAssembly !== "object" || config.type === "js" ) {
+		var array;
 
 
-		// Load with asm.js.
-		promise = THREE.DRACOLoader._loadScript( path + "draco_decoder.js" );
+		switch ( attributeType ) {
 
 
-	} else {
+			case Float32Array:
+				dracoArray = new draco.DracoFloat32Array();
+				decoder.GetAttributeFloatForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Float32Array( numValues );
+				break;
 
 
-		// Load with WebAssembly.
-		config.wasmBinaryFile = path + "draco_decoder.wasm";
-		promise = THREE.DRACOLoader._loadScript( path + "draco_wasm_wrapper.js" )
-			.then( function () {
+			case Int8Array:
+				dracoArray = new draco.DracoInt8Array();
+				decoder.GetAttributeInt8ForAllPoints( dracoGeometry, attribute, dracoArray  );
+				array = new Int8Array( numValues );
+				break;
 
 
-				return THREE.DRACOLoader._loadArrayBuffer( config.wasmBinaryFile );
+			case Int16Array:
+				dracoArray = new draco.DracoInt16Array();
+				decoder.GetAttributeInt16ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Int16Array( numValues );
+				break;
 
 
-			} )
-			.then( function ( wasmBinary ) {
+			case Int32Array:
+				dracoArray = new draco.DracoInt32Array();
+				decoder.GetAttributeInt32ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Int32Array( numValues );
+				break;
 
 
-				config.wasmBinary = wasmBinary;
+			case Uint8Array:
+				dracoArray = new draco.DracoUInt8Array();
+				decoder.GetAttributeUInt8ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Uint8Array( numValues );
+				break;
 
 
-			} );
+			case Uint16Array:
+				dracoArray = new draco.DracoUInt16Array();
+				decoder.GetAttributeUInt16ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Uint16Array( numValues );
+				break;
 
 
-	}
+			case Uint32Array:
+				dracoArray = new draco.DracoUInt32Array();
+				decoder.GetAttributeUInt32ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Uint32Array( numValues );
+				break;
 
 
-	// Wait for source files, then create and return a decoder.
-	promise = promise.then( function () {
+			default:
+				throw new Error( 'THREE.DRACOLoader: Unexpected attribute type.' );
 
 
-		return new Promise( function ( resolve ) {
+		}
 
 
-			config.onModuleLoaded = function ( decoder ) {
+		for ( var i = 0; i < numValues; i++ ) {
 
 
-				scope.timeLoaded = performance.now();
-				// Module is Promise-like. Wrap before resolving to avoid loop.
-				resolve( { decoder: decoder } );
+			array[ i ] = dracoArray.GetValue( i );
 
 
-			};
-			DracoDecoderModule( config );
+		}
 
 
-		} );
+		draco.destroy( dracoArray );
 
 
-	} );
+		return {
+			name: attributeName,
+			array: array,
+			itemSize: numComponents
+		};
 
 
-	THREE.DRACOLoader.decoderModulePromise = promise;
-	return promise;
+	};
 
 
 };
 };
 
 
-/**
- * @param {string} src
- * @return {Promise}
- */
-THREE.DRACOLoader._loadScript = function ( src ) {
+/** Deprecated static methods */
 
 
-	var prevScript = document.getElementById( "decoder_script" );
-	if ( prevScript !== null ) {
+/** @deprecated */
+THREE.DRACOLoader.setDecoderPath = function () {
 
 
-		prevScript.parentNode.removeChild( prevScript );
+	console.warn( 'THREE.DRACOLoader: The .setDecoderPath() method has been removed. Use instance methods.' );
 
 
-	}
-	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 );
+/** @deprecated */
+THREE.DRACOLoader.setDecoderConfig = function () {
 
 
-	} );
+	console.warn( 'THREE.DRACOLoader: The .setDecoderConfig() method has been removed. Use instance methods.' );
 
 
 };
 };
 
 
-/**
- * @param {string} src
- * @return {Promise}
- */
-THREE.DRACOLoader._loadArrayBuffer = function ( src ) {
+/** @deprecated */
+THREE.DRACOLoader.releaseDecoderModule = function () {
 
 
-	var loader = new THREE.FileLoader();
-	loader.setResponseType( "arraybuffer" );
-	return new Promise( function ( resolve, reject ) {
+	console.warn( 'THREE.DRACOLoader: The .releaseDecoderModule() method has been removed. Use instance methods.' );
 
 
-		loader.load( src, resolve, undefined, reject );
+};
+
+/** @deprecated */
+THREE.DRACOLoader.getDecoderModule = function () {
 
 
-	} );
+	console.warn( 'THREE.DRACOLoader: The .getDecoderModule() method has been removed. Use instance methods.' );
 
 
 };
 };

+ 6 - 11
examples/jsm/loaders/DRACOLoader.d.ts

@@ -1,21 +1,16 @@
 import {
 import {
   LoadingManager,
   LoadingManager,
-  BufferGeometry,
-  TrianglesDrawModes
+  BufferGeometry
 } from '../../../src/Three';
 } from '../../../src/Three';
 
 
 export class DRACOLoader {
 export class DRACOLoader {
   constructor(manager?: LoadingManager);
   constructor(manager?: LoadingManager);
 
 
-  static setDecoderPath(path: string): void;
-  static setDecoderConfig(config: object): void;
-  static getDecoderModule(): Promise<any>;
-  static releaseDecoderModule(): void;
-
   load(url: string, onLoad: (geometry: BufferGeometry) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): void;
   load(url: string, onLoad: (geometry: BufferGeometry) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): void;
   setPath(path: string): DRACOLoader;
   setPath(path: string): DRACOLoader;
-  setVerbosity(level: number): DRACOLoader;
-  setDrawMode(drawMode: TrianglesDrawModes): DRACOLoader;
-  setSkipDequantization(attributeName: 'position', skip?: boolean): DRACOLoader;
-  isVersionSupported(version: number, callback: (isVersionSupported: boolean) => any): void;
+  setDecoderPath(path: string): DRACOLoader;
+  setDecoderConfig(config: object): DRACOLoader;
+  setCrossOrigin(crossOrigin: string): DRACOLoader;
+  setWorkerLimit(workerLimit: number): DRACOLoader;
+  dispose(): DRACOLoader;
 }
 }

+ 430 - 535
examples/jsm/loaders/DRACOLoader.js

@@ -1,739 +1,634 @@
-/** Copyright 2016 The Draco Authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+/**
+ * @author Don McCurdy / https://www.donmccurdy.com
  */
  */
 
 
 import {
 import {
+	BufferAttribute,
 	BufferGeometry,
 	BufferGeometry,
 	DefaultLoadingManager,
 	DefaultLoadingManager,
-	FileLoader,
-	Float32BufferAttribute,
-	Int16BufferAttribute,
-	Int32BufferAttribute,
-	Int8BufferAttribute,
-	TriangleStripDrawMode,
-	TrianglesDrawMode,
-	Uint16BufferAttribute,
-	Uint32BufferAttribute,
-	Uint8BufferAttribute
+	FileLoader
 } from "../../../build/three.module.js";
 } from "../../../build/three.module.js";
 
 
-/**
- * @param {THREE.LoadingManager} manager
- */
 var DRACOLoader = function ( manager ) {
 var DRACOLoader = function ( manager ) {
 
 
-	this.timeLoaded = 0;
 	this.manager = manager || DefaultLoadingManager;
 	this.manager = manager || DefaultLoadingManager;
-	this.materials = null;
-	this.verbosity = 0;
-	this.attributeOptions = {};
-	this.drawMode = TrianglesDrawMode;
-	// Native Draco attribute type to Three.JS attribute type.
-	this.nativeAttributeMap = {
-		position: "POSITION",
-		normal: "NORMAL",
-		color: "COLOR",
-		uv: "TEX_COORD"
+
+	this.path = '';
+	this.crossOrigin = 'anonymous';
+
+	this.decoderPath = '';
+	this.decoderConfig = {};
+	this.decoderBinary = null;
+	this.decoderPending = null;
+
+	this.workerLimit = 4;
+	this.workerPool = [];
+	this.workerNextTaskID = 1;
+	this.workerSourceURL = '';
+
+	this.defaultAttributeIDs = {
+		position: 'POSITION',
+		normal: 'NORMAL',
+		color: 'COLOR',
+		uv: 'TEX_COORD'
+	};
+	this.defaultAttributeTypes = {
+		position: 'Float32Array',
+		normal: 'Float32Array',
+		color: 'Float32Array',
+		uv: 'Float32Array'
 	};
 	};
 
 
 };
 };
 
 
 DRACOLoader.prototype = {
 DRACOLoader.prototype = {
-	constructor: DRACOLoader,
 
 
-	load: function ( url, onLoad, onProgress, onError ) {
+	constructor: DRACOLoader,
 
 
-		var scope = this;
-		var loader = new FileLoader( scope.manager );
-		loader.setPath( this.path );
-		loader.setResponseType( "arraybuffer" );
-		loader.load(
-			url,
-			function ( blob ) {
+	setPath: function ( path ) {
 
 
-				scope.decodeDracoFile( blob, onLoad );
+		this.path = path;
 
 
-			},
-			onProgress,
-			onError
-		);
+		return this;
 
 
 	},
 	},
 
 
-	setPath: function ( value ) {
+	setCrossOrigin: function ( crossOrigin ) {
+
+		this.crossOrigin = crossOrigin;
 
 
-		this.path = value;
 		return this;
 		return this;
 
 
 	},
 	},
 
 
-	setVerbosity: function ( level ) {
+	setDecoderPath: function ( path ) {
+
+		this.decoderPath = path;
 
 
-		this.verbosity = level;
 		return this;
 		return this;
 
 
 	},
 	},
 
 
-	/**
-	 *  Sets desired mode for generated geometry indices.
-	 *  Can be either:
-	 *      TrianglesDrawMode
-	 *      TriangleStripDrawMode
-	 */
-	setDrawMode: function ( drawMode ) {
+	setDecoderConfig: function ( config ) {
+
+		this.decoderConfig = config;
 
 
-		this.drawMode = drawMode;
 		return this;
 		return this;
 
 
 	},
 	},
 
 
-	/**
-	 * Skips dequantization for a specific attribute.
-	 * |attributeName| is the js name of the given attribute type.
-	 * The only currently supported |attributeName| is 'position', more may be
-	 * added in future.
-	 */
-	setSkipDequantization: function ( attributeName, skip ) {
-
-		var skipDequantization = true;
-		if ( typeof skip !== "undefined" ) skipDequantization = skip;
-		this.getAttributeOptions(
-			attributeName
-		).skipDequantization = skipDequantization;
+	setWorkerLimit: function ( workerLimit ) {
+
+		this.workerLimit = workerLimit;
+
 		return this;
 		return this;
 
 
 	},
 	},
 
 
-	/**
-	 * Decompresses a Draco buffer. Names of attributes (for ID and type maps)
-	 * must be one of the supported three.js types, including: position, color,
-	 * normal, uv, uv2, skinIndex, skinWeight.
-	 *
-	 * @param {ArrayBuffer} rawBuffer
-	 * @param {Function} callback
-	 * @param {Object|undefined} attributeUniqueIdMap Provides a pre-defined ID
-	 *     for each attribute in the geometry to be decoded. If given,
-	 *     `attributeTypeMap` is required and `nativeAttributeMap` will be
-	 *     ignored.
-	 * @param {Object|undefined} attributeTypeMap Provides a predefined data
-	 *     type (as a typed array constructor) for each attribute in the
-	 *     geometry to be decoded.
-	 */
-	decodeDracoFile: function (
-		rawBuffer,
-		callback,
-		attributeUniqueIdMap,
-		attributeTypeMap
-	) {
-
-		var scope = this;
-		DRACOLoader.getDecoderModule().then( function ( module ) {
-
-			scope.decodeDracoFileInternal(
-				rawBuffer,
-				module.decoder,
-				callback,
-				attributeUniqueIdMap,
-				attributeTypeMap
-			);
+	/** @deprecated */
+	setVerbosity: function () {
 
 
-		} );
+		console.warn( 'THREE.DRACOLoader: The .setVerbosity() method has been removed.' );
 
 
 	},
 	},
 
 
-	decodeDracoFileInternal: function (
-		rawBuffer,
-		dracoDecoder,
-		callback,
-		attributeUniqueIdMap,
-		attributeTypeMap
-	) {
+	/** @deprecated */
+	setDrawMode: function () {
 
 
-		/*
-		 * Here is how to use Draco Javascript decoder and get the geometry.
-		 */
-		var buffer = new dracoDecoder.DecoderBuffer();
-		buffer.Init( new Int8Array( rawBuffer ), rawBuffer.byteLength );
-		var decoder = new dracoDecoder.Decoder();
+		console.warn( 'THREE.DRACOLoader: The .setDrawMode() method has been removed.' );
 
 
-		/*
-		 * Determine what type is this file: mesh or point cloud.
-		 */
-		var geometryType = decoder.GetEncodedGeometryType( buffer );
-		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
-
-			if ( this.verbosity > 0 ) {
+	},
 
 
-				console.log( "Loaded a mesh." );
+	/** @deprecated */
+	setSkipDequantization: function () {
 
 
-			}
+		console.warn( 'THREE.DRACOLoader: The .setSkipDequantization() method has been removed.' );
 
 
-		} else if ( geometryType == dracoDecoder.POINT_CLOUD ) {
+	},
 
 
-			if ( this.verbosity > 0 ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 
-				console.log( "Loaded a point cloud." );
+		var loader = new FileLoader( this.manager );
 
 
-			}
+		loader.setPath( this.path );
+		loader.setResponseType( 'arraybuffer' );
 
 
-		} else {
+		if ( this.crossOrigin === 'use-credentials' ) {
 
 
-			var errorMsg = "DRACOLoader: Unknown geometry type.";
-			console.error( errorMsg );
-			throw new Error( errorMsg );
+			loader.setWithCredentials( true );
 
 
 		}
 		}
-		callback(
-			this.convertDracoGeometryTo3JS(
-				dracoDecoder,
-				decoder,
-				geometryType,
-				buffer,
-				attributeUniqueIdMap,
-				attributeTypeMap
-			)
-		);
+
+		loader.load( url, ( buffer ) => {
+
+			var taskConfig = {
+				attributeIDs: this.defaultAttributeIDs,
+				attributeTypes: this.defaultAttributeTypes
+			};
+
+			this.decodeGeometry( buffer, taskConfig )
+				.then( onLoad )
+				.catch( onError );
+
+		}, onProgress, onError );
+
+	},
+
+	/** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */
+	decodeDracoFile: function ( buffer, callback, attributeIDs, attributeTypes ) {
+
+		var taskConfig = {
+			attributeIDs: attributeIDs || this.defaultAttributeIDs,
+			attributeTypes: attributeTypes || this.defaultAttributeTypes
+		};
+
+		this.decodeGeometry( buffer, taskConfig ).then( callback );
 
 
 	},
 	},
 
 
-	addAttributeToGeometry: function (
-		dracoDecoder,
-		decoder,
-		dracoGeometry,
-		attributeName,
-		attributeType,
-		attribute,
-		geometry,
-		geometryBuffer
-	) {
+	decodeGeometry: function ( buffer, taskConfig ) {
 
 
-		if ( attribute.ptr === 0 ) {
+		var worker;
+		var taskID = this.workerNextTaskID ++;
+		var taskCost = buffer.byteLength;
 
 
-			var errorMsg = "DRACOLoader: No attribute " + attributeName;
-			console.error( errorMsg );
-			throw new Error( errorMsg );
+		// TODO: For backward-compatibility, support 'attributeTypes' objects containing
+		// references (rather than names) to typed array constructors. These must be
+		// serialized before sending them to the worker.
+		for ( var attribute in taskConfig.attributeTypes ) {
+
+			var type = taskConfig.attributeTypes[ attribute ];
+
+			if ( type.BYTES_PER_ELEMENT !== undefined ) {
+
+				taskConfig.attributeTypes[ attribute ] = type.name;
+
+			}
 
 
 		}
 		}
 
 
-		var numComponents = attribute.num_components();
-		var numPoints = dracoGeometry.num_points();
-		var numValues = numPoints * numComponents;
-		var attributeData;
-		var TypedBufferAttribute;
+		// Obtain a worker and assign a task, and construct a geometry instance
+		// when the task completes.
+		var geometryPending = this._getWorker( taskID, taskCost )
+			.then( ( _worker ) => {
 
 
-		switch ( attributeType ) {
+				worker = _worker;
 
 
-			case Float32Array:
-				attributeData = new dracoDecoder.DracoFloat32Array();
-				decoder.GetAttributeFloatForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Float32Array( numValues );
-				TypedBufferAttribute = Float32BufferAttribute;
-				break;
+				return new Promise( ( resolve, reject ) => {
 
 
-			case Int8Array:
-				attributeData = new dracoDecoder.DracoInt8Array();
-				decoder.GetAttributeInt8ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Int8Array( numValues );
-				TypedBufferAttribute = Int8BufferAttribute;
-				break;
+					worker._callbacks[ taskID ] = { resolve, reject };
 
 
-			case Int16Array:
-				attributeData = new dracoDecoder.DracoInt16Array();
-				decoder.GetAttributeInt16ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Int16Array( numValues );
-				TypedBufferAttribute = Int16BufferAttribute;
-				break;
+					worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] );
 
 
-			case Int32Array:
-				attributeData = new dracoDecoder.DracoInt32Array();
-				decoder.GetAttributeInt32ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Int32Array( numValues );
-				TypedBufferAttribute = Int32BufferAttribute;
-				break;
+					// this.debug();
 
 
-			case Uint8Array:
-				attributeData = new dracoDecoder.DracoUInt8Array();
-				decoder.GetAttributeUInt8ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Uint8Array( numValues );
-				TypedBufferAttribute = Uint8BufferAttribute;
-				break;
+				} );
 
 
-			case Uint16Array:
-				attributeData = new dracoDecoder.DracoUInt16Array();
-				decoder.GetAttributeUInt16ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Uint16Array( numValues );
-				TypedBufferAttribute = Uint16BufferAttribute;
-				break;
+			} )
+			.then( ( message ) => this._createGeometry( message.geometry ) );
 
 
-			case Uint32Array:
-				attributeData = new dracoDecoder.DracoUInt32Array();
-				decoder.GetAttributeUInt32ForAllPoints(
-					dracoGeometry,
-					attribute,
-					attributeData
-				);
-				geometryBuffer[ attributeName ] = new Uint32Array( numValues );
-				TypedBufferAttribute = Uint32BufferAttribute;
-				break;
+		// Remove task from the task list.
+		geometryPending
+			.finally( () => {
 
 
-			default:
-				var errorMsg = "DRACOLoader: Unexpected attribute type.";
-				console.error( errorMsg );
-				throw new Error( errorMsg );
+				if ( worker && taskID ) {
 
 
-		}
+					this._releaseTask( worker, taskID );
 
 
-		// Copy data from decoder.
-		for ( var i = 0; i < numValues; i ++ ) {
+					// this.debug();
 
 
-			geometryBuffer[ attributeName ][ i ] = attributeData.GetValue( i );
+				}
 
 
-		}
-		// Add attribute to THREEJS geometry for rendering.
-		geometry.addAttribute(
-			attributeName,
-			new TypedBufferAttribute( geometryBuffer[ attributeName ], numComponents )
-		);
-		dracoDecoder.destroy( attributeData );
+			} );
+
+		return geometryPending;
 
 
 	},
 	},
 
 
-	convertDracoGeometryTo3JS: function (
-		dracoDecoder,
-		decoder,
-		geometryType,
-		buffer,
-		attributeUniqueIdMap,
-		attributeTypeMap
-	) {
+	_createGeometry: function ( geometryData ) {
+
+		var geometry = new BufferGeometry();
 
 
-		// TODO: Should not assume native Draco attribute IDs apply.
-		if ( this.getAttributeOptions( "position" ).skipDequantization === true ) {
+		if ( geometryData.index ) {
 
 
-			decoder.SkipAttributeTransform( dracoDecoder.POSITION );
+			geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) );
 
 
 		}
 		}
-		var dracoGeometry;
-		var decodingStatus;
-		var start_time = performance.now();
-		if ( geometryType === dracoDecoder.TRIANGULAR_MESH ) {
 
 
-			dracoGeometry = new dracoDecoder.Mesh();
-			decodingStatus = decoder.DecodeBufferToMesh( buffer, dracoGeometry );
+		for ( var i = 0; i < geometryData.attributes.length; i++ ) {
 
 
-		} else {
+			var attribute = geometryData.attributes[ i ];
+			var name = attribute.name;
+			var array = attribute.array;
+			var itemSize = attribute.itemSize;
 
 
-			dracoGeometry = new dracoDecoder.PointCloud();
-			decodingStatus = decoder.DecodeBufferToPointCloud( buffer, dracoGeometry );
+			geometry.addAttribute( name, new BufferAttribute( array, itemSize ) );
 
 
 		}
 		}
-		if ( ! decodingStatus.ok() || dracoGeometry.ptr == 0 ) {
 
 
-			var errorMsg = "DRACOLoader: Decoding failed: ";
-			errorMsg += decodingStatus.error_msg();
-			console.error( errorMsg );
-			dracoDecoder.destroy( decoder );
-			dracoDecoder.destroy( dracoGeometry );
-			throw new Error( errorMsg );
+		return geometry;
 
 
-		}
+	},
 
 
-		var decode_end = performance.now();
-		dracoDecoder.destroy( buffer );
-		/*
-		 * Example on how to retrieve mesh and attributes.
-		 */
-		var numFaces;
-		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+	_loadLibrary: function ( url, responseType ) {
 
 
-			numFaces = dracoGeometry.num_faces();
-			if ( this.verbosity > 0 ) {
+		var loader = new FileLoader( this.manager );
+		loader.setPath( this.decoderPath );
+		loader.setResponseType( responseType );
 
 
-				console.log( "Number of faces loaded: " + numFaces.toString() );
+		return new Promise( ( resolve, reject ) => {
 
 
-			}
+			loader.load( url, resolve, undefined, reject );
+
+		} );
+
+	},
+
+	_initDecoder: function () {
+
+		if ( this.decoderPending ) return this.decoderPending;
+
+		var useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
+		var librariesPending = [];
+
+		if ( useJS ) {
+
+			librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) );
 
 
 		} else {
 		} else {
 
 
-			numFaces = 0;
+			librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) );
+			librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) );
 
 
 		}
 		}
 
 
-		var numPoints = dracoGeometry.num_points();
-		var numAttributes = dracoGeometry.num_attributes();
-		if ( this.verbosity > 0 ) {
+		this.decoderPending = Promise.all( librariesPending )
+			.then( ( libraries ) => {
 
 
-			console.log( "Number of points loaded: " + numPoints.toString() );
-			console.log( "Number of attributes loaded: " + numAttributes.toString() );
+				var jsContent = libraries[ 0 ];
 
 
-		}
+				if ( ! useJS ) {
 
 
-		// Verify if there is position attribute.
-		// TODO: Should not assume native Draco attribute IDs apply.
-		var posAttId = decoder.GetAttributeId( dracoGeometry, dracoDecoder.POSITION );
-		if ( posAttId == - 1 ) {
+					this.decoderConfig.wasmBinary = libraries[ 1 ];
 
 
-			var errorMsg = "DRACOLoader: No position attribute found.";
-			console.error( errorMsg );
-			dracoDecoder.destroy( decoder );
-			dracoDecoder.destroy( dracoGeometry );
-			throw new Error( errorMsg );
+				}
 
 
-		}
-		var posAttribute = decoder.GetAttribute( dracoGeometry, posAttId );
+				var fn = DRACOLoader.DRACOWorker.toString();
 
 
-		// Structure for converting to THREEJS geometry later.
-		var geometryBuffer = {};
-		// Import data to Three JS geometry.
-		var geometry = new BufferGeometry();
+				var body = [
+					'/* draco decoder */',
+					jsContent,
+					'',
+					'/* worker */',
+					fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
+				].join( '\n' );
 
 
-		// Do not use both the native attribute map and a provided (e.g. glTF) map.
-		if ( attributeUniqueIdMap ) {
-
-			// Add attributes of user specified unique id. E.g. GLTF models.
-			for ( var attributeName in attributeUniqueIdMap ) {
-
-				var attributeType = attributeTypeMap[ attributeName ];
-				var attributeId = attributeUniqueIdMap[ attributeName ];
-				var attribute = decoder.GetAttributeByUniqueId(
-					dracoGeometry,
-					attributeId
-				);
-				this.addAttributeToGeometry(
-					dracoDecoder,
-					decoder,
-					dracoGeometry,
-					attributeName,
-					attributeType,
-					attribute,
-					geometry,
-					geometryBuffer
-				);
+				this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
 
 
-			}
+			} );
 
 
-		} else {
+		return this.decoderPending;
 
 
-			// Add native Draco attribute type to geometry.
-			for ( var attributeName in this.nativeAttributeMap ) {
+	},
 
 
-				var attId = decoder.GetAttributeId(
-					dracoGeometry,
-					dracoDecoder[ this.nativeAttributeMap[ attributeName ] ]
-				);
-				if ( attId !== - 1 ) {
+	_getWorker: function ( taskID, taskCost ) {
 
 
-					if ( this.verbosity > 0 ) {
+		return this._initDecoder().then( () => {
 
 
-						console.log( "Loaded " + attributeName + " attribute." );
+			if ( this.workerPool.length < this.workerLimit ) {
 
 
-					}
-					var attribute = decoder.GetAttribute( dracoGeometry, attId );
-					this.addAttributeToGeometry(
-						dracoDecoder,
-						decoder,
-						dracoGeometry,
-						attributeName,
-						Float32Array,
-						attribute,
-						geometry,
-						geometryBuffer
-					);
+				var worker = new Worker( this.workerSourceURL );
 
 
-				}
+				worker._callbacks = {};
+				worker._taskCosts = {};
+				worker._taskLoad = 0;
 
 
-			}
+				worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } );
 
 
-		}
+				worker.onmessage = function ( e ) {
 
 
-		// For mesh, we need to generate the faces.
-		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+					var message = e.data;
 
 
-			if ( this.drawMode === TriangleStripDrawMode ) {
+					switch ( message.type ) {
 
 
-				var stripsArray = new dracoDecoder.DracoInt32Array();
-				decoder.GetTriangleStripsFromMesh(
-					dracoGeometry,
-					stripsArray
-				);
-				geometryBuffer.indices = new Uint32Array( stripsArray.size() );
-				for ( var i = 0; i < stripsArray.size(); ++ i ) {
+						case 'decode':
+							worker._callbacks[ message.id ].resolve( message );
+							break;
 
 
-					geometryBuffer.indices[ i ] = stripsArray.GetValue( i );
+						case 'error':
+							worker._callbacks[ message.id ].reject( message );
+							break;
 
 
-				}
-				dracoDecoder.destroy( stripsArray );
+						default:
+							console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' );
+
+					}
+
+				};
+
+				this.workerPool.push( worker );
 
 
 			} else {
 			} else {
 
 
-				var numIndices = numFaces * 3;
-				geometryBuffer.indices = new Uint32Array( numIndices );
-				var ia = new dracoDecoder.DracoInt32Array();
-				for ( var i = 0; i < numFaces; ++ i ) {
+				this.workerPool.sort( function ( a, b ) {
 
 
-					decoder.GetFaceFromMesh( dracoGeometry, i, ia );
-					var index = i * 3;
-					geometryBuffer.indices[ index ] = ia.GetValue( 0 );
-					geometryBuffer.indices[ index + 1 ] = ia.GetValue( 1 );
-					geometryBuffer.indices[ index + 2 ] = ia.GetValue( 2 );
+					return a._taskLoad > b._taskLoad ? - 1 : 1;
 
 
-				}
-				dracoDecoder.destroy( ia );
+				} );
 
 
 			}
 			}
 
 
-		}
+			var worker = this.workerPool[ this.workerPool.length - 1 ];
+			worker._taskCosts[ taskID ] = taskCost;
+			worker._taskLoad += taskCost;
+			return worker;
+
+		} );
+
+	},
+
+	_releaseTask: function ( worker, taskID ) {
+
+		worker._taskLoad -= worker._taskCosts[ taskID ];
+		delete worker._callbacks[ taskID ];
+		delete worker._taskCosts[ taskID ];
+
+	},
+
+	debug: function () {
 
 
-		geometry.drawMode = this.drawMode;
-		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+		console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) );
 
 
-			geometry.setIndex(
-				new ( geometryBuffer.indices.length > 65535
-					? Uint32BufferAttribute
-					: Uint16BufferAttribute )( geometryBuffer.indices, 1 )
-			);
+	},
+
+	dispose: function () {
+
+		for ( var i = 0; i < this.workerPool.length; ++ i ) {
+
+			this.workerPool[ i ].terminate();
 
 
 		}
 		}
 
 
-		// TODO: Should not assume native Draco attribute IDs apply.
-		// TODO: Can other attribute types be quantized?
-		var posTransform = new dracoDecoder.AttributeQuantizationTransform();
-		if ( posTransform.InitFromAttribute( posAttribute ) ) {
-
-			// Quantized attribute. Store the quantization parameters into the
-			// js attribute.
-			geometry.attributes[ "position" ].isQuantized = true;
-			geometry.attributes[ "position" ].maxRange = posTransform.range();
-			geometry.attributes[
-				"position"
-			].numQuantizationBits = posTransform.quantization_bits();
-			geometry.attributes[ "position" ].minValues = new Float32Array( 3 );
-			for ( var i = 0; i < 3; ++ i ) {
-
-				geometry.attributes[ "position" ].minValues[ i ] = posTransform.min_value(
-					i
-				);
+		this.workerPool.length = 0;
 
 
-			}
+		return this;
+
+	}
+};
+
+/* WEB WORKER */
+
+DRACOLoader.DRACOWorker = function () {
+
+	var decoderConfig;
+	var decoderPending;
+
+	onmessage = function ( e ) {
+
+		var message = e.data;
+
+		switch ( message.type ) {
+
+			case 'init':
+				decoderConfig = message.decoderConfig;
+				decoderPending = new Promise( function ( resolve, reject ) {
+
+					decoderConfig.onModuleLoaded = function ( draco ) {
+
+						// Module is Promise-like. Wrap before resolving to avoid loop.
+						resolve( { draco: draco } );
+
+					};
+
+					DracoDecoderModule( decoderConfig );
+
+				} );
+				break;
+
+			case 'decode':
+				var buffer = message.buffer;
+				var taskConfig = message.taskConfig;
+				decoderPending.then( ( module ) => {
+
+					var draco = module.draco;
+					var decoder = new draco.Decoder();
+					var decoderBuffer = new draco.DecoderBuffer();
+					decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength );
+
+					try {
+
+						var geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig );
+
+						var buffers = geometry.attributes.map( ( attr ) => attr.array.buffer );
+
+						if ( geometry.index ) buffers.push( geometry.index.array.buffer );
+
+						self.postMessage( { type: 'decode', id: message.id, geometry }, buffers );
+
+					} catch ( error ) {
+
+						console.error( error );
+
+						self.postMessage( { type: 'error', id: message.id, error: error.message } );
+
+					} finally {
+
+						draco.destroy( decoderBuffer );
+						draco.destroy( decoder );
+
+					}
+
+				} );
+				break;
 
 
 		}
 		}
-		dracoDecoder.destroy( posTransform );
-		dracoDecoder.destroy( decoder );
-		dracoDecoder.destroy( dracoGeometry );
 
 
-		this.decode_time = decode_end - start_time;
-		this.import_time = performance.now() - decode_end;
+	};
+
+	function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) {
+
+		var attributeIDs = taskConfig.attributeIDs;
+		var attributeTypes = taskConfig.attributeTypes;
+
+		var dracoGeometry;
+		var decodingStatus;
 
 
-		if ( this.verbosity > 0 ) {
+		var geometryType = decoder.GetEncodedGeometryType( decoderBuffer );
 
 
-			console.log( "Decode time: " + this.decode_time );
-			console.log( "Import time: " + this.import_time );
+		if ( geometryType === draco.TRIANGULAR_MESH ) {
+
+			dracoGeometry = new draco.Mesh();
+			decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry );
+
+		} else if ( geometryType === draco.POINT_CLOUD ) {
+
+			dracoGeometry = new draco.PointCloud();
+			decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry );
+
+		} else {
+
+			throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' );
 
 
 		}
 		}
-		return geometry;
 
 
-	},
+		if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) {
 
 
-	isVersionSupported: function ( version, callback ) {
+			throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() );
 
 
-		DRACOLoader.getDecoderModule().then( function ( module ) {
+		}
 
 
-			callback( module.decoder.isVersionSupported( version ) );
+		var geometry = { index: null, attributes: [] };
 
 
-		} );
+		var numPoints = dracoGeometry.num_points();
+		var numAttributes = dracoGeometry.num_attributes();
 
 
-	},
+		// Add attributes of user specified unique id.
+		for (var attributeName in attributeIDs) {
 
 
-	getAttributeOptions: function ( attributeName ) {
+			var attributeType = self[ attributeTypes[ attributeName ] ];
+			var attributeId = attributeIDs[ attributeName ];
+			var attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeId );
 
 
-		if ( typeof this.attributeOptions[ attributeName ] === "undefined" )
-			this.attributeOptions[ attributeName ] = {};
-		return this.attributeOptions[ attributeName ];
+			geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) );
 
 
-	}
-};
+		}
 
 
-DRACOLoader.decoderPath = "./";
-DRACOLoader.decoderConfig = {};
-DRACOLoader.decoderModulePromise = null;
+		// Add index.
+		if ( geometryType === draco.TRIANGULAR_MESH ) {
 
 
-/**
- * Sets the base path for decoder source files.
- * @param {string} path
- */
-DRACOLoader.setDecoderPath = function ( path ) {
+			// Generate mesh faces.
+			var numFaces = dracoGeometry.num_faces();
+			var numIndices = numFaces * 3;
+			var index = new Uint32Array( numIndices );
+			var indexArray = new draco.DracoInt32Array();
 
 
-	DRACOLoader.decoderPath = path;
+			for ( var i = 0; i < numFaces; ++ i ) {
 
 
-};
+				decoder.GetFaceFromMesh( dracoGeometry, i, indexArray );
 
 
-/**
- * Sets decoder configuration and releases singleton decoder module. Module
- * will be recreated with the next decoding call.
- * @param {Object} config
- */
-DRACOLoader.setDecoderConfig = function ( config ) {
+				for ( var j = 0; j < 3; ++ j ) {
 
 
-	var wasmBinary = DRACOLoader.decoderConfig.wasmBinary;
-	DRACOLoader.decoderConfig = config || {};
-	DRACOLoader.releaseDecoderModule();
+					index[ i * 3 + j ] = indexArray.GetValue( j );
 
 
-	// Reuse WASM binary.
-	if ( wasmBinary ) DRACOLoader.decoderConfig.wasmBinary = wasmBinary;
+				}
 
 
-};
+			}
 
 
-/**
- * Releases the singleton DracoDecoderModule instance. Module will be recreated
- * with the next decoding call.
- */
-DRACOLoader.releaseDecoderModule = function () {
+			geometry.index = { array: index, itemSize: 1 };
 
 
-	DRACOLoader.decoderModulePromise = null;
+			draco.destroy( indexArray );
 
 
-};
+		}
 
 
-/**
- * Gets WebAssembly or asm.js singleton instance of DracoDecoderModule
- * after testing for browser support. Returns Promise that resolves when
- * module is available.
- * @return {Promise<{decoder: DracoDecoderModule}>}
- */
-DRACOLoader.getDecoderModule = function () {
+		draco.destroy( dracoGeometry );
 
 
-	var scope = this;
-	var path = DRACOLoader.decoderPath;
-	var config = DRACOLoader.decoderConfig;
-	var promise = DRACOLoader.decoderModulePromise;
+		return geometry;
 
 
-	if ( promise ) return promise;
+	};
 
 
-	// Load source files.
-	if ( typeof DracoDecoderModule !== "undefined" ) {
+	function decodeAttribute ( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) {
 
 
-		// Loaded externally.
-		promise = Promise.resolve();
+		var numComponents = attribute.num_components();
+		var numPoints = dracoGeometry.num_points();
+		var numValues = numPoints * numComponents;
+		var dracoArray;
 
 
-	} else if ( typeof WebAssembly !== "object" || config.type === "js" ) {
+		var array;
 
 
-		// Load with asm.js.
-		promise = DRACOLoader._loadScript( path + "draco_decoder.js" );
+		switch ( attributeType ) {
 
 
-	} else {
+			case Float32Array:
+				dracoArray = new draco.DracoFloat32Array();
+				decoder.GetAttributeFloatForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Float32Array( numValues );
+				break;
 
 
-		// Load with WebAssembly.
-		config.wasmBinaryFile = path + "draco_decoder.wasm";
-		promise = DRACOLoader._loadScript( path + "draco_wasm_wrapper.js" )
-			.then( function () {
+			case Int8Array:
+				dracoArray = new draco.DracoInt8Array();
+				decoder.GetAttributeInt8ForAllPoints( dracoGeometry, attribute, dracoArray  );
+				array = new Int8Array( numValues );
+				break;
 
 
-				return DRACOLoader._loadArrayBuffer( config.wasmBinaryFile );
+			case Int16Array:
+				dracoArray = new draco.DracoInt16Array();
+				decoder.GetAttributeInt16ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Int16Array( numValues );
+				break;
 
 
-			} )
-			.then( function ( wasmBinary ) {
+			case Int32Array:
+				dracoArray = new draco.DracoInt32Array();
+				decoder.GetAttributeInt32ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Int32Array( numValues );
+				break;
 
 
-				config.wasmBinary = wasmBinary;
+			case Uint8Array:
+				dracoArray = new draco.DracoUInt8Array();
+				decoder.GetAttributeUInt8ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Uint8Array( numValues );
+				break;
 
 
-			} );
+			case Uint16Array:
+				dracoArray = new draco.DracoUInt16Array();
+				decoder.GetAttributeUInt16ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Uint16Array( numValues );
+				break;
 
 
-	}
+			case Uint32Array:
+				dracoArray = new draco.DracoUInt32Array();
+				decoder.GetAttributeUInt32ForAllPoints( dracoGeometry, attribute, dracoArray );
+				array = new Uint32Array( numValues );
+				break;
 
 
-	// Wait for source files, then create and return a decoder.
-	promise = promise.then( function () {
+			default:
+				throw new Error( 'THREE.DRACOLoader: Unexpected attribute type.' );
 
 
-		return new Promise( function ( resolve ) {
+		}
 
 
-			config.onModuleLoaded = function ( decoder ) {
+		for ( var i = 0; i < numValues; i++ ) {
 
 
-				scope.timeLoaded = performance.now();
-				// Module is Promise-like. Wrap before resolving to avoid loop.
-				resolve( { decoder: decoder } );
+			array[ i ] = dracoArray.GetValue( i );
 
 
-			};
-			DracoDecoderModule( config );
+		}
 
 
-		} );
+		draco.destroy( dracoArray );
 
 
-	} );
+		return {
+			name: attributeName,
+			array: array,
+			itemSize: numComponents
+		};
 
 
-	DRACOLoader.decoderModulePromise = promise;
-	return promise;
+	};
 
 
 };
 };
 
 
-/**
- * @param {string} src
- * @return {Promise}
- */
-DRACOLoader._loadScript = function ( src ) {
+/** Deprecated static methods */
 
 
-	var prevScript = document.getElementById( "decoder_script" );
-	if ( prevScript !== null ) {
+/** @deprecated */
+DRACOLoader.setDecoderPath = function () {
 
 
-		prevScript.parentNode.removeChild( prevScript );
+	console.warn( 'THREE.DRACOLoader: The .setDecoderPath() method has been removed. Use instance methods.' );
 
 
-	}
-	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 );
+/** @deprecated */
+DRACOLoader.setDecoderConfig = function () {
 
 
-	} );
+	console.warn( 'THREE.DRACOLoader: The .setDecoderConfig() method has been removed. Use instance methods.' );
 
 
 };
 };
 
 
-/**
- * @param {string} src
- * @return {Promise}
- */
-DRACOLoader._loadArrayBuffer = function ( src ) {
+/** @deprecated */
+DRACOLoader.releaseDecoderModule = function () {
 
 
-	var loader = new FileLoader();
-	loader.setResponseType( "arraybuffer" );
-	return new Promise( function ( resolve, reject ) {
+	console.warn( 'THREE.DRACOLoader: The .releaseDecoderModule() method has been removed. Use instance methods.' );
 
 
-		loader.load( src, resolve, undefined, reject );
+};
+
+/** @deprecated */
+DRACOLoader.getDecoderModule = function () {
 
 
-	} );
+	console.warn( 'THREE.DRACOLoader: The .getDecoderModule() method has been removed. Use instance methods.' );
 
 
 };
 };
 
 

+ 3 - 2
examples/webgl_animation_keyframes.html

@@ -78,10 +78,11 @@
 				path + 'posz' + format, path + 'negz' + format
 				path + 'posz' + format, path + 'negz' + format
 			] );
 			] );
 
 
-			DRACOLoader.setDecoderPath( 'js/libs/draco/gltf/' );
+			var dracoLoader = new DRACOLoader();
+			dracoLoader.setDecoderPath( 'js/libs/draco/gltf/' );
 
 
 			var loader = new GLTFLoader();
 			var loader = new GLTFLoader();
-			loader.setDRACOLoader( new DRACOLoader() );
+			loader.setDRACOLoader( dracoLoader );
 			loader.load( 'models/gltf/LittlestTokyo.glb', function ( gltf ) {
 			loader.load( 'models/gltf/LittlestTokyo.glb', function ( gltf ) {
 
 
 				var model = gltf.scene;
 				var model = gltf.scene;

+ 4 - 3
examples/webgl_loader_draco.html

@@ -24,9 +24,10 @@
 		var container = document.querySelector( '#container' );
 		var container = document.querySelector( '#container' );
 
 
 		// Configure and create Draco decoder.
 		// Configure and create Draco decoder.
-		DRACOLoader.setDecoderPath( 'js/libs/draco/' );
-		DRACOLoader.setDecoderConfig( { type: 'js' } );
 		var dracoLoader = new DRACOLoader();
 		var dracoLoader = new DRACOLoader();
+		dracoLoader.setDecoderPath( 'js/libs/draco/' );
+		dracoLoader.setDecoderConfig( { type: 'js' } );
+
 		init();
 		init();
 		animate();
 		animate();
 
 
@@ -71,7 +72,7 @@
 				scene.add( mesh );
 				scene.add( mesh );
 
 
 				// Release decoder resources.
 				// Release decoder resources.
-				DRACOLoader.releaseDecoderModule();
+				dracoLoader.dispose();
 
 
 			} );
 			} );
 
 

+ 4 - 2
examples/webgl_loader_gltf_extensions.html

@@ -267,8 +267,10 @@
 
 
 				loader = new GLTFLoader();
 				loader = new GLTFLoader();
 
 
-				DRACOLoader.setDecoderPath( 'js/libs/draco/gltf/' );
-				loader.setDRACOLoader( new DRACOLoader() );
+				var dracoLoader = new DRACOLoader();
+				dracoLoader.setDecoderPath( 'js/libs/draco/gltf/' )
+				loader.setDRACOLoader( dracoLoader );
+
 				loader.setDDSLoader( new DDSLoader() );
 				loader.setDDSLoader( new DDSLoader() );
 
 
 				var url = sceneInfo.url.replace( /%s/g, state.extension );
 				var url = sceneInfo.url.replace( /%s/g, state.extension );