初始化
This commit is contained in:
368
src/gltf2glb/compressDracoMeshes.js
Normal file
368
src/gltf2glb/compressDracoMeshes.js
Normal file
@@ -0,0 +1,368 @@
|
||||
'use strict';
|
||||
const Cesium = require('cesium');
|
||||
const draco3d = require('draco3d');
|
||||
const hashObject = require('object-hash');
|
||||
const addBuffer = require('./addBuffer');
|
||||
const addExtensionsRequired = require('./addExtensionsRequired');
|
||||
const addExtensionsUsed = require('./addExtensionsUsed');
|
||||
const addToArray = require('./addToArray');
|
||||
const ForEach = require('./ForEach');
|
||||
const numberOfComponentsForType = require('./numberOfComponentsForType');
|
||||
const readAccessorPacked = require('./readAccessorPacked');
|
||||
const removeUnusedElements = require('./removeUnusedElements');
|
||||
const replaceWithDecompressedPrimitive = require('./replaceWithDecompressedPrimitive');
|
||||
const splitPrimitives = require('./splitPrimitives');
|
||||
|
||||
const arrayFill = Cesium.arrayFill;
|
||||
const Cartesian3 = Cesium.Cartesian3;
|
||||
const Check = Cesium.Check;
|
||||
const clone = Cesium.clone;
|
||||
const ComponentDatatype = Cesium.ComponentDatatype;
|
||||
const defaultValue = Cesium.defaultValue;
|
||||
const defined = Cesium.defined;
|
||||
const RuntimeError = Cesium.RuntimeError;
|
||||
const WebGLConstants = Cesium.WebGLConstants;
|
||||
|
||||
// Prepare encoder for compressing meshes.
|
||||
const encoderModule = draco3d.createEncoderModule({});
|
||||
|
||||
module.exports = compressDracoMeshes;
|
||||
|
||||
/**
|
||||
* Compresses meshes using Draco compression in the glTF model.
|
||||
*
|
||||
* @param {Object} gltf A javascript object containing a glTF asset.
|
||||
* @param {Object} options The same options object as {@link processGltf}
|
||||
* @param {Object} options.dracoOptions Options defining Draco compression settings.
|
||||
* @param {Number} [options.dracoOptions.compressionLevel=7] A value between 0 and 10 specifying the quality of the Draco compression. Higher values produce better quality compression but may take longer to decompress. A value of 0 will apply sequential encoding and preserve face order.
|
||||
* @param {Number} [options.dracoOptions.quantizePositionBits=14] A value between 0 and 30 specifying the number of bits used for positions. Lower values produce better compression, but will lose precision. A value of 0 does not set quantization.
|
||||
* @param {Number} [options.dracoOptions.quantizeNormalBits=10] A value between 0 and 30 specifying the number of bits used for normals. Lower values produce better compression, but will lose precision. A value of 0 does not set quantization.
|
||||
* @param {Number} [options.dracoOptions.quantizeTexcoordBits=12] A value between 0 and 30 specifying the number of bits used for texture coordinates. Lower values produce better compression, but will lose precision. A value of 0 does not set quantization.
|
||||
* @param {Number} [options.dracoOptions.quantizeColorBits=8] A value between 0 and 30 specifying the number of bits used for color attributes. Lower values produce better compression, but will lose precision. A value of 0 does not set quantization.
|
||||
* @param {Number} [options.dracoOptions.quantizeGenericBits=12] A value between 0 and 30 specifying the number of bits used for skinning attributes (joint indices and joint weights) and custom attributes. Lower values produce better compression, but will lose precision. A value of 0 does not set quantization.
|
||||
* @param {Boolean} [options.dracoOptions.uncompressedFallback=false] If set, add uncompressed fallback versions of the compressed meshes.
|
||||
* @param {Boolean} [options.dracoOptions.unifiedQuantization=false] Quantize positions, defined by the unified bounding box of all primitives. If not set, quantization is applied separately.
|
||||
* @param {Object} [options.dracoOptions.quantizationVolume] An AxisAlignedBoundingBox defining the explicit quantization volume.
|
||||
* @returns {Object} The glTF asset with compressed meshes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function compressDracoMeshes(gltf, options) {
|
||||
options = defaultValue(options, {});
|
||||
const dracoOptions = defaultValue(options.dracoOptions, {});
|
||||
const defaults = compressDracoMeshes.defaults;
|
||||
const compressionLevel = defaultValue(dracoOptions.compressionLevel, defaults.compressionLevel);
|
||||
const quantizePositionBits = defaultValue(dracoOptions.quantizePositionBits, defaults.quantizePositionBits);
|
||||
const quantizeNormalBits = defaultValue(dracoOptions.quantizeNormalBits, defaults.quantizeNormalBits);
|
||||
const quantizeTexcoordBits = defaultValue(dracoOptions.quantizeTexcoordBits, defaults.quantizeTexcoordBits);
|
||||
const quantizeColorBits = defaultValue(dracoOptions.quantizeColorBits, defaults.quantizeColorBits);
|
||||
const quantizeGenericBits = defaultValue(dracoOptions.quantizeGenericBits, defaults.quantizeGenericBits);
|
||||
const uncompressedFallback = defaultValue(dracoOptions.uncompressedFallback, defaults.uncompressedFallback);
|
||||
const unifiedQuantization = defaultValue(dracoOptions.unifiedQuantization, defaults.unifiedQuantization);
|
||||
const quantizationVolume = dracoOptions.quantizationVolume;
|
||||
const explicitQuantization = unifiedQuantization || defined(quantizationVolume);
|
||||
checkRange('compressionLevel', compressionLevel, 0, 10);
|
||||
checkRange('quantizePositionBits', quantizePositionBits, 0, 30);
|
||||
checkRange('quantizeNormalBits', quantizeNormalBits, 0, 30);
|
||||
checkRange('quantizeTexcoordBits', quantizeTexcoordBits, 0, 30);
|
||||
checkRange('quantizeColorBits', quantizeColorBits, 0, 30);
|
||||
checkRange('quantizeGenericBits', quantizeGenericBits, 0, 30);
|
||||
|
||||
splitPrimitives(gltf);
|
||||
|
||||
const hashPrimitives = {};
|
||||
let positionOrigin;
|
||||
let positionRange;
|
||||
|
||||
if (defined(quantizationVolume)) {
|
||||
positionOrigin = Cartesian3.pack(quantizationVolume.minimum, new Array(3));
|
||||
positionRange = Cartesian3.maximumComponent(Cartesian3.subtract(quantizationVolume.maximum, quantizationVolume.minimum, new Cartesian3()));
|
||||
} else if (unifiedQuantization) {
|
||||
// Collect bounding box from all primitives. Currently works only for vec3 positions (XYZ).
|
||||
const accessors = gltf.accessors;
|
||||
const min = arrayFill(new Array(3), Number.POSITIVE_INFINITY);
|
||||
const max = arrayFill(new Array(3), Number.NEGATIVE_INFINITY);
|
||||
ForEach.accessorWithSemantic(gltf, 'POSITION', function(accessorId) {
|
||||
const accessor = accessors[accessorId];
|
||||
if (accessor.type !== 'VEC3') {
|
||||
throw new RuntimeError('Could not perform unified quantization. Input contains position accessor with an unsupported number of components.');
|
||||
}
|
||||
const accessorMin = accessor.min;
|
||||
const accessorMax = accessor.max;
|
||||
for (let j = 0; j < 3; ++j) {
|
||||
min[j] = Math.min(min[j], accessorMin[j]);
|
||||
max[j] = Math.max(max[j], accessorMax[j]);
|
||||
}
|
||||
});
|
||||
positionOrigin = min;
|
||||
positionRange = Math.max(max[0] - min[0], max[1] - min[1], max[2] - min[2]);
|
||||
}
|
||||
|
||||
let addedExtension = false;
|
||||
|
||||
ForEach.mesh(gltf, function(mesh) {
|
||||
ForEach.meshPrimitive(mesh, function(primitive) {
|
||||
if (defined(primitive.mode) && primitive.mode !== WebGLConstants.TRIANGLES) {
|
||||
return;
|
||||
}
|
||||
if (!defined(primitive.indices)) {
|
||||
addIndices(gltf, primitive);
|
||||
}
|
||||
|
||||
addedExtension = true;
|
||||
|
||||
const primitiveGeometry = {
|
||||
attributes: primitive.attributes,
|
||||
indices: primitive.indices,
|
||||
mode: primitive.mode
|
||||
};
|
||||
const hashValue = hashObject(primitiveGeometry);
|
||||
if (defined(hashPrimitives[hashValue])) {
|
||||
// Copy compressed primitive.
|
||||
copyCompressedExtensionToPrimitive(primitive, hashPrimitives[hashValue]);
|
||||
return;
|
||||
}
|
||||
hashPrimitives[hashValue] = primitive;
|
||||
|
||||
const encoder = new encoderModule.Encoder();
|
||||
const meshBuilder = new encoderModule.MeshBuilder();
|
||||
const mesh = new encoderModule.Mesh();
|
||||
|
||||
// First get the faces and add to geometry.
|
||||
const indicesData = readAccessorPacked(gltf, gltf.accessors[primitive.indices]);
|
||||
const indices = new Uint32Array(indicesData);
|
||||
const numberOfFaces = indices.length / 3;
|
||||
meshBuilder.AddFacesToMesh(mesh, numberOfFaces, indices);
|
||||
|
||||
// Add attributes to mesh.
|
||||
const attributeToId = {};
|
||||
ForEach.meshPrimitiveAttribute(primitive, function(accessorId, semantic) {
|
||||
const accessor = gltf.accessors[accessorId];
|
||||
const componentType = accessor.componentType;
|
||||
const numberOfPoints = accessor.count;
|
||||
const numberOfComponents = numberOfComponentsForType(accessor.type);
|
||||
const packed = readAccessorPacked(gltf, accessor);
|
||||
const addAttributeFunctionName = getAddAttributeFunctionName(componentType);
|
||||
const data = ComponentDatatype.createTypedArray(componentType, packed);
|
||||
|
||||
let attributeName = semantic;
|
||||
if (semantic.indexOf('_') > 0) { // Skip user-defined semantics prefixed with underscore
|
||||
attributeName = attributeName.substring(0, semantic.indexOf('_'));
|
||||
}
|
||||
|
||||
let attributeEnum;
|
||||
if (attributeName === 'POSITION' || attributeName === 'NORMAL' || attributeName === 'COLOR') {
|
||||
attributeEnum = encoderModule[attributeName];
|
||||
} else if (attributeName === 'TEXCOORD') {
|
||||
attributeEnum = encoderModule.TEX_COORD;
|
||||
} else {
|
||||
attributeEnum = encoderModule.GENERIC;
|
||||
}
|
||||
|
||||
const attributeId = meshBuilder[addAttributeFunctionName](mesh, attributeEnum, numberOfPoints, numberOfComponents, data);
|
||||
|
||||
if (attributeId === -1) {
|
||||
throw new RuntimeError('Error: Failed adding attribute ' + semantic);
|
||||
} else {
|
||||
attributeToId[semantic] = attributeId;
|
||||
}
|
||||
});
|
||||
|
||||
const encodedDracoDataArray = new encoderModule.DracoInt8Array();
|
||||
encoder.SetSpeedOptions(10 - compressionLevel, 10 - compressionLevel); // Compression level is 10 - speed.
|
||||
if (quantizePositionBits > 0) {
|
||||
if (explicitQuantization) {
|
||||
encoder.SetAttributeExplicitQuantization(encoderModule.POSITION, quantizePositionBits, 3, positionOrigin, positionRange);
|
||||
} else {
|
||||
encoder.SetAttributeQuantization(encoderModule.POSITION, quantizePositionBits);
|
||||
}
|
||||
}
|
||||
if (quantizeNormalBits > 0) {
|
||||
encoder.SetAttributeQuantization(encoderModule.NORMAL, quantizeNormalBits);
|
||||
}
|
||||
if (quantizeTexcoordBits > 0) {
|
||||
encoder.SetAttributeQuantization(encoderModule.TEX_COORD, quantizeTexcoordBits);
|
||||
}
|
||||
if (quantizeColorBits > 0) {
|
||||
encoder.SetAttributeQuantization(encoderModule.COLOR, quantizeColorBits);
|
||||
}
|
||||
if (quantizeGenericBits > 0) {
|
||||
encoder.SetAttributeQuantization(encoderModule.GENERIC, quantizeGenericBits);
|
||||
}
|
||||
|
||||
if (defined(primitive.targets)) {
|
||||
// Set sequential encoding to preserve order of vertices.
|
||||
encoder.SetEncodingMethod(encoderModule.MESH_SEQUENTIAL_ENCODING);
|
||||
}
|
||||
|
||||
encoder.SetTrackEncodedProperties(true);
|
||||
const encodedLength = encoder.EncodeMeshToDracoBuffer(mesh, encodedDracoDataArray);
|
||||
if (encodedLength <= 0) {
|
||||
throw new RuntimeError('Error: Draco encoding failed.');
|
||||
}
|
||||
|
||||
const encodedData = Buffer.alloc(encodedLength);
|
||||
for (let i = 0; i < encodedLength; ++i) {
|
||||
encodedData[i] = encodedDracoDataArray.GetValue(i);
|
||||
}
|
||||
|
||||
const dracoEncodedBuffer = {
|
||||
buffer: encodedData,
|
||||
numberOfPoints: encoder.GetNumberOfEncodedPoints(),
|
||||
numberOfFaces: encoder.GetNumberOfEncodedFaces()
|
||||
};
|
||||
addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, dracoEncodedBuffer, uncompressedFallback);
|
||||
|
||||
encoderModule.destroy(encodedDracoDataArray);
|
||||
encoderModule.destroy(mesh);
|
||||
encoderModule.destroy(meshBuilder);
|
||||
encoderModule.destroy(encoder);
|
||||
});
|
||||
});
|
||||
|
||||
if (addedExtension) {
|
||||
if (uncompressedFallback) {
|
||||
addExtensionsUsed(gltf, 'KHR_draco_mesh_compression');
|
||||
} else {
|
||||
addExtensionsRequired(gltf, 'KHR_draco_mesh_compression');
|
||||
}
|
||||
removeUnusedElements(gltf);
|
||||
|
||||
if (uncompressedFallback) {
|
||||
assignMergedBufferNames(gltf);
|
||||
}
|
||||
}
|
||||
|
||||
return gltf;
|
||||
}
|
||||
|
||||
function addIndices(gltf, primitive) {
|
||||
// Reserve the 65535 index for primitive restart
|
||||
const length = gltf.accessors[primitive.attributes.POSITION].count;
|
||||
const componentType = length < 65535 ? WebGLConstants.UNSIGNED_SHORT : WebGLConstants.UNSIGNED_INT;
|
||||
const array = ComponentDatatype.createTypedArray(componentType, length);
|
||||
for (let i = 0; i < length; ++i) {
|
||||
array[i] = i;
|
||||
}
|
||||
const buffer = Buffer.from(array.buffer);
|
||||
const bufferView = addBuffer(gltf, buffer);
|
||||
const accessor = {
|
||||
bufferView: bufferView,
|
||||
byteOffset: 0,
|
||||
componentType: componentType,
|
||||
count: length,
|
||||
type: 'SCALAR',
|
||||
min: [0],
|
||||
max: [length - 1]
|
||||
};
|
||||
primitive.indices = addToArray(gltf.accessors, accessor);
|
||||
}
|
||||
|
||||
function addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, dracoEncodedBuffer, uncompressedFallback) {
|
||||
if (!uncompressedFallback) {
|
||||
// Remove properties from accessors.
|
||||
// Remove indices bufferView.
|
||||
const indicesAccessor = clone(gltf.accessors[primitive.indices]);
|
||||
delete indicesAccessor.bufferView;
|
||||
delete indicesAccessor.byteOffset;
|
||||
primitive.indices = addToArray(gltf.accessors, indicesAccessor);
|
||||
|
||||
// Remove attributes bufferViews.
|
||||
ForEach.meshPrimitiveAttribute(primitive, function(accessorId, semantic) {
|
||||
const attributeAccessor = clone(gltf.accessors[accessorId]);
|
||||
delete attributeAccessor.bufferView;
|
||||
delete attributeAccessor.byteOffset;
|
||||
primitive.attributes[semantic] = addToArray(gltf.accessors, attributeAccessor);
|
||||
});
|
||||
}
|
||||
|
||||
const bufferViewId = addBuffer(gltf, dracoEncodedBuffer.buffer);
|
||||
|
||||
primitive.extensions = defaultValue(primitive.extensions, {});
|
||||
primitive.extensions.KHR_draco_mesh_compression = {
|
||||
bufferView: bufferViewId,
|
||||
attributes: attributeToId
|
||||
};
|
||||
|
||||
gltf = replaceWithDecompressedPrimitive(gltf, primitive, dracoEncodedBuffer, uncompressedFallback);
|
||||
}
|
||||
|
||||
function copyCompressedExtensionToPrimitive(primitive, compressedPrimitive) {
|
||||
ForEach.meshPrimitiveAttribute(compressedPrimitive, function(accessorId, semantic) {
|
||||
primitive.attributes[semantic] = accessorId;
|
||||
});
|
||||
primitive.indices = compressedPrimitive.indices;
|
||||
|
||||
const dracoExtension = compressedPrimitive.extensions.KHR_draco_mesh_compression;
|
||||
primitive.extensions = defaultValue(primitive.extensions, {});
|
||||
primitive.extensions.KHR_draco_mesh_compression = {
|
||||
bufferView: dracoExtension.bufferView,
|
||||
attributes: dracoExtension.attributes
|
||||
};
|
||||
}
|
||||
|
||||
function assignBufferViewName(gltf, bufferViewId, name) {
|
||||
const bufferView = gltf.bufferViews[bufferViewId];
|
||||
const buffer = gltf.buffers[bufferView.buffer];
|
||||
buffer.extras._pipeline.mergedBufferName = name;
|
||||
}
|
||||
|
||||
function assignAccessorName(gltf, accessorId, name) {
|
||||
const bufferViewId = gltf.accessors[accessorId].bufferView;
|
||||
if (defined(bufferViewId)) {
|
||||
assignBufferViewName(gltf, bufferViewId, name);
|
||||
}
|
||||
}
|
||||
|
||||
function assignMergedBufferNames(gltf) {
|
||||
ForEach.accessorContainingVertexAttributeData(gltf, function(accessorId) {
|
||||
assignAccessorName(gltf, accessorId, 'uncompressed');
|
||||
});
|
||||
ForEach.accessorContainingIndexData(gltf, function(accessorId) {
|
||||
assignAccessorName(gltf, accessorId, 'uncompressed');
|
||||
});
|
||||
ForEach.mesh(gltf, function(mesh) {
|
||||
ForEach.meshPrimitive(mesh, function(primitive) {
|
||||
if (defined(primitive.extensions) &&
|
||||
defined(primitive.extensions.KHR_draco_mesh_compression)) {
|
||||
assignBufferViewName(gltf, primitive.extensions.KHR_draco_mesh_compression.bufferView, 'draco');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getAddAttributeFunctionName(componentType) {
|
||||
switch (componentType) {
|
||||
case WebGLConstants.UNSIGNED_BYTE:
|
||||
return 'AddUInt8Attribute';
|
||||
case WebGLConstants.BYTE:
|
||||
return 'AddInt8Attribute';
|
||||
case WebGLConstants.UNSIGNED_SHORT:
|
||||
return 'AddUInt16Attribute';
|
||||
case WebGLConstants.SHORT:
|
||||
return 'AddInt16Attribute';
|
||||
case WebGLConstants.UNSIGNED_INT:
|
||||
return 'AddUInt32Attribute';
|
||||
case WebGLConstants.INT:
|
||||
return 'AddInt32Attribute';
|
||||
case WebGLConstants.FLOAT:
|
||||
return 'AddFloatAttribute';
|
||||
}
|
||||
}
|
||||
|
||||
function checkRange(name, value, minimum, maximum) {
|
||||
Check.typeOf.number.greaterThanOrEquals(name, value, minimum);
|
||||
Check.typeOf.number.lessThanOrEquals(name, value, maximum);
|
||||
}
|
||||
|
||||
compressDracoMeshes.defaults = {
|
||||
compressionLevel: 7,
|
||||
quantizePositionBits: 14,
|
||||
quantizeNormalBits: 10,
|
||||
quantizeTexcoordBits: 12,
|
||||
quantizeColorBits: 8,
|
||||
quantizeSkinBits: 12,
|
||||
quantizeGenericBits: 12,
|
||||
uncompressedFallback: false,
|
||||
unifiedQuantization: false
|
||||
};
|
||||
Reference in New Issue
Block a user