614 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			614 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
									BufferAttribute,
							 | 
						||
| 
								 | 
							
									BufferGeometry,
							 | 
						||
| 
								 | 
							
									Color,
							 | 
						||
| 
								 | 
							
									FileLoader,
							 | 
						||
| 
								 | 
							
									Loader,
							 | 
						||
| 
								 | 
							
									LinearSRGBColorSpace,
							 | 
						||
| 
								 | 
							
									SRGBColorSpace
							 | 
						||
| 
								 | 
							
								} from 'three';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const _taskCache = new WeakMap();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class DRACOLoader extends Loader {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									constructor( manager ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										super( manager );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										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'
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									setDecoderPath( path ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.decoderPath = path;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									setDecoderConfig( config ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.decoderConfig = config;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									setWorkerLimit( workerLimit ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.workerLimit = workerLimit;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									load( url, onLoad, onProgress, onError ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const loader = new FileLoader( this.manager );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										loader.setPath( this.path );
							 | 
						||
| 
								 | 
							
										loader.setResponseType( 'arraybuffer' );
							 | 
						||
| 
								 | 
							
										loader.setRequestHeader( this.requestHeader );
							 | 
						||
| 
								 | 
							
										loader.setWithCredentials( this.withCredentials );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										loader.load( url, ( buffer ) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											this.parse( buffer, onLoad, onError );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}, onProgress, onError );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parse( buffer, onLoad, onError = ()=>{} ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace ).catch( onError );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace, onError = () => {} ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const taskConfig = {
							 | 
						||
| 
								 | 
							
											attributeIDs: attributeIDs || this.defaultAttributeIDs,
							 | 
						||
| 
								 | 
							
											attributeTypes: attributeTypes || this.defaultAttributeTypes,
							 | 
						||
| 
								 | 
							
											useUniqueIDs: !! attributeIDs,
							 | 
						||
| 
								 | 
							
											vertexColorSpace: vertexColorSpace,
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.decodeGeometry( buffer, taskConfig ).then( callback ).catch( onError );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									decodeGeometry( buffer, taskConfig ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const taskKey = JSON.stringify( taskConfig );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Check for an existing task using this buffer. A transferred buffer cannot be transferred
							 | 
						||
| 
								 | 
							
										// again from this thread.
							 | 
						||
| 
								 | 
							
										if ( _taskCache.has( buffer ) ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const cachedTask = _taskCache.get( buffer );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( cachedTask.key === taskKey ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												return cachedTask.promise;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} else if ( buffer.byteLength === 0 ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												// Technically, it would be possible to wait for the previous task to complete,
							 | 
						||
| 
								 | 
							
												// transfer the buffer back, and decode again with the second configuration. That
							 | 
						||
| 
								 | 
							
												// is complex, and I don't know of any reason to decode a Draco buffer twice in
							 | 
						||
| 
								 | 
							
												// different ways, so this is left unimplemented.
							 | 
						||
| 
								 | 
							
												throw new Error(
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													'THREE.DRACOLoader: Unable to re-decode a buffer with different ' +
							 | 
						||
| 
								 | 
							
													'settings. Buffer has already been transferred.'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										//
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let worker;
							 | 
						||
| 
								 | 
							
										const taskID = this.workerNextTaskID ++;
							 | 
						||
| 
								 | 
							
										const taskCost = buffer.byteLength;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Obtain a worker and assign a task, and construct a geometry instance
							 | 
						||
| 
								 | 
							
										// when the task completes.
							 | 
						||
| 
								 | 
							
										const geometryPending = this._getWorker( taskID, taskCost )
							 | 
						||
| 
								 | 
							
											.then( ( _worker ) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												worker = _worker;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												return new Promise( ( resolve, reject ) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													worker._callbacks[ taskID ] = { resolve, reject };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													// this.debug();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} )
							 | 
						||
| 
								 | 
							
											.then( ( message ) => this._createGeometry( message.geometry ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Remove task from the task list.
							 | 
						||
| 
								 | 
							
										// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
							 | 
						||
| 
								 | 
							
										geometryPending
							 | 
						||
| 
								 | 
							
											.catch( () => true )
							 | 
						||
| 
								 | 
							
											.then( () => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( worker && taskID ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													this._releaseTask( worker, taskID );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													// this.debug();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Cache the task result.
							 | 
						||
| 
								 | 
							
										_taskCache.set( buffer, {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											key: taskKey,
							 | 
						||
| 
								 | 
							
											promise: geometryPending
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return geometryPending;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									_createGeometry( geometryData ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const geometry = new BufferGeometry();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( geometryData.index ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										for ( let i = 0; i < geometryData.attributes.length; i ++ ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const result = geometryData.attributes[ i ];
							 | 
						||
| 
								 | 
							
											const name = result.name;
							 | 
						||
| 
								 | 
							
											const array = result.array;
							 | 
						||
| 
								 | 
							
											const itemSize = result.itemSize;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const attribute = new BufferAttribute( array, itemSize );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( name === 'color' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this._assignVertexColorSpace( attribute, result.vertexColorSpace );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												attribute.normalized = ( array instanceof Float32Array ) === false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											geometry.setAttribute( name, attribute );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return geometry;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									_assignVertexColorSpace( attribute, inputColorSpace ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// While .drc files do not specify colorspace, the only 'official' tooling
							 | 
						||
| 
								 | 
							
										// is PLY and OBJ converters, which use sRGB. We'll assume sRGB when a .drc
							 | 
						||
| 
								 | 
							
										// file is passed into .load() or .parse(). GLTFLoader uses internal APIs
							 | 
						||
| 
								 | 
							
										// to decode geometry, and vertex colors are already Linear-sRGB in there.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( inputColorSpace !== SRGBColorSpace ) return;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const _color = new Color();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										for ( let i = 0, il = attribute.count; i < il; i ++ ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											_color.fromBufferAttribute( attribute, i ).convertSRGBToLinear();
							 | 
						||
| 
								 | 
							
											attribute.setXYZ( i, _color.r, _color.g, _color.b );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									_loadLibrary( url, responseType ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const loader = new FileLoader( this.manager );
							 | 
						||
| 
								 | 
							
										loader.setPath( this.decoderPath );
							 | 
						||
| 
								 | 
							
										loader.setResponseType( responseType );
							 | 
						||
| 
								 | 
							
										loader.setWithCredentials( this.withCredentials );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return new Promise( ( resolve, reject ) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											loader.load( url, resolve, undefined, reject );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									preload() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this._initDecoder();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									_initDecoder() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( this.decoderPending ) return this.decoderPending;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
							 | 
						||
| 
								 | 
							
										const librariesPending = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( useJS ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) );
							 | 
						||
| 
								 | 
							
											librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.decoderPending = Promise.all( librariesPending )
							 | 
						||
| 
								 | 
							
											.then( ( libraries ) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												const jsContent = libraries[ 0 ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( ! useJS ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													this.decoderConfig.wasmBinary = libraries[ 1 ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												const fn = DRACOWorker.toString();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												const body = [
							 | 
						||
| 
								 | 
							
													'/* draco decoder */',
							 | 
						||
| 
								 | 
							
													jsContent,
							 | 
						||
| 
								 | 
							
													'',
							 | 
						||
| 
								 | 
							
													'/* worker */',
							 | 
						||
| 
								 | 
							
													fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
							 | 
						||
| 
								 | 
							
												].join( '\n' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.decoderPending;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									_getWorker( taskID, taskCost ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this._initDecoder().then( () => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( this.workerPool.length < this.workerLimit ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												const worker = new Worker( this.workerSourceURL );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												worker._callbacks = {};
							 | 
						||
| 
								 | 
							
												worker._taskCosts = {};
							 | 
						||
| 
								 | 
							
												worker._taskLoad = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												worker.onmessage = function ( e ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													const message = e.data;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													switch ( message.type ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														case 'decode':
							 | 
						||
| 
								 | 
							
															worker._callbacks[ message.id ].resolve( message );
							 | 
						||
| 
								 | 
							
															break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														case 'error':
							 | 
						||
| 
								 | 
							
															worker._callbacks[ message.id ].reject( message );
							 | 
						||
| 
								 | 
							
															break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														default:
							 | 
						||
| 
								 | 
							
															console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.workerPool.push( worker );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.workerPool.sort( function ( a, b ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													return a._taskLoad > b._taskLoad ? - 1 : 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const worker = this.workerPool[ this.workerPool.length - 1 ];
							 | 
						||
| 
								 | 
							
											worker._taskCosts[ taskID ] = taskCost;
							 | 
						||
| 
								 | 
							
											worker._taskLoad += taskCost;
							 | 
						||
| 
								 | 
							
											return worker;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									_releaseTask( worker, taskID ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										worker._taskLoad -= worker._taskCosts[ taskID ];
							 | 
						||
| 
								 | 
							
										delete worker._callbacks[ taskID ];
							 | 
						||
| 
								 | 
							
										delete worker._taskCosts[ taskID ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									debug() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									dispose() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										for ( let i = 0; i < this.workerPool.length; ++ i ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											this.workerPool[ i ].terminate();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.workerPool.length = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( this.workerSourceURL !== '' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											URL.revokeObjectURL( this.workerSourceURL );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/* WEB WORKER */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function DRACOWorker() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									let decoderConfig;
							 | 
						||
| 
								 | 
							
									let decoderPending;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									onmessage = function ( e ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const 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 ); // eslint-disable-line no-undef
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} );
							 | 
						||
| 
								 | 
							
												break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											case 'decode':
							 | 
						||
| 
								 | 
							
												const buffer = message.buffer;
							 | 
						||
| 
								 | 
							
												const taskConfig = message.taskConfig;
							 | 
						||
| 
								 | 
							
												decoderPending.then( ( module ) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													const draco = module.draco;
							 | 
						||
| 
								 | 
							
													const decoder = new draco.Decoder();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													try {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														const geometry = decodeGeometry( draco, decoder, new Int8Array( buffer ), taskConfig );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														const 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( decoder );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} );
							 | 
						||
| 
								 | 
							
												break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function decodeGeometry( draco, decoder, array, taskConfig ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const attributeIDs = taskConfig.attributeIDs;
							 | 
						||
| 
								 | 
							
										const attributeTypes = taskConfig.attributeTypes;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let dracoGeometry;
							 | 
						||
| 
								 | 
							
										let decodingStatus;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const geometryType = decoder.GetEncodedGeometryType( array );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( geometryType === draco.TRIANGULAR_MESH ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											dracoGeometry = new draco.Mesh();
							 | 
						||
| 
								 | 
							
											decodingStatus = decoder.DecodeArrayToMesh( array, array.byteLength, dracoGeometry );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} else if ( geometryType === draco.POINT_CLOUD ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											dracoGeometry = new draco.PointCloud();
							 | 
						||
| 
								 | 
							
											decodingStatus = decoder.DecodeArrayToPointCloud( array, array.byteLength, dracoGeometry );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const geometry = { index: null, attributes: [] };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Gather all vertex attributes.
							 | 
						||
| 
								 | 
							
										for ( const attributeName in attributeIDs ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const attributeType = self[ attributeTypes[ attributeName ] ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											let attribute;
							 | 
						||
| 
								 | 
							
											let attributeID;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											// A Draco file may be created with default vertex attributes, whose attribute IDs
							 | 
						||
| 
								 | 
							
											// are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively,
							 | 
						||
| 
								 | 
							
											// a Draco file may contain a custom set of attributes, identified by known unique
							 | 
						||
| 
								 | 
							
											// IDs. glTF files always do the latter, and `.drc` files typically do the former.
							 | 
						||
| 
								 | 
							
											if ( taskConfig.useUniqueIDs ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												attributeID = attributeIDs[ attributeName ];
							 | 
						||
| 
								 | 
							
												attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( attributeID === - 1 ) continue;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												attribute = decoder.GetAttribute( dracoGeometry, attributeID );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const attributeResult = decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( attributeName === 'color' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												attributeResult.vertexColorSpace = taskConfig.vertexColorSpace;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											geometry.attributes.push( attributeResult );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Add index.
							 | 
						||
| 
								 | 
							
										if ( geometryType === draco.TRIANGULAR_MESH ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											geometry.index = decodeIndex( draco, decoder, dracoGeometry );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										draco.destroy( dracoGeometry );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return geometry;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function decodeIndex( draco, decoder, dracoGeometry ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const numFaces = dracoGeometry.num_faces();
							 | 
						||
| 
								 | 
							
										const numIndices = numFaces * 3;
							 | 
						||
| 
								 | 
							
										const byteLength = numIndices * 4;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const ptr = draco._malloc( byteLength );
							 | 
						||
| 
								 | 
							
										decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr );
							 | 
						||
| 
								 | 
							
										const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice();
							 | 
						||
| 
								 | 
							
										draco._free( ptr );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return { array: index, itemSize: 1 };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const numComponents = attribute.num_components();
							 | 
						||
| 
								 | 
							
										const numPoints = dracoGeometry.num_points();
							 | 
						||
| 
								 | 
							
										const numValues = numPoints * numComponents;
							 | 
						||
| 
								 | 
							
										const byteLength = numValues * attributeType.BYTES_PER_ELEMENT;
							 | 
						||
| 
								 | 
							
										const dataType = getDracoDataType( draco, attributeType );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const ptr = draco._malloc( byteLength );
							 | 
						||
| 
								 | 
							
										decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr );
							 | 
						||
| 
								 | 
							
										const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice();
							 | 
						||
| 
								 | 
							
										draco._free( ptr );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return {
							 | 
						||
| 
								 | 
							
											name: attributeName,
							 | 
						||
| 
								 | 
							
											array: array,
							 | 
						||
| 
								 | 
							
											itemSize: numComponents
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function getDracoDataType( draco, attributeType ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										switch ( attributeType ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											case Float32Array: return draco.DT_FLOAT32;
							 | 
						||
| 
								 | 
							
											case Int8Array: return draco.DT_INT8;
							 | 
						||
| 
								 | 
							
											case Int16Array: return draco.DT_INT16;
							 | 
						||
| 
								 | 
							
											case Int32Array: return draco.DT_INT32;
							 | 
						||
| 
								 | 
							
											case Uint8Array: return draco.DT_UINT8;
							 | 
						||
| 
								 | 
							
											case Uint16Array: return draco.DT_UINT16;
							 | 
						||
| 
								 | 
							
											case Uint32Array: return draco.DT_UINT32;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export { DRACOLoader };
							 |