/** * @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(); 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': decoderPath = message.decoderPath; decoderConfig = message.decoderConfig; getDecoderModule(); break; case '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 }; }; };