@needle-tools/gltf-build-pipeline 2.8.0-alpha.2 → 2.8.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to this package will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.8.1-alpha.0] - 2024-03-26
8
+ - Fix: Issue where cache key gets longer depending on the primitive count
9
+ - Fix: Cache key not taking index buffer into account
10
+ - Fix: Caching of mesh LODs did clone shared mesh attributes for every primitive instead of re-using previous clones
11
+
7
12
  ## [2.8.0-alpha.2] - 2024-03-04
8
13
  - change: use webp compression for textures smaller than 32x32
9
14
  - fix: webp compression for small textures (64x64)
@@ -202,8 +202,9 @@ export function getHash(property, level = 0) {
202
202
  const weights = property.getWeights();
203
203
  hash += hashObject(weights);
204
204
  const primitives = property.listPrimitives();
205
- for (const prim of primitives)
206
- hashStr += getHash(prim, level + 1);
205
+ for (const prim of primitives) {
206
+ hash += hashString(getHash(prim, level + 1));
207
+ }
207
208
  }
208
209
  else if (property instanceof Primitive) {
209
210
  const indices = property.getIndices();
@@ -222,11 +223,19 @@ export function getHash(property, level = 0) {
222
223
  targets,
223
224
  semantics: semanticsNames
224
225
  });
226
+ if (indices) {
227
+ const indexBuffer = indices.getArray();
228
+ if (indexBuffer) {
229
+ hash += hashBuffer(indexBuffer.buffer, indexBuffer.byteOffset, indexBuffer.byteLength);
230
+ }
231
+ }
225
232
  // We can not pass the whole buffer into hashObject since very large meshes will cause errors when trying to stringify
226
233
  // See https://linear.app/needle/issue/NE-6414
227
234
  for (const sem of semantics) {
228
- const arr = property.getAttribute(sem).getArray();
229
- hash += hashBuffer(arr.buffer, arr.byteOffset, arr.byteLength);
235
+ const arr = property.getAttribute(sem)?.getArray();
236
+ if (arr) {
237
+ hash += hashBuffer(arr.buffer, arr.byteOffset, arr.byteLength);
238
+ }
230
239
  }
231
240
  }
232
241
  }
@@ -4,6 +4,7 @@ import { MeshoptEncoder, MeshoptSimplifier } from "meshoptimizer";
4
4
  import { NEEDLE_mesh_compression, NEEDLE_mesh_compression_model_root } from "../extensions/index.js";
5
5
  import { weldPrimitive } from "../functions/weld.3.js";
6
6
  import { calculateMeshDensity } from "../utils/mesh.js";
7
+ import { isMeshLOD } from "./needle_progressive.js";
7
8
  const NAME = 'needle_mesh_transform';
