| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 | import {	BufferAttribute,	BufferGeometry,	FileLoader,	Loader} from '../../../build/three.module.js';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 ) => {			const taskConfig = {				attributeIDs: this.defaultAttributeIDs,				attributeTypes: this.defaultAttributeTypes,				useUniqueIDs: false			};			this.decodeGeometry( buffer, taskConfig )				.then( onLoad )				.catch( onError );		}, onProgress, onError );	}	/** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */	decodeDracoFile( buffer, callback, attributeIDs, attributeTypes ) {		const taskConfig = {			attributeIDs: attributeIDs || this.defaultAttributeIDs,			attributeTypes: attributeTypes || this.defaultAttributeTypes,			useUniqueIDs: !! attributeIDs		};		this.decodeGeometry( buffer, taskConfig ).then( callback );	}	decodeGeometry( buffer, taskConfig ) {		// 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 ( const attribute in taskConfig.attributeTypes ) {			const type = taskConfig.attributeTypes[ attribute ];			if ( type.BYTES_PER_ELEMENT !== undefined ) {				taskConfig.attributeTypes[ attribute ] = type.name;			}		}		//		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 attribute = geometryData.attributes[ i ];			const name = attribute.name;			const array = attribute.array;			const itemSize = attribute.itemSize;			geometry.setAttribute( name, new BufferAttribute( array, itemSize ) );		}		return geometry;	}	_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;		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();					const decoderBuffer = new draco.DecoderBuffer();					decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength );					try {						const geometry = decodeGeometry( draco, decoder, decoderBuffer, 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( decoderBuffer );						draco.destroy( decoder );					}				} );				break;		}	};	function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) {		const attributeIDs = taskConfig.attributeIDs;		const attributeTypes = taskConfig.attributeTypes;		let dracoGeometry;		let decodingStatus;		const geometryType = decoder.GetEncodedGeometryType( decoderBuffer );		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.' );		}		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 );			}			geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) );		}		// 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 };
 |