gltf-pipeline
Version:
Content pipeline tools for optimizing glTF assets.
245 lines (222 loc) • 7.48 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 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 = 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 =
primitivesWithSharedAttributes[hashAttributes] ?? [];
primitivesWithSharedAttributes[hashAttributes] = primitivesShared;
primitivesShared.push(primitive);
});
});
for (hash in primitivesWithSharedAttributes) {
if (
Object.prototype.hasOwnProperty.call(primitivesWithSharedAttributes, 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 (Object.prototype.hasOwnProperty.call(duplicatePrimitives, hash)) {
const primitiveToCopy = hashPrimitives[hash];
primitives = duplicatePrimitives[hash];
primitivesLength = primitives.length;
for (i = 0; i < primitivesLength; ++i) {
copyPrimitive(primitiveToCopy, primitives[i]);
}
}
}
removeUnusedElements(gltf, ["accessor", "bufferView", "buffer"]);
}
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 (Object.prototype.hasOwnProperty.call(mappedIndices, 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;
}