Initial commit
This commit is contained in:
571
HTML/ThreeJS/node_modules/three/examples/js/loaders/DRACOLoader.js
generated
vendored
Normal file
571
HTML/ThreeJS/node_modules/three/examples/js/loaders/DRACOLoader.js
generated
vendored
Normal file
@@ -0,0 +1,571 @@
|
||||
( function () {
|
||||
|
||||
const _taskCache = new WeakMap();
|
||||
|
||||
class DRACOLoader extends THREE.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 THREE.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 THREE.BufferGeometry();
|
||||
|
||||
if ( geometryData.index ) {
|
||||
|
||||
geometry.setIndex( new THREE.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 THREE.BufferAttribute( array, itemSize ) );
|
||||
|
||||
}
|
||||
|
||||
return geometry;
|
||||
|
||||
}
|
||||
|
||||
_loadLibrary( url, responseType ) {
|
||||
|
||||
const loader = new THREE.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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
THREE.DRACOLoader = DRACOLoader;
|
||||
|
||||
} )();
|
||||
Reference in New Issue
Block a user