UNPKG

gltf-pipeline

Version:

Content pipeline tools for optimizing glTF assets.

245 lines (222 loc) 7.48 kB
"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; }