draco/javascript/example/DRACOLoader.js
2018-01-18 08:57:40 -08:00

449 lines
16 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.
//
'use strict';
/**
* @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');
if (this.crossOrigin !== undefined) {
loader.crossOrigin = this.crossOrigin;
}
loader.load(url, function(blob) {
scope.decodeDracoFile(blob, onLoad);
}, onProgress, onError);
},
setPath: function(value) {
this.path = value;
},
setCrossOrigin: function(value) {
this.crossOrigin = value;
},
setVerbosity: function(level) {
this.verbosity = level;
},
/**
* Sets desired mode for generated geometry indices.
* Can be either:
* THREE.TrianglesDrawMode
* THREE.TriangleStripDrawMode
*/
setDrawMode: function(drawMode) {
this.drawMode = drawMode;
},
/**
* 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;
},
/**
* |attributeUniqueIdMap| specifies attribute unique id for an attribute in
* the geometry to be decoded. The name of the attribute must be one of the
* supported attribute type in Three.JS, including:
* 'position',
* 'color',
* 'normal',
* 'uv',
* 'uv2',
* 'skinIndex',
* 'skinWeight'.
* The format is:
* attributeUniqueIdMap[attributeName] = attributeId
*/
decodeDracoFile: function(rawBuffer, callback, attributeUniqueIdMap) {
var scope = this;
THREE.DRACOLoader.getDecoderModule()
.then( function ( module ) {
scope.decodeDracoFileInternal( rawBuffer, module.decoder, callback,
attributeUniqueIdMap || {});
});
},
decodeDracoFileInternal: function(rawBuffer, dracoDecoder, callback,
attributeUniqueIdMap) {
/*
* 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));
},
addAttributeToGeometry: function(dracoDecoder, decoder, dracoGeometry,
attributeName, 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 attributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(
dracoGeometry, attribute, attributeData);
var numPoints = dracoGeometry.num_points();
var numValues = numPoints * numComponents;
// Allocate space for attribute.
geometryBuffer[attributeName] = new Float32Array(numValues);
// 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 THREE.Float32BufferAttribute(geometryBuffer[attributeName],
numComponents));
dracoDecoder.destroy(attributeData);
},
convertDracoGeometryTo3JS: function(dracoDecoder, decoder, geometryType,
buffer, attributeUniqueIdMap) {
if (this.getAttributeOptions('position').skipDequantization === true) {
decoder.SkipAttributeTransform(dracoDecoder.POSITION);
}
var dracoGeometry;
var decodingStatus;
const 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.
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();
// Add native Draco attribute type to geometry.
for (var attributeName in this.nativeAttributeMap) {
// The native attribute type is only used when no unique Id is
// provided. For example, loading .drc files.
if (attributeUniqueIdMap[attributeName] === undefined) {
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, attribute, geometry, geometryBuffer);
}
}
}
// Add attributes of user specified unique id. E.g. GLTF models.
for (var attributeName in attributeUniqueIdMap) {
var attributeId = attributeUniqueIdMap[attributeName];
var attribute = decoder.GetAttributeByUniqueId(dracoGeometry,
attributeId);
this.addAttributeToGeometry(dracoDecoder, decoder, dracoGeometry,
attributeName, 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();
var numStrips = 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));
}
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 );
});
};