mirror of
https://git.mirrors.martin98.com/https://github.com/google/draco
synced 2025-04-21 13:19:57 +08:00

* Up to 20% faster decoding * Added support for integer attributes to our Javascript Encoder * Fixed issues with THREE.DracoLoader not releasing memory associated with the Draco module * OBJ decoder can now be used to parse pure point clouds * Added Unity plugins to support runtime loading and design-time importing of encoded Draco files
477 lines
19 KiB
JavaScript
477 lines
19 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';
|
|
|
|
// |dracoPath| sets the path for the Draco decoder source files. The default
|
|
// path is "./". If |dracoDecoderType|.type is set to "js", then DRACOLoader
|
|
// will load the Draco JavaScript decoder.
|
|
THREE.DRACOLoader = function(dracoPath, dracoDecoderType, manager) {
|
|
this.timeLoaded = 0;
|
|
this.manager = (manager !== undefined) ? manager :
|
|
THREE.DefaultLoadingManager;
|
|
this.materials = null;
|
|
this.verbosity = 0;
|
|
this.attributeOptions = {};
|
|
if (dracoDecoderType !== undefined) {
|
|
THREE.DRACOLoader.dracoDecoderType = dracoDecoderType;
|
|
}
|
|
this.drawMode = THREE.TrianglesDrawMode;
|
|
this.dracoSrcPath = (dracoPath !== undefined) ? dracoPath : './';
|
|
// If draco_decoder.js or wasm code is already loaded/included, then do
|
|
// not dynamically load the decoder.
|
|
if (typeof DracoDecoderModule === 'undefined') {
|
|
THREE.DRACOLoader.loadDracoDecoder(this);
|
|
}
|
|
// User defined unique id for attributes.
|
|
this.attributeUniqueIdMap = {};
|
|
// Native Draco attribute type to Three.JS attribute type.
|
|
this.nativeAttributeMap = {
|
|
'position' : 'POSITION',
|
|
'normal' : 'NORMAL',
|
|
'color' : 'COLOR',
|
|
'uv' : 'TEX_COORD'
|
|
};
|
|
};
|
|
|
|
THREE.DRACOLoader.dracoDecoderType = {};
|
|
|
|
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;
|
|
this.attributeUniqueIdMap = (attributeUniqueIdMap !== undefined) ?
|
|
attributeUniqueIdMap : {};
|
|
THREE.DRACOLoader.getDecoderModule(
|
|
function(dracoDecoder) {
|
|
scope.decodeDracoFileInternal(rawBuffer, dracoDecoder, callback);
|
|
});
|
|
},
|
|
|
|
decodeDracoFileInternal: function(rawBuffer, dracoDecoder, callback) {
|
|
/*
|
|
* 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));
|
|
},
|
|
|
|
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) {
|
|
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 (this.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 this.attributeUniqueIdMap) {
|
|
var attributeId = this.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(
|
|
function(decoder) {
|
|
callback(decoder.isVersionSupported(version));
|
|
});
|
|
},
|
|
|
|
getAttributeOptions: function(attributeName) {
|
|
if (typeof this.attributeOptions[attributeName] === 'undefined')
|
|
this.attributeOptions[attributeName] = {};
|
|
return this.attributeOptions[attributeName];
|
|
}
|
|
};
|
|
|
|
// This function loads a JavaScript file and adds it to the page. "path"
|
|
// is the path to the JavaScript file. "onLoadFunc" is the function to be
|
|
// called when the JavaScript file has been loaded.
|
|
THREE.DRACOLoader.loadJavaScriptFile = function(path, onLoadFunc,
|
|
dracoDecoder) {
|
|
var previous_decoder_script = document.getElementById("decoder_script");
|
|
if (previous_decoder_script !== null) {
|
|
return;
|
|
}
|
|
var head = document.getElementsByTagName('head')[0];
|
|
var element = document.createElement('script');
|
|
element.id = "decoder_script";
|
|
element.type = 'text/javascript';
|
|
element.src = path;
|
|
if (onLoadFunc !== null) {
|
|
element.onload = onLoadFunc(dracoDecoder);
|
|
} else {
|
|
element.onload = function(dracoDecoder) {
|
|
THREE.DRACOLoader.timeLoaded = performance.now();
|
|
};
|
|
}
|
|
head.appendChild(element);
|
|
}
|
|
|
|
THREE.DRACOLoader.loadWebAssemblyDecoder = function(dracoDecoder) {
|
|
THREE.DRACOLoader.dracoDecoderType['wasmBinaryFile'] =
|
|
dracoDecoder.dracoSrcPath + 'draco_decoder.wasm';
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', dracoDecoder.dracoSrcPath + 'draco_decoder.wasm', true);
|
|
xhr.responseType = 'arraybuffer';
|
|
xhr.onload = function() {
|
|
// draco_wasm_wrapper.js must be loaded before DracoDecoderModule is
|
|
// created. The object passed into DracoDecoderModule() must contain a
|
|
// property with the name of wasmBinary and the value must be an
|
|
// ArrayBuffer containing the contents of the .wasm file.
|
|
THREE.DRACOLoader.dracoDecoderType['wasmBinary'] = xhr.response;
|
|
THREE.DRACOLoader.timeLoaded = performance.now();
|
|
};
|
|
xhr.send(null)
|
|
}
|
|
|
|
// This function will test if the browser has support for WebAssembly. If
|
|
// it does it will download the WebAssembly Draco decoder, if not it will
|
|
// download the asmjs Draco decoder.
|
|
THREE.DRACOLoader.loadDracoDecoder = function(dracoDecoder) {
|
|
if (typeof WebAssembly !== 'object' ||
|
|
THREE.DRACOLoader.dracoDecoderType.type === 'js') {
|
|
// No WebAssembly support
|
|
THREE.DRACOLoader.loadJavaScriptFile(dracoDecoder.dracoSrcPath +
|
|
'draco_decoder.js', null, dracoDecoder);
|
|
} else {
|
|
THREE.DRACOLoader.loadJavaScriptFile(dracoDecoder.dracoSrcPath +
|
|
'draco_wasm_wrapper.js',
|
|
function (dracoDecoder) {
|
|
THREE.DRACOLoader.loadWebAssemblyDecoder(dracoDecoder);
|
|
}, dracoDecoder);
|
|
}
|
|
}
|
|
|
|
THREE.DRACOLoader.decoderCreationCalled = false;
|
|
|
|
/**
|
|
* Creates and returns a singleton instance of the DracoDecoderModule.
|
|
* The module loading is done asynchronously for WebAssembly. Initialized module
|
|
* can be accessed through the callback function
|
|
* |onDracoDecoderModuleLoadedCallback|.
|
|
*/
|
|
THREE.DRACOLoader.getDecoderModule = (function() {
|
|
return function(onDracoDecoderModuleLoadedCallback) {
|
|
if (typeof THREE.DRACOLoader.decoderModule !== 'undefined') {
|
|
// Module already initialized.
|
|
if (typeof onDracoDecoderModuleLoadedCallback !== 'undefined') {
|
|
onDracoDecoderModuleLoadedCallback(THREE.DRACOLoader.decoderModule);
|
|
}
|
|
} else {
|
|
if (typeof DracoDecoderModule === 'undefined') {
|
|
// Wait until the Draco decoder is loaded before starting the error
|
|
// timer.
|
|
if (THREE.DRACOLoader.timeLoaded > 0) {
|
|
var waitMs = performance.now() - THREE.DRACOLoader.timeLoaded;
|
|
|
|
// After loading the Draco JavaScript decoder file, there is still
|
|
// some time before the DracoDecoderModule is defined. So start a
|
|
// loop to check when the DracoDecoderModule gets defined. If the
|
|
// time is hit throw an error.
|
|
if (waitMs > 5000) {
|
|
throw new Error(
|
|
'THREE.DRACOLoader: DracoDecoderModule not found.');
|
|
}
|
|
}
|
|
} else {
|
|
if (!THREE.DRACOLoader.decoderCreationCalled) {
|
|
THREE.DRACOLoader.decoderCreationCalled = true;
|
|
THREE.DRACOLoader.dracoDecoderType['onModuleLoaded'] =
|
|
function(module) {
|
|
THREE.DRACOLoader.decoderModule = module;
|
|
};
|
|
DracoDecoderModule(THREE.DRACOLoader.dracoDecoderType);
|
|
}
|
|
}
|
|
|
|
// Either the DracoDecoderModule has not been defined or the decoder
|
|
// has not been created yet. Call getDecoderModule() again.
|
|
setTimeout(function() {
|
|
THREE.DRACOLoader.getDecoderModule(
|
|
onDracoDecoderModuleLoadedCallback);
|
|
}, 10);
|
|
}
|
|
};
|
|
|
|
})();
|
|
|
|
// Releases the DracoDecoderModule instance associated with the draco loader.
|
|
// The module will be automatically re-created the next time a new geometry is
|
|
// loaded by THREE.DRACOLoader.
|
|
THREE.DRACOLoader.releaseDecoderModule = function() {
|
|
THREE.DRACOLoader.decoderModule = undefined;
|
|
THREE.DRACOLoader.decoderCreationCalled = false;
|
|
if (THREE.DRACOLoader.dracoDecoderType !== undefined &&
|
|
THREE.DRACOLoader.dracoDecoderType.wasmBinary !== undefined) {
|
|
// For WASM build we need to preserve the wasmBinary for future use.
|
|
var wasmBinary = THREE.DRACOLoader.dracoDecoderType.wasmBinary;
|
|
THREE.DRACOLoader.dracoDecoderType = {};
|
|
THREE.DRACOLoader.dracoDecoderType.wasmBinary = wasmBinary;
|
|
} else {
|
|
THREE.DRACOLoader.dracoDecoderType = {};
|
|
}
|
|
}
|