@needle-tools/gltf-build-pipeline 2.10.2-next.efa41cf → 2.11.0-beta

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,10 @@ 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.11.0] - 2025-08-01
8
+ - add: store densities per primitive
9
+ - fix: exception during cache key calculation
10
+
7
11
  ## [2.10.2] - 2025-07-17
8
12
  - fix: issue where draco compression fails with `[KHR_draco_mesh_compression] Accessor cannot be shared by compressed and uncompressed primitives`
9
13
 
@@ -252,7 +252,7 @@ function getCacheDirectory(root) {
252
252
  }
253
253
  return tmpDir;
254
254
  }
255
- function hashObject(obj) {
255
+ function hashObject(obj, depth = 0) {
256
256
  if (obj instanceof Accessor) {
257
257
  const buffer = obj.getArray();
258
258
  return hashBuffer(buffer.buffer);
@@ -260,14 +260,42 @@ function hashObject(obj) {
260
260
  else if (obj == null) {
261
261
  return 0;
262
262
  }
263
+ if (typeof obj !== 'object') {
264
+ return hashString(String(obj));
265
+ }
263
266
  try {
264
267
  const str = JSON.stringify(obj);
265
268
  return hashString(str);
266
269
  }
267
270
  catch (err) {
268
- console.error(err);
269
- debugger;
270
- throw err;
271
+ console.warn("WARN: [Cache] error during hashing of object:\n" + err);
272
+ if (debugLogs)
273
+ debugger;
274
+ // Fallback to a simple hash of the keys and values
275
+ // TODO: maybe we want to exclude this object now from the cache if it fails to serialize?
276
+ let fallbackHashj = 0;
277
+ if (typeof obj === 'object') {
278
+ if (obj === null)
279
+ return 0;
280
+ for (const key in obj) {
281
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
282
+ const value = obj[key];
283
+ fallbackHashj += hashString(key);
284
+ if (typeof value === 'object') {
285
+ if (depth > 10) {
286
+ fallbackHashj += hashString("[object]");
287
+ }
288
+ else {
289
+ fallbackHashj += hashObject(value, depth + 1);
290
+ }
291
+ }
292
+ else {
293
+ fallbackHashj += hashString(String(value));
294
+ }
295
+ }
296
+ }
297
+ }
298
+ return fallbackHashj;
271
299
  }
272
300
  }
273
301
  function hashBuffer(buffer, offset, length) {
@@ -20,13 +20,13 @@ export type ProgressiveTextureModel = ProgressiveLoadingInfo & {
20
20
  lods?: Array<ProgressiveTextureLodEntry>;
21
21
  };
22
22
  export type ProgressiveMeshModel = ProgressiveLoadingInfo & {
23
- /** LOD0 vertex density. TODO: remove this since we now have a LODs array. This is just for supporting older engine versions */
24
- density: number;
25
23
  lods?: Array<LodInfo & {
26
- /** Mesh vertex density */
24
+ /** @deprecated Mesh vertex density TODO: remove in the future */
27
25
  density: number;
28
26
  vertexCount: number;
29
27
  indexCount: number;
28
+ /** Densities per primitive */
29
+ densities?: Array<number>;
30
30
  }>;
31
31
  };
32
32
  export declare class NEEDLE_progressive extends Extension {
@@ -194,6 +194,7 @@ export async function compressPrimitive(document, mesh, prim, index, weldOptions
194
194
  if (!res)
195
195
  return;
196
196
  }
197
+ // TODO: enable regularize once it's available: https://github.com/zeux/meshoptimizer/pull/914/files
197
198
  logger.debug(`simplify: primitive[${index}] of mesh "${mesh.getName()}" → ratio: ${meshOptions.ratio}, error: ${meshOptions.error}, lockBorder: ${meshOptions.lockBorder}`);
198
199
  return simplifyPrimitive(prim, meshOptions);
199
200
  }
@@ -546,10 +546,10 @@ export function make_progressive_meshes(filePath, _options, opts) {
546
546
  },
547
547
  ];
548
548
  for (let l = 0; l < MAX_LODS; l++) {
549
- // const t = l / MAX_LODS;
549
+ const t = l / (MAX_LODS - 1);
550
550
  const latest = LODS[LODS.length - 1];
551
551
  const ratio = latest.ratio * .5;
552
- let error = Math.min(.013 * (l + 1), 0.003);
552
+ let error = 0.003 + t * .01; // using max 1%, example mesh: https://linear.app/needle/issue/NE-6653
553
553
  if (opts.config.usecase === "product") {
554
554
  error *= .5;
555
555
  }
@@ -688,7 +688,8 @@ export function make_progressive_meshes(filePath, _options, opts) {
688
688
  let primitiveIndex = 0;
689
689
  const lodLevelInFile = generatedLODInfos.length;
690
690
  const clonedData = { primitives: [], weights: [...mesh.getWeights()] };
691
- for (const prim of mesh.listPrimitives()) {
691
+ // const primitiveDensities = new Array<number>(mesh.listPrimitives().length);
692
+ mesh.listPrimitives().forEach((prim, index) => {
692
693
  const data = clonePrimitiveData(prim, logger);
693
694
  if (data != null) {
694
695
  clonedData.primitives.push(data);
@@ -697,10 +698,11 @@ export function make_progressive_meshes(filePath, _options, opts) {
697
698
  const lastLod = LODS[lodLevelInFile];
698
699
  // TODO: this should be run after simplification but then we need to change the runtime to use the LOD 0 density
699
700
  const density = calculateMeshDensity(prim);
701
+ // primitiveDensities[index] = density;
700
702
  maxDensity = Math.max(maxDensity, density);
701
703
  const task = simplifyMesh(doc, mesh, prim, primitiveIndex++, lastLod);
702
704
  tasks.push(task);
703
- }
705
+ });
704
706
  await Promise.all(tasks);
705
707
  tasks.length = 0;
706
708
  const { totalVertexCount: totalVertexCountAfter } = getMeshInformation(mesh);
@@ -741,11 +743,11 @@ export function make_progressive_meshes(filePath, _options, opts) {
741
743
  path: l.path,
742
744
  hash: l.content_hash.toString(),
743
745
  density: l.density,
746
+ densities: l.densities,
744
747
  vertexCount: l.lodMeshInfo?.totalVertexCount,
745
748
  indexCount: l.lodMeshInfo?.totalIndexCount,
746
749
  };
747
750
  }),
748
- density: maxDensity,
749
751
  });
750
752
  mesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, model);
751
753
  index++;
@@ -825,7 +827,16 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
825
827
  if (cachedValue && meta) {
826
828
  logger.info(`[${NAME}] LOD ${level} loaded from cache for \"${mesh.getName()}\"`);
827
829
  writeFileSync(outPath, cachedValue);
828
- return { level, path: fileName, success: true, cacheKey, content_hash: hash, lodMeshInfo: meta.meshInfo, density: meta.density };
830
+ return {
831
+ level,
832
+ path: fileName,
833
+ success: true,
834
+ cacheKey,
835
+ content_hash: hash,
836
+ lodMeshInfo: meta.meshInfo,
837
+ density: meta.density,
838
+ densities: meta.densities
839
+ };
829
840
  }
830
841
  }
