|
@@ -0,0 +1,366 @@
|
|
|
|
+/**
|
|
|
|
+ * @author Don McCurdy / https://www.donmccurdy.com
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+THREE.GLTFDRACOLoader = function(manager) {
|
|
|
|
+
|
|
|
|
+ this.manager = manager || THREE.DefaultLoadingManager;
|
|
|
|
+
|
|
|
|
+ this.decoderPath = '';
|
|
|
|
+ this.decoderConfig = {};
|
|
|
|
+
|
|
|
|
+ this.workerLimit = 4;
|
|
|
|
+ this.workerPool = [];
|
|
|
|
+ this.workerNextTaskID = 1;
|
|
|
|
+ this.workerSourceURL = '';
|
|
|
|
+
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+THREE.GLTFDRACOLoader.prototype = {
|
|
|
|
+
|
|
|
|
+ constructor: THREE.GLTFDRACOLoader,
|
|
|
|
+
|
|
|
|
+ decodeDracoFile: function ( rawBuffer, callback, attributeIDs, attributeTypes ) {
|
|
|
|
+
|
|
|
|
+ var taskID = this.workerNextTaskID++;
|
|
|
|
+ var worker = this.getWorker();
|
|
|
|
+ worker._callbacks[ taskID ] = callback;
|
|
|
|
+ worker._taskCosts[ taskID ] = rawBuffer.byteLength;
|
|
|
|
+ worker._taskLoad += worker._taskCosts[ taskID ];
|
|
|
|
+ worker._taskCount++;
|
|
|
|
+
|
|
|
|
+ var attributeTypesSerialized = {};
|
|
|
|
+
|
|
|
|
+ for ( var name in attributeTypes ) {
|
|
|
|
+
|
|
|
|
+ attributeTypesSerialized[ name ] = attributeTypes[ name ].name;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ worker.postMessage( {
|
|
|
|
+ type: 'decode',
|
|
|
|
+ id: taskID,
|
|
|
|
+ rawBuffer: rawBuffer,
|
|
|
|
+ attributeIDs: attributeIDs,
|
|
|
|
+ attributeTypes: attributeTypesSerialized
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ setDecoderPath: function ( path ) {
|
|
|
|
+
|
|
|
|
+ this.decoderPath = path;
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ setDecoderConfig: function ( config ) {
|
|
|
|
+
|
|
|
|
+ this.decoderConfig = config || {};
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ setWorkerLimit: function ( count ) {
|
|
|
|
+
|
|
|
|
+ this.workerLimit = count;
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ getWorker: function () {
|
|
|
|
+
|
|
|
|
+ if ( this.workerPool.length < this.workerLimit ) {
|
|
|
|
+
|
|
|
|
+ if ( !this.workerSourceURL ) {
|
|
|
|
+
|
|
|
|
+ var fn = GLTFDRACOWorker.toString(); // this part may fail!
|
|
|
|
+ var body = fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) );
|
|
|
|
+ this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var worker = new Worker( this.workerSourceURL );
|
|
|
|
+
|
|
|
|
+ worker._callbacks = {};
|
|
|
|
+ worker._taskCosts = {};
|
|
|
|
+ worker._taskLoad = 0;
|
|
|
|
+ worker._taskCount = 0;
|
|
|
|
+
|
|
|
|
+ worker.postMessage( {
|
|
|
|
+ type: 'init',
|
|
|
|
+ decoderPath: new URL( this.decoderPath, window.location.href ).href, // TODO: Fails in IE11.
|
|
|
|
+ decoderConfig: this.decoderConfig
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ worker.onmessage = function ( e ) {
|
|
|
|
+
|
|
|
|
+ var message = e.data;
|
|
|
|
+
|
|
|
|
+ switch ( message.type ) {
|
|
|
|
+
|
|
|
|
+ case 'decode':
|
|
|
|
+ var geometry = new THREE.BufferGeometry();
|
|
|
|
+ geometry.setIndex( new THREE.BufferAttribute( message.mesh.index.buffer, 1 ) );
|
|
|
|
+ for ( var i = 0; i < message.mesh.attributes.length; i++ ) {
|
|
|
|
+ var attribute = message.mesh.attributes[ i ];
|
|
|
|
+ geometry.addAttribute( attribute.name, new THREE.BufferAttribute( attribute.buffer, attribute.itemSize ) );
|
|
|
|
+ }
|
|
|
|
+ worker._callbacks[ message.id ]( geometry );
|
|
|
|
+ worker._taskLoad -= worker._taskCosts[ message.id ];
|
|
|
|
+ delete worker._callbacks[ message.id ];
|
|
|
|
+ delete worker._taskCosts[ message.id ];
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ throw new Error( 'THREE.GLTFDRACOLoader: Unexpected message, "' + message.type + '"' );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.workerPool.push( worker );
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ this.workerPool.sort( function ( a, b ) { return a._taskLoad > b._taskLoad ? -1 : 1; } );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return this.workerPool[ this.workerPool.length - 1 ];
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ dispose: function () {
|
|
|
|
+
|
|
|
|
+ for ( var i = 0; i < this.workerPool.length; i++ ) {
|
|
|
|
+
|
|
|
|
+ this.workerPool[ i ].terminate();
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.workerPool.length = 0;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+function GLTFDRACOWorker () {
|
|
|
|
+
|
|
|
|
+ var decoderPath;
|
|
|
|
+ var decoderConfig;
|
|
|
|
+ var decoderModulePromise;
|
|
|
|
+
|
|
|
|
+ onmessage = function ( e ) {
|
|
|
|
+
|
|
|
|
+ var message = e.data;
|
|
|
|
+
|
|
|
|
+ switch ( message.type ) {
|
|
|
|
+
|
|
|
|
+ case 'init':
|
|
|
|
+ console.log('worker:init');
|
|
|
|
+ decoderPath = message.decoderPath;
|
|
|
|
+ decoderConfig = message.decoderConfig;
|
|
|
|
+ getDecoderModule();
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'decode':
|
|
|
|
+ console.log('worker:decode');
|
|
|
|
+ decode( message.rawBuffer, message.attributeIDs, message.attributeTypes )
|
|
|
|
+ .then( function ( mesh ) {
|
|
|
|
+
|
|
|
|
+ var buffers = [ mesh.index.buffer.buffer ];
|
|
|
|
+
|
|
|
|
+ for ( var i = 0; i < mesh.attributes.length; i++ ) {
|
|
|
|
+
|
|
|
|
+ buffers.push( mesh.attributes[ i ].buffer.buffer );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.postMessage( { type: 'decode', id: message.id, mesh: mesh }, buffers );
|
|
|
|
+
|
|
|
|
+ } );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ throw new Error( 'THREE.GLTFDRACOLoader: Unexpected message type.' );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ function getDecoderModule () {
|
|
|
|
+ if ( decoderModulePromise ) return decoderModulePromise;
|
|
|
|
+
|
|
|
|
+ var promise;
|
|
|
|
+
|
|
|
|
+ if ( typeof WebAssembly !== 'object' || decoderConfig.type === 'js' ) {
|
|
|
|
+ // Load with asm.js.
|
|
|
|
+ self.importScripts( decoderPath + 'draco_decoder.js' );
|
|
|
|
+ promise = Promise.resolve();
|
|
|
|
+ } else {
|
|
|
|
+ // Load with WebAssembly.
|
|
|
|
+ self.importScripts( decoderPath + 'draco_wasm_wrapper.js' );
|
|
|
|
+ decoderConfig.wasmBinaryFile = decoderPath + 'draco_decoder.wasm';
|
|
|
|
+ promise = fetch( decoderConfig.wasmBinaryFile )
|
|
|
|
+ .then( function ( wasmResponse ) { return wasmResponse.arrayBuffer(); } )
|
|
|
|
+ .then( function ( wasmBinary ) { decoderConfig.wasmBinary = wasmBinary; } );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Wait for source files, then create and return a decoder.
|
|
|
|
+ promise = promise.then( function () {
|
|
|
|
+ return new Promise( function ( resolve ) {
|
|
|
|
+ decoderConfig.onModuleLoaded = function ( draco ) {
|
|
|
|
+ // Module is Promise-like. Wrap before resolving to avoid loop.
|
|
|
|
+ resolve( { draco: draco } );
|
|
|
|
+ };
|
|
|
|
+ DracoDecoderModule( decoderConfig );
|
|
|
|
+ } );
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ decoderModulePromise = promise;
|
|
|
|
+ return promise;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function decode ( rawBuffer, attributeIDs, attributeTypes ) {
|
|
|
|
+
|
|
|
|
+ return getDecoderModule().then( function ( module ) {
|
|
|
|
+
|
|
|
|
+ var draco = module.draco;
|
|
|
|
+ var decoder = new draco.Decoder();
|
|
|
|
+
|
|
|
|
+ var buffer = new draco.DecoderBuffer();
|
|
|
|
+ buffer.Init( new Int8Array( rawBuffer ), rawBuffer.byteLength );
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+
|
|
|
|
+ return this.decodeGeometry( draco, decoder, buffer, attributeIDs, attributeTypes );
|
|
|
|
+
|
|
|
|
+ } finally {
|
|
|
|
+
|
|
|
|
+ draco.destroy( buffer );
|
|
|
|
+ draco.destroy( decoder );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function decodeGeometry ( draco, decoder, buffer, attributeIDs, attributeTypes ) {
|
|
|
|
+
|
|
|
|
+ var dracoGeometry = new draco.Mesh();
|
|
|
|
+ var decodingStatus = decoder.DecodeBufferToMesh( buffer, dracoGeometry );
|
|
|
|
+
|
|
|
|
+ if ( !decodingStatus.ok() || dracoGeometry.ptr == 0 ) {
|
|
|
|
+
|
|
|
|
+ throw new Error( 'THREE.GLTFDRACOLoader: Decoding failed: ' + decodingStatus.error_msg() );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var geometry = { index: null, attributes: [] };
|
|
|
|
+
|
|
|
|
+ var numFaces = dracoGeometry.num_faces();
|
|
|
|
+ var numPoints = dracoGeometry.num_points();
|
|
|
|
+ var numAttributes = dracoGeometry.num_attributes();
|
|
|
|
+
|
|
|
|
+ // Add attributes of user specified unique id.
|
|
|
|
+ for (var attributeName in attributeIDs) {
|
|
|
|
+ var attributeType = self[ attributeTypes[ attributeName ] ];
|
|
|
|
+ var attributeId = attributeIDs[attributeName];
|
|
|
|
+ var attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeId );
|
|
|
|
+ geometry.attributes.push( this.decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Generate mesh faces.
|
|
|
|
+ var numIndices = numFaces * 3;
|
|
|
|
+ var index = new Uint32Array(numIndices);
|
|
|
|
+ var indexArray = new draco.DracoInt32Array();
|
|
|
|
+ for (var i = 0; i < numFaces; ++i) {
|
|
|
|
+ decoder.GetFaceFromMesh(dracoGeometry, i, indexArray);
|
|
|
|
+ index[i * 3] = indexArray.GetValue(0);
|
|
|
|
+ index[i * 3 + 1] = indexArray.GetValue(1);
|
|
|
|
+ index[i * 3 + 2] = indexArray.GetValue(2);
|
|
|
|
+ }
|
|
|
|
+ geometry.index = { buffer: index, itemSize: 1 };
|
|
|
|
+
|
|
|
|
+ draco.destroy( indexArray );
|
|
|
|
+ draco.destroy( dracoGeometry );
|
|
|
|
+
|
|
|
|
+ return geometry;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function decodeAttribute ( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) {
|
|
|
|
+
|
|
|
|
+ var numComponents = attribute.num_components();
|
|
|
|
+ var numPoints = dracoGeometry.num_points();
|
|
|
|
+ var numValues = numPoints * numComponents;
|
|
|
|
+ var dracoArray;
|
|
|
|
+
|
|
|
|
+ var buffer;
|
|
|
|
+
|
|
|
|
+ switch ( attributeType ) {
|
|
|
|
+
|
|
|
|
+ case Float32Array:
|
|
|
|
+ dracoArray = new draco.DracoFloat32Array();
|
|
|
|
+ decoder.GetAttributeFloatForAllPoints( dracoGeometry, attribute, dracoArray );
|
|
|
|
+ buffer = new Float32Array( numValues );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Int8Array:
|
|
|
|
+ dracoArray = new draco.DracoInt8Array();
|
|
|
|
+ decoder.GetAttributeInt8ForAllPoints( dracoGeometry, attribute, dracoArray );
|
|
|
|
+ buffer = new Int8Array( numValues );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Int16Array:
|
|
|
|
+ dracoArray = new draco.DracoInt16Array();
|
|
|
|
+ decoder.GetAttributeInt16ForAllPoints( dracoGeometry, attribute, dracoArray );
|
|
|
|
+ buffer = new Int16Array( numValues );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Int32Array:
|
|
|
|
+ dracoArray = new draco.DracoInt32Array();
|
|
|
|
+ decoder.GetAttributeInt32ForAllPoints( dracoGeometry, attribute, dracoArray );
|
|
|
|
+ buffer = new Int32Array( numValues );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Uint8Array:
|
|
|
|
+ dracoArray = new draco.DracoUInt8Array();
|
|
|
|
+ decoder.GetAttributeUInt8ForAllPoints( dracoGeometry, attribute, dracoArray );
|
|
|
|
+ buffer = new Uint8Array( numValues );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Uint16Array:
|
|
|
|
+ dracoArray = new draco.DracoUInt16Array();
|
|
|
|
+ decoder.GetAttributeUInt16ForAllPoints( dracoGeometry, attribute, dracoArray );
|
|
|
|
+ buffer = new Uint16Array( numValues );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Uint32Array:
|
|
|
|
+ dracoArray = new draco.DracoUInt32Array();
|
|
|
|
+ decoder.GetAttributeUInt32ForAllPoints( dracoGeometry, attribute, dracoArray );
|
|
|
|
+ buffer = new Uint32Array( numValues );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ throw new Error( 'THREE.GLTFDRACOLoader: Unexpected attribute type.' );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for ( var i = 0; i < numValues; i++ ) {
|
|
|
|
+
|
|
|
|
+ buffer[ i ] = dracoArray.GetValue( i );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ draco.destroy( dracoArray );
|
|
|
|
+
|
|
|
|
+ return {
|
|
|
|
+ name: attributeName,
|
|
|
|
+ buffer: buffer,
|
|
|
|
+ itemSize: numComponents
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+};
|