gltf-pipeline
Version:
Content pipeline tools for optimizing glTF assets.
201 lines (178 loc) • 7.69 kB
JavaScript
'use strict';
const Cesium = require('cesium');
const hashObject = require('object-hash');
const addBuffer = require('./addBuffer');
const addToArray = require('./addToArray');
const findAccessorMinMax = require('./findAccessorMinMax');
const ForEach = require('./ForEach');
const readAccessorPacked = require('./readAccessorPacked');
const removeUnusedElements = require('./removeUnusedElements');
const clone = Cesium.clone;
const ComponentDatatype = Cesium.ComponentDatatype;
const defaultValue = Cesium.defaultValue;
const defined = Cesium.defined;
const numberOfComponentsForType = Cesium.numberOfComponentsForType;
module.exports = splitPrimitives;
/**
* Splits primitives that reference different indices within the same mesh.
* This stage is used internally by compressDracoMeshes.
*
* @param {Object} gltf A javascript object containing a glTF asset.
* @returns {Object} glTF with primitives split.
*
* @private
*/
function splitPrimitives(gltf) {
let i;
let hash;
let primitives;
let primitivesLength;
const hashPrimitives = {};
const duplicatePrimitives = {};
const primitivesWithSharedAttributes = {};
let primitivesSplit = false;
ForEach.mesh(gltf, function(mesh) {
ForEach.meshPrimitive(mesh, function(primitive) {
const hashPrimitive = hashObject({
indices: primitive.indices,
attributes: primitive.attributes,
targets: primitive.targets,
mode: primitive.mode
});
if (defined(hashPrimitives[hashPrimitive])) {
const duplicates = defaultValue(duplicatePrimitives[hashPrimitive], []);
duplicatePrimitives[hashPrimitive] = duplicates;
duplicates.push(primitive);
return;
}
hashPrimitives[hashPrimitive] = primitive;
const hashAttributes = hashObject({
attributes: primitive.attributes,
targets: primitive.targets,
mode: primitive.mode
});
const primitivesShared = defaultValue(primitivesWithSharedAttributes[hashAttributes], []);
primitivesWithSharedAttributes[hashAttributes] = primitivesShared;
primitivesShared.push(primitive);
});
});
for (hash in primitivesWithSharedAttributes) {
if (primitivesWithSharedAttributes.hasOwnProperty(hash)) {
primitives = primitivesWithSharedAttributes[hash];
primitivesLength = primitives.length;
if (primitivesLength === 1) {
continue;
}
primitivesSplit = true;
const attributeData = readAttributes(gltf, primitives[0]);
const targetData = readTargets(gltf, primitives[0]);
for (i = 0; i < primitivesLength; ++i) {
splitPrimitive(gltf, primitives[i], attributeData, targetData);
}
}
}
if (primitivesSplit) {
for (hash in duplicatePrimitives) {
if (duplicatePrimitives.hasOwnProperty(hash)) {
const primitiveToCopy = hashPrimitives[hash];
primitives = duplicatePrimitives[hash];
primitivesLength = primitives.length;
for (i = 0; i < primitivesLength; ++i) {
copyPrimitive(primitiveToCopy, primitives[i]);
}
}
}
removeUnusedElements(gltf);
}
return gltf;
}
function copyPrimitive(primitiveToCopy, primitive) {
primitive.indices = primitiveToCopy.indices;
ForEach.meshPrimitiveAttribute(primitiveToCopy, function(accessorId, semantic) {
primitive.attributes[semantic] = accessorId;
});
ForEach.meshPrimitiveTarget(primitiveToCopy, function(target, targetIndex) {
ForEach.meshPrimitiveTargetAttribute(target, function(accessorId, semantic) {
primitive.targets[targetIndex][semantic] = accessorId;
});
});
}
function splitPrimitive(gltf, primitive, attributeData, targetData) {
const indicesAccessor = gltf.accessors[primitive.indices];
const indices = readAccessorPacked(gltf, indicesAccessor);
const mappedIndices = {};
const newIndices = [];
let uniqueIndicesLength = 0;
const indicesLength = indices.length;
for (let i = 0; i < indicesLength; ++i) {
const index = indices[i];
let mappedIndex = mappedIndices[index];
if (!defined(mappedIndex)) {
mappedIndex = uniqueIndicesLength++;
mappedIndices[index] = mappedIndex;
}
newIndices.push(mappedIndex);
}
primitive.indices = createNewAccessor(gltf, indicesAccessor, newIndices);
ForEach.meshPrimitiveAttribute(primitive, function(accessorId, semantic) {
primitive.attributes[semantic] = createNewAttribute(gltf, accessorId, semantic, attributeData, mappedIndices, uniqueIndicesLength);
});
ForEach.meshPrimitiveTarget(primitive, function(target, targetIndex) {
ForEach.meshPrimitiveTargetAttribute(target, function(accessorId, semantic) {
target[semantic] = createNewAttribute(gltf, accessorId, semantic, targetData[targetIndex], mappedIndices, uniqueIndicesLength);
});
});
}
function createNewAttribute(gltf, accessorId, semantic, attributeData, mappedIndices, uniqueIndicesLength) {
const accessor = gltf.accessors[accessorId];
const numberOfComponents = numberOfComponentsForType(accessor.type);
const dataArray = attributeData[semantic];
const newDataArray = new Array(uniqueIndicesLength * numberOfComponents);
remapData(dataArray, newDataArray, mappedIndices, numberOfComponents);
return createNewAccessor(gltf, accessor, newDataArray);
}
function remapData(dataArray, newDataArray, mappedIndices, numberOfComponents) {
for (const index in mappedIndices) {
if (mappedIndices.hasOwnProperty(index)) {
const mappedIndex = mappedIndices[index];
for (let i = 0; i < numberOfComponents; ++i) {
newDataArray[mappedIndex * numberOfComponents + i] = dataArray[index * numberOfComponents + i];
}
}
}
}
function createNewAccessor(gltf, oldAccessor, dataArray) {
const componentType = oldAccessor.componentType;
const type = oldAccessor.type;
const numberOfComponents = numberOfComponentsForType(type);
const count = dataArray.length / numberOfComponents;
const newBuffer = Buffer.from(ComponentDatatype.createTypedArray(componentType, dataArray).buffer);
const newBufferViewId = addBuffer(gltf, newBuffer);
const accessor = clone(oldAccessor, true);
const accessorId = addToArray(gltf.accessors, accessor);
accessor.bufferView = newBufferViewId;
accessor.byteOffset = 0;
accessor.count = count;
const minMax = findAccessorMinMax(gltf, accessor);
accessor.min = minMax.min;
accessor.max = minMax.max;
return accessorId;
}
function readAttributes(gltf, primitive) {
const attributeData = {};
ForEach.meshPrimitiveAttribute(primitive, function(accessorId, semantic) {
attributeData[semantic] = readAccessorPacked(gltf, gltf.accessors[accessorId]);
});
return attributeData;
}
function readTargets(gltf, primitive) {
const targetData = [];
ForEach.meshPrimitiveTarget(primitive, function(target) {
const attributeData = {};
targetData.push(attributeData);
ForEach.meshPrimitiveTargetAttribute(target, function(accessorId, semantic) {
attributeData[semantic] = readAccessorPacked(gltf, gltf.accessors[accessorId]);
});
});
return targetData;
}