8
9
  /**
9
10
  * Which compression type for a model should be used. This should be respected when creating LOD meshes and the appropriate extension should be added to the LOD mesh so it receives the same mesh compression as the parent mesh
@@ -66,6 +67,7 @@ export const needle_mesh_transform = function (context) {
66
67
  };
67
68
  };
68
69
  async function applyMeshCompression(document, transform_context, context) {
70
+ const isLOD = isMeshLOD(context.file);
69
71
  const compressionModel = determineMeshCompression(document, context.file); // document.getRoot().getExtension(NEEDLE_mesh_compression.EXTENSION_NAME) as NEEDLE_mesh_compression_property;
70
72
  if (compressionModel) {
71
73
  // const rootModel = rootExtension.model as NEEDLE_mesh_compression_model_root;
@@ -76,7 +78,9 @@ async function applyMeshCompression(document, transform_context, context) {
76
78
  }
77
79
  document.getLogger().info(`[${NAME}]: ${compressionModel.compression.toUpperCase()} → will apply ${compressionModel.compression} mesh compression`);
78
80
  if (compressionModel.compression === "draco") {
79
- const transform = draco();
81
+ const transform = draco({
82
+ quantizationVolume: isLOD ? "scene" : undefined
83
+ });
80
84
  return transform(document, transform_context);
81
85
  }
82
86
  else if (compressionModel.compression === "meshopt") {
@@ -87,7 +91,10 @@ async function applyMeshCompression(document, transform_context, context) {
87
91
  }
88
92
  // by default we still want to run draco if the root doesnt have a compression model configured
89
93
  document.getLogger().info(`[${NAME}]: → will apply draco mesh compression (default)`);
90
- return draco()(document, transform_context);
94
+ const transform = draco({
95
+ quantizationVolume: isLOD ? "scene" : undefined
96
+ });
97
+ return transform(document, transform_context);
91
98
  }
92
99
  async function simplifierTransform(document, _context, meshContext) {
93
100
  const simplifier = MeshoptSimplifier;
@@ -660,7 +660,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
660
660
  const vertexDifference = lastMeshInfo.totalVertexCount - meshInfo.totalVertexCount;
661
661
  const diff01 = vertexDifference / lastMeshInfo.totalVertexCount;
662
662
  if (vertexDifference < 0) {
663
- console.log(`WARN: ❌ [${NAME}] Flag mesh LOD ${level}: vertex count did increase (${-vertexDifference.toFixed(0)} vertices, ${(-diff01 * 100).toFixed(4)}%) → LOD ${level} will be omitted. \"${mesh.getName()}\" at \"${lodInfo.path}\" ${JSON.stringify(lastMeshInfo)} → ${JSON.stringify(meshInfo)}`);
663
+ console.log(`WARN: [${NAME}] Flag mesh LOD ${level}: vertex count did increase (${-vertexDifference.toFixed(0)} vertices, ${(-diff01 * 100).toFixed(4)}%) → LOD ${level} will be omitted. \"${mesh.getName()}\" at \"${lodInfo.path}\" ${JSON.stringify(lastMeshInfo)} → ${JSON.stringify(meshInfo)}`);
664
664
  shouldOmitLevel = true;
665
665
  }
666
666
  else if (vertexDifference < 32 || diff01 < 0.05) {
@@ -850,27 +850,13 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
850
850
  }
851
851
  const tasks = new Array();
852
852
  // let didRunSimplification = false;
853
+ const clonedAttributes = new Map();
853
854
  for (const prim of primitives) {
854
855
  const newPrim = newDoc.createPrimitive();
855
856
  newMesh.addPrimitive(newPrim);
856
857
  newPrim.setName(prim.getName());
857
858
  newPrim.setMode(prim.getMode());
858
859
  newPrim.setMaterial(newMat);
859
- const semantics = prim.listSemantics();
860
- for (let i = 0; i < semantics.length; i++) {
861
- const attr = prim.getAttribute(semantics[i]);
862
- if (attr) {
863
- const newAttr = newDoc.createAccessor();
864
- newAttr.setArray(attr.getArray());
865
- newAttr.setType(attr.getType());
866
- newAttr.setSparse(attr.getSparse());
867
- newAttr.setNormalized(attr.getNormalized());
868
- newPrim.setAttribute(semantics[i], newAttr);
869
- }
870
- else {
871
- logger.warn(`[${NAME}] Missing attribute ${semantics[i]} in mesh LOD ${level}`);
872
- }
873
- }
874
860
  const indices = prim.getIndices();
875
861
  if (indices) {
876
862
  const newIndices = newDoc.createAccessor();
@@ -880,6 +866,28 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
880
866
  newIndices.setNormalized(indices.getNormalized());
881
867
  newPrim.setIndices(newIndices);
882
868
  }
869
+ const semantics = prim.listSemantics();
870
+ for (let i = 0; i < semantics.length; i++) {
871
+ const attr = prim.getAttribute(semantics[i]);
872
+ if (attr) {
873
+ if (clonedAttributes.has(attr)) {
874
+ const cloned = clonedAttributes.get(attr);
875
+ newPrim.setAttribute(semantics[i], cloned);
876
+ }
877
+ else {
878
+ const newAttr = newDoc.createAccessor();
879
+ newAttr.setArray(attr.getArray());
880
+ newAttr.setType(attr.getType());
881
+ newAttr.setSparse(attr.getSparse());
882
+ newAttr.setNormalized(attr.getNormalized());
883
+ newPrim.setAttribute(semantics[i], newAttr);
884
+ clonedAttributes.set(attr, newAttr);
885
+ }
886
+ }
887
+ else {
888
+ logger.warn(`[${NAME}] Missing attribute ${semantics[i]} in mesh LOD ${level}`);
889
+ }
890
+ }
883
891
  const targets = prim.listTargets();
884
892
  for (const target of targets) {
885
893
  const newTarget = newDoc.createPrimitiveTarget(target.getName());
@@ -964,7 +972,8 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
964
972
  // add to cache
965
973
  if (cacheKey != undefined) {
966
974
  const bytes = readFileSync(outPath);
967
- logger.info(`[${NAME}] Add LOD ${level} to cache for \"${mesh.getName()}\" as ${cacheKey}`);
975
+ const sizeInMB = (bytes.byteLength / 1024 / 1024).toFixed(2);
976
+ logger.info(`[${NAME}] Add LOD ${level} to cache (${sizeInMB} MB) for \"${mesh.getName()}\" as ${cacheKey}`);
968
977
  addToCache(cacheKey, bytes);
969
978
  addMetaToCache(cacheKey, { meshInfo: newMeshInformation, density: density });
970
979
  }
@@ -1 +1 @@
1
- export declare const version = "2.8.0-alpha.2";
1
+ export declare const version = "2.8.1-alpha.1";
@@ -1 +1 @@
1
- export const version = "2.8.0-alpha.2";
1
+ export const version = "2.8.1-alpha.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "2.8.0-alpha.2",
3
+ "version": "2.8.1-alpha.1",
4
4
  "description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",