689 lines
16 KiB
JavaScript
689 lines
16 KiB
JavaScript
|
import {
|
|||
|
BufferAttribute,
|
|||
|
BufferGeometry,
|
|||
|
Color,
|
|||
|
ColorManagement,
|
|||
|
FileLoader,
|
|||
|
Loader,
|
|||
|
LinearSRGBColorSpace,
|
|||
|
SRGBColorSpace
|
|||
|
} from 'three';
|
|||
|
|
|||
|
const _taskCache = new WeakMap();
|
|||
|
|
|||
|
/**
|
|||
|
* A loader for the Draco format.
|
|||
|
*
|
|||
|
* [Draco]{@link https://google.github.io/draco/} is an open source library for compressing
|
|||
|
* and decompressing 3D meshes and point clouds. Compressed geometry can be significantly smaller,
|
|||
|
* at the cost of additional decoding time on the client device.
|
|||
|
*
|
|||
|
* Standalone Draco files have a `.drc` extension, and contain vertex positions, normals, colors,
|
|||
|
* and other attributes. Draco files do not contain materials, textures, animation, or node hierarchies –
|
|||
|
* to use these features, embed Draco geometry inside of a glTF file. A normal glTF file can be converted
|
|||
|
* to a Draco-compressed glTF file using [glTF-Pipeline]{@link https://github.com/CesiumGS/gltf-pipeline}.
|
|||
|
* When using Draco with glTF, an instance of `DRACOLoader` will be used internally by {@link GLTFLoader}.
|
|||
|
*
|
|||
|
* It is recommended to create one DRACOLoader instance and reuse it to avoid loading and creating
|
|||
|
* multiple decoder instances.
|
|||
|
*
|
|||
|
* `DRACOLoader` will automatically use either the JS or the WASM decoding library, based on
|
|||
|
* browser capabilities.
|
|||
|
*
|
|||
|
* ```js
|
|||
|
* const loader = new DRACOLoader();
|
|||
|
* loader.setDecoderPath( '/examples/jsm/libs/draco/' );
|
|||
|
*
|
|||
|
* const geometry = await dracoLoader.loadAsync( 'models/draco/bunny.drc' );
|
|||
|
* geometry.computeVertexNormals(); // optional
|
|||
|
*
|
|||
|
* dracoLoader.dispose();
|
|||
|
* ```
|
|||
|
*
|
|||
|
* @augments Loader
|
|||
|
* @three_import import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|||
|
*/
|
|||
|
class DRACOLoader extends Loader {
|
|||
|
|
|||
|
/**
|
|||
|
* Constructs a new Draco loader.
|
|||
|
*
|
|||
|
* @param {LoadingManager} [manager] - The loading manager.
|
|||
|
*/
|
|||
|
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'
|
|||
|
};
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Provides configuration for the decoder libraries. Configuration cannot be changed after decoding begins.
|
|||
|
*
|
|||
|
* @param {string} path - The decoder path.
|
|||
|
* @return {DRACOLoader} A reference to this loader.
|
|||
|
*/
|
|||
|
setDecoderPath( path ) {
|
|||
|
|
|||
|
this.decoderPath = path;
|
|||
|
|
|||
|
return this;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Provides configuration for the decoder libraries. Configuration cannot be changed after decoding begins.
|
|||
|
*
|
|||
|
* @param {{type:('js'|'wasm')}} config - The decoder config.
|
|||
|
* @return {DRACOLoader} A reference to this loader.
|
|||
|
*/
|
|||
|
setDecoderConfig( config ) {
|
|||
|
|
|||
|
this.decoderConfig = config;
|
|||
|
|
|||
|
return this;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets the maximum number of Web Workers to be used during decoding.
|
|||
|
* A lower limit may be preferable if workers are also for other tasks in the application.
|
|||
|
*
|
|||
|
* @param {number} workerLimit - The worker limit.
|
|||
|
* @return {DRACOLoader} A reference to this loader.
|
|||
|
*/
|
|||
|
setWorkerLimit( workerLimit ) {
|
|||
|
|
|||
|
this.workerLimit = workerLimit;
|
|||
|
|
|||
|
return this;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Starts loading from the given URL and passes the loaded Draco asset
|
|||
|
* to the `onLoad()` callback.
|
|||
|
*
|
|||
|
* @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
|
|||
|
* @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished.
|
|||
|
* @param {onProgressCallback} onProgress - Executed while the loading is in progress.
|
|||
|
* @param {onErrorCallback} onError - Executed when errors occur.
|
|||
|
*/
|
|||
|
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 );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Parses the given Draco data.
|
|||
|
*
|
|||
|
* @param {ArrayBuffer} buffer - The raw Draco data as an array buffer.
|
|||
|
* @param {function(BufferGeometry)} onLoad - Executed when the loading/parsing process has been finished.
|
|||
|
* @param {onErrorCallback} onError - Executed when errors occur.
|
|||
|
*/
|
|||
|
parse( buffer, onLoad, onError = ()=>{} ) {
|
|||
|
|
|||
|
this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace, onError ).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 );
|
|||
|
ColorManagement.toWorkingColorSpace( _color, SRGBColorSpace );
|
|||
|
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 };
|