831
842
  const newDoc = new Document();
@@ -939,13 +950,24 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
939
950
  const newMeshInformation = getMeshInformation(newMesh);
940
951
  if (newMeshInformation.totalVertexCount < 4) {
941
952
  logger.error(`[${NAME}] mesh LOD ${level} is has less than 4 vertices → skipping`);
942
- return { level, path: fileName, success: false, cacheKey: cacheKey, content_hash: hash, lodMeshInfo: null, density: null, };
953
+ return {
954
+ level,
955
+ path: fileName,
956
+ success: false,
957
+ cacheKey: cacheKey,
958
+ content_hash: hash,
959
+ lodMeshInfo: null,
960
+ density: null,
961
+ densities: null
962
+ };
943
963
  }
944
964
  let density = 0;
945
- for (const prim of newMesh.listPrimitives()) {
965
+ const densities = new Array(newMesh.listPrimitives().length);
966
+ newMesh.listPrimitives().forEach((prim, index) => {
946
967
  const d = calculateMeshDensity(prim);
947
968
  density = Math.max(density, d);
948
- }
969
+ densities[index] = d;
970
+ });
949
971
  const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
950
972
  if (!progressiveExt || !progressiveExt.createMesh) {
951
973
  logger.error("Failed to create progressive extension");
@@ -953,7 +975,6 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
953
975
  }
954
976
  newMesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.createMesh({
955
977
  guid: guid,
956
- density: density,
957
978
  }));
958
979
  // we need to compress the mesh with the same compression method as the original file
959
980
  const model = determineMeshCompression(doc, fileName);
@@ -977,7 +998,16 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
977
998
  addToCache(cacheKey, bytes);
978
999
  addMetaToCache(cacheKey, { meshInfo: newMeshInformation, density: density });
979
1000
  }
980
- return { level, path: fileName, success: true, lodMeshInfo: newMeshInformation, cacheKey: cacheKey, content_hash: hash, density: density };
1001
+ return {
1002
+ level,
1003
+ path: fileName,
1004
+ success: true,
1005
+ lodMeshInfo: newMeshInformation,
1006
+ cacheKey: cacheKey,
1007
+ content_hash: hash,
1008
+ density: density,
1009
+ densities: densities
1010
+ };
981
1011
  }
982
1012
  async function simplifyMesh(doc, mesh, prim, index, lodinfo) {
983
1013
  const weldOptions = {
@@ -1 +1 @@
1
- export declare const version = "2.10.2-next.efa41cf";
1
+ export declare const version = "2.11.0-beta";
@@ -1 +1 @@
1
- export const version = "2.10.2-next.efa41cf";
1
+ export const version = "2.11.0-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "2.10.2-next.efa41cf",
3
+ "version": "2.11.0-beta",
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",
@@ -48,14 +48,14 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@donmccurdy/caporal": "~0.0.10",
51
- "@gltf-transform/core": "4.1.4",
52
- "@gltf-transform/extensions": "4.1.4",
53
- "@gltf-transform/functions": "4.1.4",
51
+ "@gltf-transform/core": "4.2.1",
52
+ "@gltf-transform/extensions": "4.2.1",
53
+ "@gltf-transform/functions": "4.2.1",
54
54
  "command-exists": "^1.2.9",
55
55
  "command-line-args": "^5.2.1",
56
56
  "draco3dgltf": "^1.5.3",
57
57
  "glob": "^7.2.0",
58
- "meshoptimizer": "^0.18.1",
58
+ "meshoptimizer": "0.24.0",
59
59
  "micromatch": "^4.0.5",
60
60
  "ndarray": "^1.0.19",
61
61
  "ndarray-lanczos": "^0.3.0",