123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- import {
- BufferAttribute,
- BufferGeometry,
- FileLoader,
- Loader
- } 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.decodeDracoFile( buffer, onLoad ).catch( onError );
- }, onProgress, onError );
- }
- decodeDracoFile( buffer, callback, attributeIDs, attributeTypes ) {
- const taskConfig = {
- attributeIDs: attributeIDs || this.defaultAttributeIDs,
- attributeTypes: attributeTypes || this.defaultAttributeTypes,
- useUniqueIDs: !! attributeIDs
- };
- return this.decodeGeometry( buffer, taskConfig ).then( callback );
- }
- 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 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 };
|