draco/javascript/example/DRACOLoader.js
Ondrej Stava 81d73904ac Draco updated to 1.0.0 version
The latest version of Draco brings many new enhancements to improve the
development experience:
* Stable API release
* Support for npm Javascript package management
* Javascript based encoder
* Generalized metadata for meshes and point clouds
  * Now supporting material properties included along with encoded file
* Improved compression rates:
  * 15% better compression on smaller models
  * 40% better compression of normals
* Performance improvements (~10% faster encoding, decoding)
* Reduced GPU memory usage:
  * Option to store decoded quantized attributes
  * Support for triangle strip connectivity on decoded meshes
* iOS 9 Javascript decoder
* Bitstream specification now available
2017-07-28 14:25:10 -07:00

406 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';
THREE.DRACOLoader = function(manager) {
this.manager = (manager !== undefined) ? manager :
THREE.DefaultLoadingManager;
this.materials = null;
this.verbosity = 0;
this.attributeOptions = {};
this.dracoDecoderType = {};
this.drawMode = THREE.TrianglesDrawMode;
};
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;
},
setDracoDecoderType: function(dracoDecoderType) {
this.dracoDecoderType = dracoDecoderType;
},
/**
* 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;
},
decodeDracoFile: function(rawBuffer, callback) {
var scope = this;
THREE.DRACOLoader.getDecoder(this.dracoDecoderType,
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));
},
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, numPoints;
var numVertexCoordinates, numTextureCoordinates, numColorCoordinates;
var numAttributes;
var numColorCoordinateComponents = 3;
// For output basic geometry information.
var geometryInfoStr;
if (geometryType == dracoDecoder.TRIANGULAR_MESH) {
numFaces = dracoGeometry.num_faces();
if (this.verbosity > 0) {
console.log('Number of faces loaded: ' + numFaces.toString());
}
} else {
numFaces = 0;
}
numPoints = dracoGeometry.num_points();
numVertexCoordinates = numPoints * 3;
numTextureCoordinates = numPoints * 2;
numColorCoordinates = numPoints * 3;
numAttributes = dracoGeometry.num_attributes();
if (this.verbosity > 0) {
console.log('Number of points loaded: ' + numPoints.toString());
console.log('Number of attributes loaded: ' +
numAttributes.toString());
}
// Get position attribute. Must exists.
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);
var posAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(
dracoGeometry, posAttribute, posAttributeData);
// Get color attributes if exists.
var colorAttId = decoder.GetAttributeId(dracoGeometry,
dracoDecoder.COLOR);
var colAttributeData;
if (colorAttId != -1) {
if (this.verbosity > 0) {
console.log('Loaded color attribute.');
}
var colAttribute = decoder.GetAttribute(dracoGeometry, colorAttId);
if (colAttribute.num_components() === 4) {
numColorCoordinates = numPoints * 4;
numColorCoordinateComponents = 4;
}
colAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(dracoGeometry, colAttribute,
colAttributeData);
}
// Get normal attributes if exists.
var normalAttId =
decoder.GetAttributeId(dracoGeometry, dracoDecoder.NORMAL);
var norAttributeData;
if (normalAttId != -1) {
if (this.verbosity > 0) {
console.log('Loaded normal attribute.');
}
var norAttribute = decoder.GetAttribute(dracoGeometry, normalAttId);
norAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(dracoGeometry, norAttribute,
norAttributeData);
}
// Get texture coord attributes if exists.
var texCoordAttId =
decoder.GetAttributeId(dracoGeometry, dracoDecoder.TEX_COORD);
var textCoordAttributeData;
if (texCoordAttId != -1) {
if (this.verbosity > 0) {
console.log('Loaded texture coordinate attribute.');
}
var texCoordAttribute = decoder.GetAttribute(dracoGeometry,
texCoordAttId);
textCoordAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(dracoGeometry,
texCoordAttribute,
textCoordAttributeData);
}
// Structure for converting to THREEJS geometry later.
var geometryBuffer = {
vertices: new Float32Array(numVertexCoordinates),
normals: new Float32Array(numVertexCoordinates),
uvs: new Float32Array(numTextureCoordinates),
colors: new Float32Array(numColorCoordinates)
};
for (var i = 0; i < numVertexCoordinates; i += 3) {
geometryBuffer.vertices[i] = posAttributeData.GetValue(i);
geometryBuffer.vertices[i + 1] = posAttributeData.GetValue(i + 1);
geometryBuffer.vertices[i + 2] = posAttributeData.GetValue(i + 2);
// Add normal.
if (normalAttId != -1) {
geometryBuffer.normals[i] = norAttributeData.GetValue(i);
geometryBuffer.normals[i + 1] = norAttributeData.GetValue(i + 1);
geometryBuffer.normals[i + 2] = norAttributeData.GetValue(i + 2);
}
}
// Add color.
for (var i = 0; i < numColorCoordinates; i += 1) {
if (colorAttId != -1) {
// Draco colors are already normalized.
geometryBuffer.colors[i] = colAttributeData.GetValue(i);
} else {
// Default is white. This is faster than TypedArray.fill().
geometryBuffer.colors[i] = 1.0;
}
}
// Add texture coordinates.
if (texCoordAttId != -1) {
for (var i = 0; i < numTextureCoordinates; i += 2) {
geometryBuffer.uvs[i] = textCoordAttributeData.GetValue(i);
geometryBuffer.uvs[i + 1] = textCoordAttributeData.GetValue(i + 1);
}
}
dracoDecoder.destroy(posAttributeData);
if (colorAttId != -1)
dracoDecoder.destroy(colAttributeData);
if (normalAttId != -1)
dracoDecoder.destroy(norAttributeData);
if (texCoordAttId != -1)
dracoDecoder.destroy(textCoordAttributeData);
// 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);
}
}
// Import data to Three JS geometry.
var geometry = new THREE.BufferGeometry();
geometry.drawMode = this.drawMode;
if (geometryType == dracoDecoder.TRIANGULAR_MESH) {
geometry.setIndex(new(geometryBuffer.indices.length > 65535 ?
THREE.Uint32BufferAttribute : THREE.Uint16BufferAttribute)
(geometryBuffer.indices, 1));
}
geometry.addAttribute('position',
new THREE.Float32BufferAttribute(geometryBuffer.vertices, 3));
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);
geometry.addAttribute('color',
new THREE.Float32BufferAttribute(geometryBuffer.colors,
numColorCoordinateComponents));
if (normalAttId != -1) {
geometry.addAttribute('normal',
new THREE.Float32BufferAttribute(geometryBuffer.normals, 3));
}
if (texCoordAttId != -1) {
geometry.addAttribute('uv',
new THREE.Float32BufferAttribute(geometryBuffer.uvs, 2));
}
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.getDecoder(this.dracoDecoderType,
function(decoder) {
callback(decoder.isVersionSupported(version));
});
},
getAttributeOptions: function(attributeName) {
if (typeof this.attributeOptions[attributeName] === 'undefined')
this.attributeOptions[attributeName] = {};
return this.attributeOptions[attributeName];
}
};
/**
* 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.getDecoder = (function() {
var decoder;
return function(dracoDecoderType, onDracoDecoderModuleLoadedCallback) {
if (typeof DracoDecoderModule === 'undefined') {
throw new Error('THREE.DRACOLoader: DracoDecoderModule not found.');
}
if (typeof decoder !== 'undefined') {
// Module already initialized.
if (typeof onDracoDecoderModuleLoadedCallback !== 'undefined') {
onDracoDecoderModuleLoadedCallback(decoder);
}
} else {
dracoDecoderType['onModuleLoaded'] = function(module) {
if (typeof onDracoDecoderModuleLoadedCallback === 'function') {
decoder = module;
onDracoDecoderModuleLoadedCallback(module);
}
};
DracoDecoderModule(dracoDecoderType);
}
};
})();