1// Copyright 2016 The Draco Authors. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14// 15'use strict'; 16 17/** 18 * @param {THREEW.LoadingManager} manager 19 */ 20THREEW.DRACOLoader = function(manager) { 21 this.timeLoaded = 0; 22 this.manager = manager || THREEW.DefaultLoadingManager; 23 this.materials = null; 24 this.verbosity = 0; 25 this.attributeOptions = {}; 26 this.drawMode = THREEW.TrianglesDrawMode; 27 // Native Draco attribute type to THREEW.JS attribute type. 28 this.nativeAttributeMap = { 29 'position' : 'POSITION', 30 'normal' : 'NORMAL', 31 'color' : 'COLOR', 32 'uv' : 'TEX_COORD' 33 }; 34}; 35 36THREEW.DRACOLoader.prototype = { 37 38 constructor: THREEW.DRACOLoader, 39 40 load: function(url, onLoad, onProgress, onError) { 41 var scope = this; 42 var loader = new THREEW.FileLoader(scope.manager); 43 loader.setPath(this.path); 44 loader.setResponseType('arraybuffer'); 45 loader.load(url, function(blob) { 46 scope.decodeDracoFile(blob, onLoad); 47 }, onProgress, onError); 48 }, 49 50 setPath: function(value) { 51 this.path = value; 52 return this; 53 }, 54 55 setVerbosity: function(level) { 56 this.verbosity = level; 57 return this; 58 }, 59 60 /** 61 * Sets desired mode for generated geometry indices. 62 * Can be either: 63 * THREEW.TrianglesDrawMode 64 * THREEW.TriangleStripDrawMode 65 */ 66 setDrawMode: function(drawMode) { 67 this.drawMode = drawMode; 68 return this; 69 }, 70 71 /** 72 * Skips dequantization for a specific attribute. 73 * |attributeName| is the THREEW.js name of the given attribute type. 74 * The only currently supported |attributeName| is 'position', more may be 75 * added in future. 76 */ 77 setSkipDequantization: function(attributeName, skip) { 78 var skipDequantization = true; 79 if (typeof skip !== 'undefined') 80 skipDequantization = skip; 81 this.getAttributeOptions(attributeName).skipDequantization = 82 skipDequantization; 83 return this; 84 }, 85 86 /** 87 * Decompresses a Draco buffer. Names of attributes (for ID and type maps) 88 * must be one of the supported THREEW.js types, including: position, color, 89 * normal, uv, uv2, skinIndex, skinWeight. 90 * 91 * @param {ArrayBuffer} rawBuffer 92 * @param {Function} callback 93 * @param {Object|undefined} attributeUniqueIdMap Provides a pre-defined ID 94 * for each attribute in the geometry to be decoded. If given, 95 * `attributeTypeMap` is required and `nativeAttributeMap` will be 96 * ignored. 97 * @param {Object|undefined} attributeTypeMap Provides a predefined data 98 * type (as a typed array constructor) for each attribute in the 99 * geometry to be decoded. 100 */ 101 decodeDracoFile: function(rawBuffer, callback, attributeUniqueIdMap, 102 attributeTypeMap) { 103 var scope = this; 104 THREEW.DRACOLoader.getDecoderModule() 105 .then( function ( module ) { 106 scope.decodeDracoFileInternal( rawBuffer, module.decoder, callback, 107 attributeUniqueIdMap, attributeTypeMap); 108 }); 109 }, 110 111 decodeDracoFileInternal: function(rawBuffer, dracoDecoder, callback, 112 attributeUniqueIdMap, attributeTypeMap) { 113 /* 114 * Here is how to use Draco Javascript decoder and get the geometry. 115 */ 116 var buffer = new dracoDecoder.DecoderBuffer(); 117 buffer.Init(new Int8Array(rawBuffer), rawBuffer.byteLength); 118 var decoder = new dracoDecoder.Decoder(); 119 120 /* 121 * Determine what type is this file: mesh or point cloud. 122 */ 123 var geometryType = decoder.GetEncodedGeometryType(buffer); 124 if (geometryType == dracoDecoder.TRIANGULAR_MESH) { 125 if (this.verbosity > 0) { 126 console.log('Loaded a mesh.'); 127 } 128 } else if (geometryType == dracoDecoder.POINT_CLOUD) { 129 if (this.verbosity > 0) { 130 console.log('Loaded a point cloud.'); 131 } 132 } else { 133 var errorMsg = 'THREEW.DRACOLoader: Unknown geometry type.'; 134 console.error(errorMsg); 135 throw new Error(errorMsg); 136 } 137 callback(this.convertDracoGeometryTo3JS(dracoDecoder, decoder, 138 geometryType, buffer, attributeUniqueIdMap, attributeTypeMap)); 139 }, 140 141 addAttributeToGeometry: function(dracoDecoder, decoder, dracoGeometry, 142 attributeName, attributeType, attribute, 143 geometry, geometryBuffer) { 144 if (attribute.ptr === 0) { 145 var errorMsg = 'THREEW.DRACOLoader: No attribute ' + attributeName; 146 console.error(errorMsg); 147 throw new Error(errorMsg); 148 } 149 150 var numComponents = attribute.num_components(); 151 var numPoints = dracoGeometry.num_points(); 152 var numValues = numPoints * numComponents; 153 var attributeData; 154 var TypedBufferAttribute; 155 156 switch ( attributeType ) { 157 158 case Float32Array: 159 attributeData = new dracoDecoder.DracoFloat32Array(); 160 decoder.GetAttributeFloatForAllPoints( 161 dracoGeometry, attribute, attributeData); 162 geometryBuffer[ attributeName ] = new Float32Array( numValues ); 163 TypedBufferAttribute = THREEW.Float32BufferAttribute; 164 break; 165 166 case Int8Array: 167 attributeData = new dracoDecoder.DracoInt8Array(); 168 decoder.GetAttributeInt8ForAllPoints( 169 dracoGeometry, attribute, attributeData ); 170 geometryBuffer[ attributeName ] = new Int8Array( numValues ); 171 TypedBufferAttribute = THREEW.Int8BufferAttribute; 172 break; 173 174 case Int16Array: 175 attributeData = new dracoDecoder.DracoInt16Array(); 176 decoder.GetAttributeInt16ForAllPoints( 177 dracoGeometry, attribute, attributeData); 178 geometryBuffer[ attributeName ] = new Int16Array( numValues ); 179 TypedBufferAttribute = THREEW.Int16BufferAttribute; 180 break; 181 182 case Int32Array: 183 attributeData = new dracoDecoder.DracoInt32Array(); 184 decoder.GetAttributeInt32ForAllPoints( 185 dracoGeometry, attribute, attributeData); 186 geometryBuffer[ attributeName ] = new Int32Array( numValues ); 187 TypedBufferAttribute = THREEW.Int32BufferAttribute; 188 break; 189 190 case Uint8Array: 191 attributeData = new dracoDecoder.DracoUInt8Array(); 192 decoder.GetAttributeUInt8ForAllPoints( 193 dracoGeometry, attribute, attributeData); 194 geometryBuffer[ attributeName ] = new Uint8Array( numValues ); 195 TypedBufferAttribute = THREEW.Uint8BufferAttribute; 196 break; 197 198 case Uint16Array: 199 attributeData = new dracoDecoder.DracoUInt16Array(); 200 decoder.GetAttributeUInt16ForAllPoints( 201 dracoGeometry, attribute, attributeData); 202 geometryBuffer[ attributeName ] = new Uint16Array( numValues ); 203 TypedBufferAttribute = THREEW.Uint16BufferAttribute; 204 break; 205 206 case Uint32Array: 207 attributeData = new dracoDecoder.DracoUInt32Array(); 208 decoder.GetAttributeUInt32ForAllPoints( 209 dracoGeometry, attribute, attributeData); 210 geometryBuffer[ attributeName ] = new Uint32Array( numValues ); 211 TypedBufferAttribute = THREEW.Uint32BufferAttribute; 212 break; 213 214 default: 215 var errorMsg = 'THREEW.DRACOLoader: Unexpected attribute type.'; 216 console.error( errorMsg ); 217 throw new Error( errorMsg ); 218 219 } 220 221 // Copy data from decoder. 222 for (var i = 0; i < numValues; i++) { 223 geometryBuffer[attributeName][i] = attributeData.GetValue(i); 224 } 225 // Add attribute to THREEJS geometry for rendering. 226 geometry.addAttribute(attributeName, 227 new TypedBufferAttribute(geometryBuffer[attributeName], 228 numComponents)); 229 dracoDecoder.destroy(attributeData); 230 }, 231 232 convertDracoGeometryTo3JS: function(dracoDecoder, decoder, geometryType, 233 buffer, attributeUniqueIdMap, 234 attributeTypeMap) { 235 // TODO: Should not assume native Draco attribute IDs apply. 236 if (this.getAttributeOptions('position').skipDequantization === true) { 237 decoder.SkipAttributeTransform(dracoDecoder.POSITION); 238 } 239 var dracoGeometry; 240 var decodingStatus; 241 var start_time = performance.now(); 242 if (geometryType === dracoDecoder.TRIANGULAR_MESH) { 243 dracoGeometry = new dracoDecoder.Mesh(); 244 decodingStatus = decoder.DecodeBufferToMesh(buffer, dracoGeometry); 245 } else { 246 dracoGeometry = new dracoDecoder.PointCloud(); 247 decodingStatus = 248 decoder.DecodeBufferToPointCloud(buffer, dracoGeometry); 249 } 250 if (!decodingStatus.ok() || dracoGeometry.ptr == 0) { 251 var errorMsg = 'THREEW.DRACOLoader: Decoding failed: '; 252 errorMsg += decodingStatus.error_msg(); 253 console.error(errorMsg); 254 dracoDecoder.destroy(decoder); 255 dracoDecoder.destroy(dracoGeometry); 256 throw new Error(errorMsg); 257 } 258 259 var decode_end = performance.now(); 260 dracoDecoder.destroy(buffer); 261 /* 262 * Example on how to retrieve mesh and attributes. 263 */ 264 var numFaces; 265 if (geometryType == dracoDecoder.TRIANGULAR_MESH) { 266 numFaces = dracoGeometry.num_faces(); 267 if (this.verbosity > 0) { 268 console.log('Number of faces loaded: ' + numFaces.toString()); 269 } 270 } else { 271 numFaces = 0; 272 } 273 274 var numPoints = dracoGeometry.num_points(); 275 var numAttributes = dracoGeometry.num_attributes(); 276 if (this.verbosity > 0) { 277 console.log('Number of points loaded: ' + numPoints.toString()); 278 console.log('Number of attributes loaded: ' + 279 numAttributes.toString()); 280 } 281 282 // Verify if there is position attribute. 283 // TODO: Should not assume native Draco attribute IDs apply. 284 var posAttId = decoder.GetAttributeId(dracoGeometry, 285 dracoDecoder.POSITION); 286 if (posAttId == -1) { 287 var errorMsg = 'THREEW.DRACOLoader: No position attribute found.'; 288 console.error(errorMsg); 289 dracoDecoder.destroy(decoder); 290 dracoDecoder.destroy(dracoGeometry); 291 throw new Error(errorMsg); 292 } 293 var posAttribute = decoder.GetAttribute(dracoGeometry, posAttId); 294 295 // Structure for converting to THREEJS geometry later. 296 var geometryBuffer = {}; 297 // Import data to Three JS geometry. 298 var geometry = new THREEW.BufferGeometry(); 299
300 // Do not use both the native attribute map and a provided (e.g. glTF) map. 301 if ( attributeUniqueIdMap ) { 302 303 // Add attributes of user specified unique id. E.g. GLTF models. 304 for (var attributeName in attributeUniqueIdMap) { 305 var attributeType = attributeTypeMap[attributeName]; 306 var attributeId = attributeUniqueIdMap[attributeName]; 307 var attribute = decoder.GetAttributeByUniqueId(dracoGeometry, 308 attributeId); 309 this.addAttributeToGeometry(dracoDecoder, decoder, dracoGeometry, 310 attributeName, attributeType, attribute, geometry, geometryBuffer); 311 } 312 313 } else { 314 315 // Add native Draco attribute type to geometry. 316 for (var attributeName in this.nativeAttributeMap) { 317 var attId = decoder.GetAttributeId(dracoGeometry, 318 dracoDecoder[this.nativeAttributeMap[attributeName]]); 319 if (attId !== -1) { 320 if (this.verbosity > 0) { 321 console.log('Loaded ' + attributeName + ' attribute.'); 322 } 323 var attribute = decoder.GetAttribute(dracoGeometry, attId); 324 this.addAttributeToGeometry(dracoDecoder, decoder, dracoGeometry, 325 attributeName, Float32Array, attribute, geometry, geometryBuffer); 326 } 327 } 328 329 } 330 331 // For mesh, we need to generate the faces. 332 if (geometryType == dracoDecoder.TRIANGULAR_MESH) { 333 if (this.drawMode === THREEW.TriangleStripDrawMode) { 334 var stripsArray = new dracoDecoder.DracoInt32Array(); 335 var numStrips = decoder.GetTriangleStripsFromMesh( 336 dracoGeometry, stripsArray); 337 geometryBuffer.indices = new Uint32Array(stripsArray.size()); 338 for (var i = 0; i < stripsArray.size(); ++i) { 339 geometryBuffer.indices[i] = stripsArray.GetValue(i); 340 } 341 dracoDecoder.destroy(stripsArray); 342 } else { 343 var numIndices = numFaces * 3; 344 geometryBuffer.indices = new Uint32Array(numIndices); 345 var ia = new dracoDecoder.DracoInt32Array(); 346 for (var i = 0; i < numFaces; ++i) { 347 decoder.GetFaceFromMesh(dracoGeometry, i, ia); 348 var index = i * 3; 349 geometryBuffer.indices[index] = ia.GetValue(0); 350 geometryBuffer.indices[index + 1] = ia.GetValue(1); 351 geometryBuffer.indices[index + 2] = ia.GetValue(2); 352 } 353 dracoDecoder.destroy(ia); 354 } 355 } 356 357 geometry.drawMode = this.drawMode; 358 if (geometryType == dracoDecoder.TRIANGULAR_MESH) { 359 geometry.setIndex(new(geometryBuffer.indices.length > 65535 ? 360 THREEW.Uint32BufferAttribute : THREEW.Uint16BufferAttribute) 361 (geometryBuffer.indices, 1)); 362 } 363 364 // TODO: Should not assume native Draco attribute IDs apply. 365 // TODO: Can other attribute types be quantized? 366 var posTransform = new dracoDecoder.AttributeQuantizationTransform(); 367 if (posTransform.InitFromAttribute(posAttribute)) { 368 // Quantized attribute. Store the quantization parameters into the 369 // THREEW.js attribute. 370 geometry.attributes['position'].isQuantized = true; 371 geometry.attributes['position'].maxRange = posTransform.range(); 372 geometry.attributes['position'].numQuantizationBits = 373 posTransform.quantization_bits(); 374 geometry.attributes['position'].minValues = new Float32Array(3); 375 for (var i = 0; i < 3; ++i) { 376 geometry.attributes['position'].minValues[i] = 377 posTransform.min_value(i); 378 } 379 } 380 dracoDecoder.destroy(posTransform); 381 dracoDecoder.destroy(decoder); 382 dracoDecoder.destroy(dracoGeometry); 383 384 this.decode_time = decode_end - start_time; 385 this.import_time = performance.now() - decode_end; 386 387 if (this.verbosity > 0) { 388 console.log('Decode time: ' + this.decode_time); 389 console.log('Import time: ' + this.import_time); 390 } 391 return geometry; 392 }, 393 394 isVersionSupported: function(version, callback) { 395 THREEW.DRACOLoader.getDecoderModule() 396 .then( function ( module ) { 397 callback( module.decoder.isVersionSupported( version ) ); 398 }); 399 }, 400 401 getAttributeOptions: function(attributeName) { 402 if (typeof this.attributeOptions[attributeName] === 'undefined') 403 this.attributeOptions[attributeName] = {}; 404 return this.attributeOptions[attributeName]; 405 } 406}; 407 408THREEW.DRACOLoader.decoderPath = './'; 409THREEW.DRACOLoader.decoderConfig = {}; 410THREEW.DRACOLoader.decoderModulePromise = null; 411 412/** 413 * Sets the base path for decoder source files. 414 * @param {string} path 415 */ 416THREEW.DRACOLoader.setDecoderPath = function ( path ) { 417 THREEW.DRACOLoader.decoderPath = path; 418}; 419 420/** 421 * Sets decoder configuration and releases singleton decoder module. Module
422 * will be recreated with the next decoding call. 423 * @param {Object} config 424 */ 425THREEW.DRACOLoader.setDecoderConfig = function ( config ) { 426 var wasmBinary = THREEW.DRACOLoader.decoderConfig.wasmBinary; 427 THREEW.DRACOLoader.decoderConfig = config || {}; 428 THREEW.DRACOLoader.releaseDecoderModule(); 429 430 // Reuse WASM binary. 431 if ( wasmBinary ) THREEW.DRACOLoader.decoderConfig.wasmBinary = wasmBinary; 432}; 433 434/** 435 * Releases the singleton DracoDecoderModule instance. Module will be recreated 436 * with the next decoding call. 437 */ 438THREEW.DRACOLoader.releaseDecoderModule = function () { 439 THREEW.DRACOLoader.decoderModulePromise = null; 440}; 441 442/** 443 * Gets WebAssembly or asm.js singleton instance of DracoDecoderModule 444 * after testing for browser support. Returns Promise that resolves when 445 * module is available. 446 * @return {Promise<{decoder: DracoDecoderModule}>} 447 */ 448THREEW.DRACOLoader.getDecoderModule = function () { 449 var scope = this; 450 var path = THREEW.DRACOLoader.decoderPath; 451 var config = THREEW.DRACOLoader.decoderConfig; 452 var promise = THREEW.DRACOLoader.decoderModulePromise; 453 454 if ( promise ) return promise; 455 456 // Load source files. 457 if ( typeof DracoDecoderModule !== 'undefined' ) { 458 // Loaded externally. 459 promise = Promise.resolve(); 460 } else if ( typeof WebAssembly !== 'object' || config.type === 'js' ) { 461 // Load with asm.js. 462 promise = THREEW.DRACOLoader._loadScript( path + 'draco_decoder.js' ); 463 } else { 464 // Load with WebAssembly. 465 config.wasmBinaryFile = path + 'draco_decoder.wasm'; 466 promise = THREEW.DRACOLoader._loadScript( path + 'draco_wasm_wrapper.js' ) 467 .then( function () { 468 return THREEW.DRACOLoader._loadArrayBuffer( config.wasmBinaryFile ); 469 } ) 470 .then( function ( wasmBinary ) { 471 config.wasmBinary = wasmBinary; 472 } ); 473 } 474 475 // Wait for source files, then create and return a decoder. 476 promise = promise.then( function () { 477 return new Promise( function ( resolve ) { 478 config.onModuleLoaded = function ( decoder ) { 479 scope.timeLoaded = performance.now(); 480 // Module is Promise-like. Wrap before resolving to avoid loop. 481 resolve( { decoder: decoder } ); 482 }; 483 DracoDecoderModule( config ); 484 } ); 485 } ); 486 487 THREEW.DRACOLoader.decoderModulePromise = promise; 488 return promise; 489}; 490 491/** 492 * @param {string} src 493 * @return {Promise} 494 */ 495THREEW.DRACOLoader._loadScript = function ( src ) { 496 var prevScript = document.getElementById( 'decoder_script' ); 497 if ( prevScript !== null ) { 498 prevScript.parentNode.removeChild( prevScript ); 499 } 500 var head = document.getElementsByTagName( 'head' )[ 0 ]; 501 var script = document.createElement( 'script' ); 502 script.id = 'decoder_script'; 503 script.type = 'text/javascript'; 504 script.src = src; 505 return new Promise( function ( resolve ) { 506 script.onload = resolve; 507 head.appendChild( script ); 508 }); 509}; 510 511/** 512 * @param {string} src 513 * @return {Promise} 514 */ 515THREEW.DRACOLoader._loadArrayBuffer = function ( src ) { 516 var loader = new THREEW.FileLoader(); 517 loader.setResponseType( 'arraybuffer' ); 518 return new Promise( function( resolve, reject ) { 519 loader.load( src, resolve, undefined, reject ); 520 }); 521};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.