2019-08-03 16:16:07 +08:00

724 lines
18 KiB
JavaScript

/** Copyright 2016 The Draco Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @param {THREE.LoadingManager} manager
*/
THREE.DRACOLoader = function ( manager ) {
this.timeLoaded = 0;
this.manager = manager || THREE.DefaultLoadingManager;
this.materials = null;
this.verbosity = 0;
this.attributeOptions = {};
this.drawMode = THREE.TrianglesDrawMode;
// Native Draco attribute type to Three.JS attribute type.
this.nativeAttributeMap = {
position: "POSITION",
normal: "NORMAL",
color: "COLOR",
uv: "TEX_COORD"
};
};
THREE.DRACOLoader.prototype = {
constructor: THREE.DRACOLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new THREE.FileLoader( scope.manager );
loader.setPath( this.path );
loader.setResponseType( "arraybuffer" );
loader.load(
url,
function ( blob ) {
scope.decodeDracoFile( blob, onLoad );
},
onProgress,
onError
);
},
setPath: function ( value ) {
this.path = value;
return this;
},
setVerbosity: function ( level ) {
this.verbosity = level;
return this;
},
/**
* Sets desired mode for generated geometry indices.
* Can be either:
* THREE.TrianglesDrawMode
* THREE.TriangleStripDrawMode
*/
setDrawMode: function ( drawMode ) {
this.drawMode = drawMode;
return this;
},
/**
* Skips dequantization for a specific attribute.
* |attributeName| is the THREE.js name of the given attribute type.
* The only currently supported |attributeName| is 'position', more may be
* added in future.
*/
setSkipDequantization: function ( attributeName, skip ) {
var skipDequantization = true;
if ( typeof skip !== "undefined" ) skipDequantization = skip;
this.getAttributeOptions(
attributeName
).skipDequantization = skipDequantization;
return this;
},
/**
* Decompresses a Draco buffer. Names of attributes (for ID and type maps)
* must be one of the supported three.js types, including: position, color,
* normal, uv, uv2, skinIndex, skinWeight.
*
* @param {ArrayBuffer} rawBuffer
* @param {Function} callback
* @param {Object|undefined} attributeUniqueIdMap Provides a pre-defined ID
* for each attribute in the geometry to be decoded. If given,
* `attributeTypeMap` is required and `nativeAttributeMap` will be
* ignored.
* @param {Object|undefined} attributeTypeMap Provides a predefined data
* type (as a typed array constructor) for each attribute in the
* geometry to be decoded.
*/
decodeDracoFile: function (
rawBuffer,
callback,
attributeUniqueIdMap,
attributeTypeMap
) {
var scope = this;
THREE.DRACOLoader.getDecoderModule().then( function ( module ) {
scope.decodeDracoFileInternal(
rawBuffer,
module.decoder,
callback,
attributeUniqueIdMap,
attributeTypeMap
);
} );
},
decodeDracoFileInternal: function (
rawBuffer,
dracoDecoder,
callback,
attributeUniqueIdMap,
attributeTypeMap
) {
/*
* Here is how to use Draco Javascript decoder and get the geometry.
*/
var buffer = new dracoDecoder.DecoderBuffer();
buffer.Init( new Int8Array( rawBuffer ), rawBuffer.byteLength );
var decoder = new dracoDecoder.Decoder();
/*
* Determine what type is this file: mesh or point cloud.
*/
var geometryType = decoder.GetEncodedGeometryType( buffer );
if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
if ( this.verbosity > 0 ) {
console.log( "Loaded a mesh." );
}
} else if ( geometryType == dracoDecoder.POINT_CLOUD ) {
if ( this.verbosity > 0 ) {
console.log( "Loaded a point cloud." );
}
} else {
var errorMsg = "THREE.DRACOLoader: Unknown geometry type.";
console.error( errorMsg );
throw new Error( errorMsg );
}
callback(
this.convertDracoGeometryTo3JS(
dracoDecoder,
decoder,
geometryType,
buffer,
attributeUniqueIdMap,
attributeTypeMap
)
);
},
addAttributeToGeometry: function (
dracoDecoder,
decoder,
dracoGeometry,
attributeName,
attributeType,
attribute,
geometry,
geometryBuffer
) {
if ( attribute.ptr === 0 ) {
var errorMsg = "THREE.DRACOLoader: No attribute " + attributeName;
console.error( errorMsg );
throw new Error( errorMsg );
}
var numComponents = attribute.num_components();
var numPoints = dracoGeometry.num_points();
var numValues = numPoints * numComponents;
var attributeData;
var TypedBufferAttribute;
switch ( attributeType ) {
case Float32Array:
attributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(
dracoGeometry,
attribute,
attributeData
);
geometryBuffer[ attributeName ] = new Float32Array( numValues );
TypedBufferAttribute = THREE.Float32BufferAttribute;
break;
case Int8Array:
attributeData = new dracoDecoder.DracoInt8Array();
decoder.GetAttributeInt8ForAllPoints(
dracoGeometry,
attribute,
attributeData
);
geometryBuffer[ attributeName ] = new Int8Array( numValues );
TypedBufferAttribute = THREE.Int8BufferAttribute;
break;
case Int16Array:
attributeData = new dracoDecoder.DracoInt16Array();
decoder.GetAttributeInt16ForAllPoints(
dracoGeometry,
attribute,
attributeData
);
geometryBuffer[ attributeName ] = new Int16Array( numValues );
TypedBufferAttribute = THREE.Int16BufferAttribute;
break;
case Int32Array:
attributeData = new dracoDecoder.DracoInt32Array();
decoder.GetAttributeInt32ForAllPoints(
dracoGeometry,
attribute,
attributeData
);
geometryBuffer[ attributeName ] = new Int32Array( numValues );
TypedBufferAttribute = THREE.Int32BufferAttribute;
break;
case Uint8Array:
attributeData = new dracoDecoder.DracoUInt8Array();
decoder.GetAttributeUInt8ForAllPoints(
dracoGeometry,
attribute,
attributeData
);
geometryBuffer[ attributeName ] = new Uint8Array( numValues );
TypedBufferAttribute = THREE.Uint8BufferAttribute;
break;
case Uint16Array:
attributeData = new dracoDecoder.DracoUInt16Array();
decoder.GetAttributeUInt16ForAllPoints(
dracoGeometry,
attribute,
attributeData
);
geometryBuffer[ attributeName ] = new Uint16Array( numValues );
TypedBufferAttribute = THREE.Uint16BufferAttribute;
break;
case Uint32Array:
attributeData = new dracoDecoder.DracoUInt32Array();
decoder.GetAttributeUInt32ForAllPoints(
dracoGeometry,
attribute,
attributeData
);
geometryBuffer[ attributeName ] = new Uint32Array( numValues );
TypedBufferAttribute = THREE.Uint32BufferAttribute;
break;
default:
var errorMsg = "THREE.DRACOLoader: Unexpected attribute type.";
console.error( errorMsg );
throw new Error( errorMsg );
}
// Copy data from decoder.
for ( var i = 0; i < numValues; i ++ ) {
geometryBuffer[ attributeName ][ i ] = attributeData.GetValue( i );
}
// Add attribute to THREEJS geometry for rendering.
geometry.addAttribute(
attributeName,
new TypedBufferAttribute( geometryBuffer[ attributeName ], numComponents )
);
dracoDecoder.destroy( attributeData );
},
convertDracoGeometryTo3JS: function (
dracoDecoder,
decoder,
geometryType,
buffer,
attributeUniqueIdMap,
attributeTypeMap
) {
// TODO: Should not assume native Draco attribute IDs apply.
if ( this.getAttributeOptions( "position" ).skipDequantization === true ) {
decoder.SkipAttributeTransform( dracoDecoder.POSITION );
}
var dracoGeometry;
var decodingStatus;
var start_time = performance.now();
if ( geometryType === dracoDecoder.TRIANGULAR_MESH ) {
dracoGeometry = new dracoDecoder.Mesh();
decodingStatus = decoder.DecodeBufferToMesh( buffer, dracoGeometry );
} else {
dracoGeometry = new dracoDecoder.PointCloud();
decodingStatus = decoder.DecodeBufferToPointCloud( buffer, dracoGeometry );
}
if ( ! decodingStatus.ok() || dracoGeometry.ptr == 0 ) {
var errorMsg = "THREE.DRACOLoader: Decoding failed: ";
errorMsg += decodingStatus.error_msg();
console.error( errorMsg );
dracoDecoder.destroy( decoder );
dracoDecoder.destroy( dracoGeometry );
throw new Error( errorMsg );
}
var decode_end = performance.now();
dracoDecoder.destroy( buffer );
/*
* Example on how to retrieve mesh and attributes.
*/
var numFaces;
if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
numFaces = dracoGeometry.num_faces();
if ( this.verbosity > 0 ) {
console.log( "Number of faces loaded: " + numFaces.toString() );
}
} else {
numFaces = 0;
}
var numPoints = dracoGeometry.num_points();
var numAttributes = dracoGeometry.num_attributes();
if ( this.verbosity > 0 ) {
console.log( "Number of points loaded: " + numPoints.toString() );
console.log( "Number of attributes loaded: " + numAttributes.toString() );
}
// Verify if there is position attribute.
// TODO: Should not assume native Draco attribute IDs apply.
var posAttId = decoder.GetAttributeId( dracoGeometry, dracoDecoder.POSITION );
if ( posAttId == - 1 ) {
var errorMsg = "THREE.DRACOLoader: No position attribute found.";
console.error( errorMsg );
dracoDecoder.destroy( decoder );
dracoDecoder.destroy( dracoGeometry );
throw new Error( errorMsg );
}
var posAttribute = decoder.GetAttribute( dracoGeometry, posAttId );
// Structure for converting to THREEJS geometry later.
var geometryBuffer = {};
// Import data to Three JS geometry.
var geometry = new THREE.BufferGeometry();
// Do not use both the native attribute map and a provided (e.g. glTF) map.
if ( attributeUniqueIdMap ) {
// Add attributes of user specified unique id. E.g. GLTF models.
for ( var attributeName in attributeUniqueIdMap ) {
var attributeType = attributeTypeMap[ attributeName ];
var attributeId = attributeUniqueIdMap[ attributeName ];
var attribute = decoder.GetAttributeByUniqueId(
dracoGeometry,
attributeId
);
this.addAttributeToGeometry(
dracoDecoder,
decoder,
dracoGeometry,
attributeName,
attributeType,
attribute,
geometry,
geometryBuffer
);
}
} else {
// Add native Draco attribute type to geometry.
for ( var attributeName in this.nativeAttributeMap ) {
var attId = decoder.GetAttributeId(
dracoGeometry,
dracoDecoder[ this.nativeAttributeMap[ attributeName ] ]
);
if ( attId !== - 1 ) {
if ( this.verbosity > 0 ) {
console.log( "Loaded " + attributeName + " attribute." );
}
var attribute = decoder.GetAttribute( dracoGeometry, attId );
this.addAttributeToGeometry(
dracoDecoder,
decoder,
dracoGeometry,
attributeName,
Float32Array,
attribute,
geometry,
geometryBuffer
);
}
}
}
// For mesh, we need to generate the faces.
if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
if ( this.drawMode === THREE.TriangleStripDrawMode ) {
var stripsArray = new dracoDecoder.DracoInt32Array();
decoder.GetTriangleStripsFromMesh(
dracoGeometry,
stripsArray
);
geometryBuffer.indices = new Uint32Array( stripsArray.size() );
for ( var i = 0; i < stripsArray.size(); ++ i ) {
geometryBuffer.indices[ i ] = stripsArray.GetValue( i );
}
dracoDecoder.destroy( stripsArray );
} else {
var numIndices = numFaces * 3;
geometryBuffer.indices = new Uint32Array( numIndices );
var ia = new dracoDecoder.DracoInt32Array();
for ( var i = 0; i < numFaces; ++ i ) {
decoder.GetFaceFromMesh( dracoGeometry, i, ia );
var index = i * 3;
geometryBuffer.indices[ index ] = ia.GetValue( 0 );
geometryBuffer.indices[ index + 1 ] = ia.GetValue( 1 );
geometryBuffer.indices[ index + 2 ] = ia.GetValue( 2 );
}
dracoDecoder.destroy( ia );
}
}
geometry.drawMode = this.drawMode;
if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
geometry.setIndex(
new ( geometryBuffer.indices.length > 65535
? THREE.Uint32BufferAttribute
: THREE.Uint16BufferAttribute )( geometryBuffer.indices, 1 )
);
}
// TODO: Should not assume native Draco attribute IDs apply.
// TODO: Can other attribute types be quantized?
var posTransform = new dracoDecoder.AttributeQuantizationTransform();
if ( posTransform.InitFromAttribute( posAttribute ) ) {
// Quantized attribute. Store the quantization parameters into the
// THREE.js attribute.
geometry.attributes[ "position" ].isQuantized = true;
geometry.attributes[ "position" ].maxRange = posTransform.range();
geometry.attributes[
"position"
].numQuantizationBits = posTransform.quantization_bits();
geometry.attributes[ "position" ].minValues = new Float32Array( 3 );
for ( var i = 0; i < 3; ++ i ) {
geometry.attributes[ "position" ].minValues[ i ] = posTransform.min_value(
i
);
}
}
dracoDecoder.destroy( posTransform );
dracoDecoder.destroy( decoder );
dracoDecoder.destroy( dracoGeometry );
this.decode_time = decode_end - start_time;
this.import_time = performance.now() - decode_end;
if ( this.verbosity > 0 ) {
console.log( "Decode time: " + this.decode_time );
console.log( "Import time: " + this.import_time );
}
return geometry;
},
isVersionSupported: function ( version, callback ) {
THREE.DRACOLoader.getDecoderModule().then( function ( module ) {
callback( module.decoder.isVersionSupported( version ) );
} );
},
getAttributeOptions: function ( attributeName ) {
if ( typeof this.attributeOptions[ attributeName ] === "undefined" )
this.attributeOptions[ attributeName ] = {};
return this.attributeOptions[ attributeName ];
}
};
THREE.DRACOLoader.decoderPath = "./";
THREE.DRACOLoader.decoderConfig = {};
THREE.DRACOLoader.decoderModulePromise = null;
/**
* Sets the base path for decoder source files.
* @param {string} path
*/
THREE.DRACOLoader.setDecoderPath = function ( path ) {
THREE.DRACOLoader.decoderPath = path;
};
/**
* Sets decoder configuration and releases singleton decoder module. Module
* will be recreated with the next decoding call.
* @param {Object} config
*/
THREE.DRACOLoader.setDecoderConfig = function ( config ) {
var wasmBinary = THREE.DRACOLoader.decoderConfig.wasmBinary;
THREE.DRACOLoader.decoderConfig = config || {};
THREE.DRACOLoader.releaseDecoderModule();
// Reuse WASM binary.
if ( wasmBinary ) THREE.DRACOLoader.decoderConfig.wasmBinary = wasmBinary;
};
/**
* Releases the singleton DracoDecoderModule instance. Module will be recreated
* with the next decoding call.
*/
THREE.DRACOLoader.releaseDecoderModule = function () {
THREE.DRACOLoader.decoderModulePromise = null;
};
/**
* Gets WebAssembly or asm.js singleton instance of DracoDecoderModule
* after testing for browser support. Returns Promise that resolves when
* module is available.
* @return {Promise<{decoder: DracoDecoderModule}>}
*/
THREE.DRACOLoader.getDecoderModule = function () {
var scope = this;
var path = THREE.DRACOLoader.decoderPath;
var config = THREE.DRACOLoader.decoderConfig;
var promise = THREE.DRACOLoader.decoderModulePromise;
if ( promise ) return promise;
// Load source files.
if ( typeof DracoDecoderModule !== "undefined" ) {
// Loaded externally.
promise = Promise.resolve();
} else if ( typeof WebAssembly !== "object" || config.type === "js" ) {
// Load with asm.js.
promise = THREE.DRACOLoader._loadScript( path + "draco_decoder.js" );
} else {
// Load with WebAssembly.
config.wasmBinaryFile = path + "draco_decoder.wasm";
promise = THREE.DRACOLoader._loadScript( path + "draco_wasm_wrapper.js" )
.then( function () {
return THREE.DRACOLoader._loadArrayBuffer( config.wasmBinaryFile );
} )
.then( function ( wasmBinary ) {
config.wasmBinary = wasmBinary;
} );
}
// Wait for source files, then create and return a decoder.
promise = promise.then( function () {
return new Promise( function ( resolve ) {
config.onModuleLoaded = function ( decoder ) {
scope.timeLoaded = performance.now();
// Module is Promise-like. Wrap before resolving to avoid loop.
resolve( { decoder: decoder } );
};
DracoDecoderModule( config );
} );
} );
THREE.DRACOLoader.decoderModulePromise = promise;
return promise;
};
/**
* @param {string} src
* @return {Promise}
*/
THREE.DRACOLoader._loadScript = function ( src ) {
var prevScript = document.getElementById( "decoder_script" );
if ( prevScript !== null ) {
prevScript.parentNode.removeChild( prevScript );
}
var head = document.getElementsByTagName( "head" )[ 0 ];
var script = document.createElement( "script" );
script.id = "decoder_script";
script.type = "text/javascript";
script.src = src;
return new Promise( function ( resolve ) {
script.onload = resolve;
head.appendChild( script );
} );
};
/**
* @param {string} src
* @return {Promise}
*/
THREE.DRACOLoader._loadArrayBuffer = function ( src ) {
var loader = new THREE.FileLoader();
loader.setResponseType( "arraybuffer" );
return new Promise( function ( resolve, reject ) {
loader.load( src, resolve, undefined, reject );
} );
};