@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 +4 -0
- package/dist/cache/cache.js +32 -4
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.d.ts +3 -3
- package/dist/transforms/needle_mesh_transform.js +1 -0
- package/dist/transforms/needle_progressive.js +41 -11
- package/dist/utils/version.gen.d.ts +1 -1
- package/dist/utils/version.gen.js +1 -1
- package/package.json +5 -5
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
|
|
package/dist/cache/cache.js
CHANGED
|
@@ -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
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
1
|
+
export declare const version = "2.11.0-beta";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "2.
|
|
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.
|
|
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
|
|
52
|
-
"@gltf-transform/extensions": "4.1
|
|
53
|
-
"@gltf-transform/functions": "4.1
|
|
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": "
|
|
58
|
+
"meshoptimizer": "0.24.0",
|
|
59
59
|
"micromatch": "^4.0.5",
|
|
60
60
|
"ndarray": "^1.0.19",
|
|
61
61
|
"ndarray-lanczos": "^0.3.0",
|