@needle-tools/gltf-build-pipeline 1.4.1-beta.4 → 1.4.1-beta.6
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 +7 -2
- package/dist/cache/cache.d.ts +5 -0
- package/dist/cache/cache.js +90 -26
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/transforms/needle_mesh_transform.js +1 -0
- package/dist/transforms/needle_progressive.d.ts +3 -0
- package/dist/transforms/needle_progressive.js +48 -21
- package/index.mjs +12 -4
- package/pack-gltf.mjs +36 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,10 +4,15 @@ 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
|
-
## [1.4.1-beta.
|
|
7
|
+
## [1.4.1-beta.6] - 2023-03-15
|
|
8
|
+
- add: caching support for mesh LODs
|
|
9
|
+
|
|
10
|
+
## [1.4.1-beta.5] - 2023-03-15
|
|
8
11
|
- add: auto LODs now calculate mesh density
|
|
9
|
-
- change: generate 6 LOD level
|
|
12
|
+
- change: generate 6 LOD level
|
|
13
|
+
- change: use ratio 0.5 of the previous LOD level
|
|
10
14
|
- change: bump gltf-transform to 3.10.1 to fix simplifier
|
|
15
|
+
- change: add sparse transform to progressive transform
|
|
11
16
|
|
|
12
17
|
## [1.4.1-beta.3] - 2023-03-14
|
|
13
18
|
- fix: wait for mesh simplifier to be ready
|
package/dist/cache/cache.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { ExtensibleProperty } from "@gltf-transform/core";
|
|
2
|
+
/**
|
|
3
|
+
* Get the available space on the cache directory in bytes.
|
|
4
|
+
*/
|
|
2
5
|
export declare function clearCache(): void;
|
|
6
|
+
export declare function ensureCacheSize(maxSizeMB: number): void;
|
|
3
7
|
export declare function addToCache(key: string, bytes: Uint8Array): void;
|
|
4
8
|
export declare function tryGetFromCache(key: string): Uint8Array | null;
|
|
5
9
|
export declare function getKey(property: ExtensibleProperty): string;
|
|
10
|
+
export declare function getHash(property: ExtensibleProperty | object): number;
|
package/dist/cache/cache.js
CHANGED
|
@@ -1,16 +1,59 @@
|
|
|
1
|
-
import { Texture, Primitive } from "@gltf-transform/core";
|
|
1
|
+
import { ExtensibleProperty, Texture, Primitive, Accessor, Mesh } from "@gltf-transform/core";
|
|
2
2
|
import tmp from 'tmp';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
// import * as CHECKDISCSPACE from 'check-disk-space'
|
|
6
|
+
/**
|
|
7
|
+
* Get the available space on the cache directory in bytes.
|
|
8
|
+
*/
|
|
9
|
+
// export async function getCacheAvailableSpaceOnDisc(): Promise<number> {
|
|
10
|
+
// try {
|
|
11
|
+
// const dir = getCacheDirectory();
|
|
12
|
+
// const res = await CHECKDISCSPACE.default(dir).catch(() => { return { free: 0 } });
|
|
13
|
+
// return res.free;
|
|
14
|
+
// }
|
|
15
|
+
// catch (e) {
|
|
16
|
+
// console.error(`Error checking disk space: ${e}`);
|
|
17
|
+
// }
|
|
18
|
+
// return 0;
|
|
19
|
+
// }
|
|
5
20
|
export function clearCache() {
|
|
6
21
|
const dir = getCacheDirectory();
|
|
7
22
|
fs.rmSync(dir, { recursive: true });
|
|
8
23
|
console.log(`Cleared cache at ${dir}`);
|
|
9
24
|
}
|
|
25
|
+
export function ensureCacheSize(maxSizeMB) {
|
|
26
|
+
const dir = getCacheDirectory();
|
|
27
|
+
let totalSize = 0;
|
|
28
|
+
const files = fs.readdirSync(dir).map(file => {
|
|
29
|
+
const path = `${dir}/${file}`;
|
|
30
|
+
const stats = fs.statSync(path);
|
|
31
|
+
totalSize += stats.size;
|
|
32
|
+
return { path, size: stats.size };
|
|
33
|
+
});
|
|
34
|
+
const maxSizeInBytes = maxSizeMB * 1024 * 1024;
|
|
35
|
+
if (totalSize < maxSizeInBytes)
|
|
36
|
+
return;
|
|
37
|
+
files.sort((a, b) => {
|
|
38
|
+
const mtimeA = fs.statSync(a.path).mtimeMs;
|
|
39
|
+
const mtimeB = fs.statSync(b.path).mtimeMs;
|
|
40
|
+
return mtimeA - mtimeB;
|
|
41
|
+
});
|
|
42
|
+
const targetSize = maxSizeInBytes * 0.9;
|
|
43
|
+
// remove oldest files first
|
|
44
|
+
while (totalSize > targetSize) {
|
|
45
|
+
const file = files.shift();
|
|
46
|
+
if (!file)
|
|
47
|
+
break;
|
|
48
|
+
fs.rmSync(file.path);
|
|
49
|
+
totalSize -= file.size;
|
|
50
|
+
}
|
|
51
|
+
console.log(`Cache size reduced to ${(totalSize / (1024 * 1024)).toFixed(1)} MB`);
|
|
52
|
+
}
|
|
10
53
|
export function addToCache(key, bytes) {
|
|
11
54
|
const dir = getCacheDirectory();
|
|
12
55
|
const path = `${dir}/${key}.bin`;
|
|
13
|
-
console.log(`Added ${(bytes.byteLength / 1024).toFixed(1)} KB to cache at ${path}`);
|
|
56
|
+
console.log(`Added ${(bytes.byteLength / 1024).toFixed(1)} KB to cache at \"${path}\"`);
|
|
14
57
|
fs.writeFileSync(path, bytes);
|
|
15
58
|
}
|
|
16
59
|
export function tryGetFromCache(key) {
|
|
@@ -23,37 +66,54 @@ export function tryGetFromCache(key) {
|
|
|
23
66
|
return null;
|
|
24
67
|
}
|
|
25
68
|
export function getKey(property) {
|
|
26
|
-
let hash = 0;
|
|
27
69
|
let prefix = "prop";
|
|
28
|
-
const extensions = property.listExtensions();
|
|
29
|
-
const extras = property.getExtras();
|
|
30
|
-
// const slots = listTextureSlots(doc, texture);
|
|
31
|
-
// const channels = getTextureChannelMask(doc, texture);
|
|
32
|
-
hash += hashObject({
|
|
33
|
-
extensions,
|
|
34
|
-
extras,
|
|
35
|
-
});
|
|
36
70
|
if (property instanceof Texture) {
|
|
37
71
|
prefix = "image";
|
|
38
|
-
const image = property.getImage();
|
|
39
|
-
const bytes = image?.buffer;
|
|
40
|
-
if (bytes)
|
|
41
|
-
hash += hashBuffer(bytes, image.byteOffset, image.byteLength);
|
|
42
72
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
let hash = getHash(property);
|
|
74
|
+
const key = `${prefix}-${hash}`;
|
|
75
|
+
return key;
|
|
76
|
+
}
|
|
77
|
+
export function getHash(property) {
|
|
78
|
+
let hash = 0;
|
|
79
|
+
if (property instanceof ExtensibleProperty) {
|
|
80
|
+
const extensions = property.listExtensions();
|
|
81
|
+
const extras = property.getExtras();
|
|
82
|
+
// const slots = listTextureSlots(doc, texture);
|
|
83
|
+
// const channels = getTextureChannelMask(doc, texture);
|
|
48
84
|
hash += hashObject({
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
targets
|
|
85
|
+
extensions,
|
|
86
|
+
extras,
|
|
52
87
|
});
|
|
88
|
+
if (property instanceof Texture) {
|
|
89
|
+
const image = property.getImage();
|
|
90
|
+
const bytes = image?.buffer;
|
|
91
|
+
if (bytes)
|
|
92
|
+
hash += hashBuffer(bytes, image.byteOffset, image.byteLength);
|
|
93
|
+
}
|
|
94
|
+
else if (property instanceof Mesh) {
|
|
95
|
+
const weights = property.getWeights();
|
|
96
|
+
hash += hashObject(weights);
|
|
97
|
+
const primitives = property.listPrimitives();
|
|
98
|
+
hash += hashObject(primitives);
|
|
99
|
+
}
|
|
100
|
+
else if (property instanceof Primitive) {
|
|
101
|
+
const indices = property.getIndices();
|
|
102
|
+
const attributes = property.listAttributes();
|
|
103
|
+
const targets = property.listTargets();
|
|
104
|
+
const semantics = property.listSemantics();
|
|
105
|
+
hash += hashObject({
|
|
106
|
+
indices,
|
|
107
|
+
attributes,
|
|
108
|
+
targets,
|
|
109
|
+
semantics
|
|
110
|
+
});
|
|
111
|
+
}
|
|
53
112
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
113
|
+
else {
|
|
114
|
+
hash += hashObject(property);
|
|
115
|
+
}
|
|
116
|
+
return hash;
|
|
57
117
|
}
|
|
58
118
|
function getCacheDirectory() {
|
|
59
119
|
const tmpDir = path.join(tmp.tmpdir, "Needle", "build-pipeline-cache");
|
|
@@ -63,6 +123,10 @@ function getCacheDirectory() {
|
|
|
63
123
|
return tmpDir;
|
|
64
124
|
}
|
|
65
125
|
function hashObject(obj) {
|
|
126
|
+
if (obj instanceof Accessor) {
|
|
127
|
+
const buffer = obj.getArray();
|
|
128
|
+
return hashBuffer(buffer);
|
|
129
|
+
}
|
|
66
130
|
const str = JSON.stringify(obj);
|
|
67
131
|
let hash = 0;
|
|
68
132
|
for (let i = 0; i < str.length; i++) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { NEEDLE_mesh_compression } from "../extensions/index.js";
|
|
|
5
5
|
const NAME = 'needle_mesh_transform';
|
|
6
6
|
export const needle_mesh_transform = function (_options) {
|
|
7
7
|
return async (document, _context) => {
|
|
8
|
+
// TODO: needs to be moved into NEEDLE_progressive
|
|
8
9
|
await simplifierTransform(document, _context, _options);
|
|
9
10
|
await applyMeshCompression(document, _context);
|
|
10
11
|
};
|
|
@@ -32,4 +32,7 @@ export declare enum TextureResizeFilter {
|
|
|
32
32
|
export declare const TEXTURE_RESIZE_DEFAULTS: TextureResizeOptions;
|
|
33
33
|
export declare function resizeTextureToMax(texture: Texture, maxSize: number, logger: ILogger): Promise<void>;
|
|
34
34
|
export declare function make_progressive_meshes(filePath: string, _options: any, opts?: ProgressiveOptions): Transform;
|
|
35
|
+
export declare function isMeshLOD(filepath: string): boolean;
|
|
36
|
+
export declare function isImageLOD(filepath: string): boolean;
|
|
37
|
+
export declare function isLOD(filepath: string): boolean;
|
|
35
38
|
export {};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { BufferUtils, Document, NodeIO, PropertyType } from '@gltf-transform/core';
|
|
2
|
-
import { SIMPLIFY_DEFAULTS, WELD_DEFAULTS, listTextureSlots } from '@gltf-transform/functions';
|
|
2
|
+
import { SIMPLIFY_DEFAULTS, WELD_DEFAULTS, listTextureSlots, sparse } from '@gltf-transform/functions';
|
|
3
3
|
import { NEEDLE_PROGRESSIVE_EXTENSION_NAME, NEEDLE_progressive, generateGuid } from "../extensions/NEEDLE_progressive/index.js";
|
|
4
4
|
import { createOpaqueExtension, NEEDLE_compression_texture, NEEDLE_mesh_compression, NEEDLE_progressive_mesh_settings, NEEDLE_progressive_texture_settings, registerExtensions as registerDefaultExtensions } from '../extensions/index.js';
|
|
5
5
|
import path from 'path';
|
|
6
|
-
import { existsSync, rmSync, statSync } from 'fs';
|
|
7
|
-
import { addToCache, getKey, tryGetFromCache } from '../cache/index.js';
|
|
6
|
+
import { existsSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
7
|
+
import { addToCache, getHash, getKey, tryGetFromCache } from '../cache/index.js';
|
|
8
8
|
import sharp from "sharp";
|
|
9
9
|
import { foreachGLTF } from '../utils/index.js';
|
|
10
10
|
import { needle_asset } from './needle_asset.js';
|
|
@@ -67,7 +67,7 @@ async function process(file, opts) {
|
|
|
67
67
|
}
|
|
68
68
|
//@ts-ignore
|
|
69
69
|
document.getLogger().verbosity = 0;
|
|
70
|
-
await document.transform(needle_asset(), make_progressive_textures(file, { size: [256, 256] }, opts), make_progressive_meshes(file, {}, opts));
|
|
70
|
+
await document.transform(needle_asset(), make_progressive_textures(file, { size: [256, 256] }, opts), make_progressive_meshes(file, {}, opts), sparse());
|
|
71
71
|
let filename = file;
|
|
72
72
|
if (opts.debug === true) {
|
|
73
73
|
// get extension
|
|
@@ -314,8 +314,8 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
314
314
|
for (let l = 0; l < MAX_LODS; l++) {
|
|
315
315
|
const t = l / MAX_LODS;
|
|
316
316
|
const latest = LODS[LODS.length - 1];
|
|
317
|
-
const ratio = latest.ratio * .
|
|
318
|
-
const error = .
|
|
317
|
+
const ratio = latest.ratio * .5;
|
|
318
|
+
const error = .05 + t * .5;
|
|
319
319
|
LODS.push({
|
|
320
320
|
ratio: ratio,
|
|
321
321
|
error,
|
|
@@ -357,10 +357,9 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
357
357
|
const lod = LODS[level];
|
|
358
358
|
const guid = settings.guid;
|
|
359
359
|
// TODO: check if the LOD level did actually change the mesh - otherwise we can just skip generating lower resolutions
|
|
360
|
-
const lodTask = createMeshLod(doc, outputDirectory, level, mesh, guid, lod);
|
|
360
|
+
const lodTask = createMeshLod(doc, outputDirectory, level, mesh, guid, lod, opts);
|
|
361
361
|
lodTask.then(res => {
|
|
362
|
-
|
|
363
|
-
if (res.success) {
|
|
362
|
+
if (res?.success) {
|
|
364
363
|
generatedLODs[level] = res.path;
|
|
365
364
|
}
|
|
366
365
|
});
|
|
@@ -428,7 +427,29 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
428
427
|
* @param guid a unique identifier for the mesh
|
|
429
428
|
* @param error the error to use for simplification. Higher means more simplification
|
|
430
429
|
*/
|
|
431
|
-
async function createMeshLod(doc, basePath, level, mesh, guid, lodLevel) {
|
|
430
|
+
async function createMeshLod(doc, basePath, level, mesh, guid, lodLevel, opts) {
|
|
431
|
+
const fileExtension = ".glb";
|
|
432
|
+
const fileName_withoutExtension = "mesh_lod_" + level + "_" + guid;
|
|
433
|
+
const fileName = fileName_withoutExtension + fileExtension;
|
|
434
|
+
const outPath = basePath + fileName;
|
|
435
|
+
const primitives = mesh.listPrimitives();
|
|
436
|
+
let cacheKey = undefined;
|
|
437
|
+
if (opts.useCache !== false) {
|
|
438
|
+
// generate hash for mesh
|
|
439
|
+
let hash = mesh["needle:mesh-hash"];
|
|
440
|
+
if (hash === undefined) {
|
|
441
|
+
hash = getHash(mesh);
|
|
442
|
+
mesh["needle:mesh-hash"] = hash;
|
|
443
|
+
}
|
|
444
|
+
hash += getHash(lodLevel);
|
|
445
|
+
cacheKey = `mesh_lod_${level}_${hash}`;
|
|
446
|
+
const cachedValue = tryGetFromCache(cacheKey);
|
|
447
|
+
if (cachedValue) {
|
|
448
|
+
console.log(`${NAME}: ${mesh.getName()} LOD ${level} loaded ${(cachedValue.length / 1024).toFixed()} KB from cache`);
|
|
449
|
+
writeFileSync(outPath, cachedValue);
|
|
450
|
+
return { path: fileName, success: true };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
432
453
|
const newDoc = new Document();
|
|
433
454
|
const newScene = newDoc.createScene();
|
|
434
455
|
newDoc.createBuffer();
|
|
@@ -439,11 +460,10 @@ async function createMeshLod(doc, basePath, level, mesh, guid, lodLevel) {
|
|
|
439
460
|
const newMesh = newDoc.createMesh();
|
|
440
461
|
newMesh.setName(mesh.getName() + " LOD " + level);
|
|
441
462
|
newNode.setMesh(newMesh);
|
|
442
|
-
const
|
|
463
|
+
const newMat = newDoc.createMaterial();
|
|
443
464
|
const tasks = new Array();
|
|
444
465
|
// let didRunSimplification = false;
|
|
445
466
|
for (const prim of primitives) {
|
|
446
|
-
const newMat = newDoc.createMaterial();
|
|
447
467
|
const newPrim = newDoc.createPrimitive();
|
|
448
468
|
newMesh.addPrimitive(newPrim);
|
|
449
469
|
newPrim.setName(prim.getName());
|
|
@@ -475,6 +495,8 @@ async function createMeshLod(doc, basePath, level, mesh, guid, lodLevel) {
|
|
|
475
495
|
console.log(`${NAME}: Copy primitive ${name} extension`);
|
|
476
496
|
newPrim.setExtension(name, copy);
|
|
477
497
|
}
|
|
498
|
+
}
|
|
499
|
+
for (const newPrim of newMesh.listPrimitives()) {
|
|
478
500
|
if (lodLevel.error != undefined || lodLevel.ratio != undefined) {
|
|
479
501
|
if (lodLevel.error > 0 || lodLevel.ratio < 1) {
|
|
480
502
|
// didRunSimplification = true;
|
|
@@ -484,6 +506,7 @@ async function createMeshLod(doc, basePath, level, mesh, guid, lodLevel) {
|
|
|
484
506
|
}
|
|
485
507
|
}
|
|
486
508
|
await Promise.all(tasks);
|
|
509
|
+
tasks.length = 0;
|
|
487
510
|
// if we run simplify but the mesh didnt change
|
|
488
511
|
// if (didRunSimplification) {
|
|
489
512
|
// let anyPrimitiveDidChange = false;
|
|
@@ -511,13 +534,14 @@ async function createMeshLod(doc, basePath, level, mesh, guid, lodLevel) {
|
|
|
511
534
|
}
|
|
512
535
|
const newIo = new NodeIO();
|
|
513
536
|
registerExtensions(newIo);
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
537
|
+
console.log(`${NAME}: Write mesh LOD ${level} to \"${fileName}\" with: ${JSON.stringify(lodLevel)}`);
|
|
538
|
+
await newIo.write(outPath, newDoc);
|
|
539
|
+
// add to cache
|
|
540
|
+
if (cacheKey != undefined) {
|
|
541
|
+
const bytes = readFileSync(outPath);
|
|
542
|
+
addToCache(cacheKey, bytes);
|
|
543
|
+
}
|
|
544
|
+
return { path: fileName, success: true };
|
|
521
545
|
}
|
|
522
546
|
function calculateMeshDensity(prim) {
|
|
523
547
|
const bounds = {
|
|
@@ -612,9 +636,12 @@ async function simplifyMesh(doc, mesh, prim, lodinfo) {
|
|
|
612
636
|
// simplifyOptions.lockBorder = lodinfo.lockBorder;
|
|
613
637
|
return compressPrimitive(doc, mesh, prim, weldOptions, simplifyOptions);
|
|
614
638
|
}
|
|
615
|
-
function isMeshLOD(filepath) {
|
|
639
|
+
export function isMeshLOD(filepath) {
|
|
616
640
|
return filepath.includes("mesh_lod_");
|
|
617
641
|
}
|
|
618
|
-
function isImageLOD(filepath) {
|
|
642
|
+
export function isImageLOD(filepath) {
|
|
619
643
|
return filepath.includes("image_");
|
|
620
644
|
}
|
|
645
|
+
export function isLOD(filepath) {
|
|
646
|
+
return isMeshLOD(filepath) || isImageLOD(filepath);
|
|
647
|
+
}
|
package/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
2
|
import { spawnSync } from "child_process";
|
|
3
|
+
import { getVersion } from "./dist/index.js";
|
|
4
|
+
|
|
4
5
|
const workingDir = process.cwd();
|
|
5
6
|
|
|
6
7
|
// Original invoked dir set by npm
|
|
@@ -28,6 +29,13 @@ function runWithArgs(cmd) {
|
|
|
28
29
|
spawnSync(cmd + " " + commandline_args, defaultArgs);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
runWithArgs("node
|
|
32
|
+
try {
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
runWithArgs("node make-progressive.mjs");
|
|
35
|
+
runWithArgs("node pack-gltf.mjs");
|
|
36
|
+
const durationInMinutes = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
|
|
37
|
+
console.log(`[Needle Build Pipeline] v${getVersion()} — Finished progressive transform and optimization in ${durationInMinutes} minutes.`)
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error("Failed running commandline args:", err.message);
|
|
41
|
+
}
|
package/pack-gltf.mjs
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import glob from 'glob';
|
|
2
1
|
import path from 'path';
|
|
3
2
|
import { NodeIO } from '@gltf-transform/core';
|
|
4
3
|
import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
|
|
5
|
-
import { draco } from '@gltf-transform/functions';
|
|
6
4
|
import draco3d from 'draco3dgltf';
|
|
7
5
|
import { ALL_EXTENSIONS as NEEDLE_EXTENSIONS } from "./dist/extensions/NEEDLE_opaque/index.js";
|
|
8
6
|
import commandLineArgs from "command-line-args";
|
|
9
7
|
import { NEEDLE_mesh_compression, NEEDLE_compression_texture } from './dist/extensions/index.js';
|
|
10
|
-
import { existsSync, statSync } from 'fs';
|
|
8
|
+
import { existsSync, readFileSync, statSync, writeFileSync } from 'fs';
|
|
11
9
|
import { needle_mesh_transform } from "./dist/transforms/needle_mesh_transform.js";
|
|
12
10
|
import { needle_texture_transform } from "./dist/transforms/needle_texture_transform.js";
|
|
13
11
|
import { needle_animation_transform } from "./dist/transforms/needle_animation_transform.js";
|
|
14
12
|
import { foreachGLTF } from './fileutils.mjs';
|
|
15
13
|
import { needle_asset } from './dist/transforms/needle_asset.js';
|
|
16
14
|
import { MeshoptDecoder, MeshoptEncoder } from 'meshoptimizer';
|
|
17
|
-
import { getVersion } from './dist/index.js';
|
|
15
|
+
import { getVersion, getHash, addToCache, tryGetFromCache, isLOD, ensureCacheSize as ensureMaxCacheSize, isMeshLOD } from './dist/index.js';
|
|
18
16
|
|
|
19
17
|
console.log(`[Needle Build Pipeline] pack glTF — version ${getVersion()}`)
|
|
20
18
|
|
|
@@ -77,6 +75,9 @@ let totalFilesProcessed = 0;
|
|
|
77
75
|
let totalFileSizeInMB = 0;
|
|
78
76
|
let totalFileSizeInMBBefore = 0;
|
|
79
77
|
|
|
78
|
+
/** Compress a glTF file
|
|
79
|
+
* @param {string} file
|
|
80
|
+
*/
|
|
80
81
|
async function packFile(file) {
|
|
81
82
|
if (!existsSync(file)) {
|
|
82
83
|
console.error("WARN: can not pack, file does not exist: " + file);
|
|
@@ -86,6 +87,22 @@ async function packFile(file) {
|
|
|
86
87
|
return;
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
// check if it's in the cache.
|
|
91
|
+
// We only need to check mesh LOD files because image LODs are cache via our toktx transform
|
|
92
|
+
// We can not cache the main glTF files because the hash always changes for them
|
|
93
|
+
let cacheKey = undefined;
|
|
94
|
+
if (allowCaching && isMeshLOD(file)) {
|
|
95
|
+
const existing = readFileSync(file);
|
|
96
|
+
const hash = getHash(existing);
|
|
97
|
+
cacheKey = "packed-" + hash;
|
|
98
|
+
const cached = tryGetFromCache(cacheKey);
|
|
99
|
+
if (cached) {
|
|
100
|
+
console.log("→ Skip packing (loaded from cache): " + path.basename(file));
|
|
101
|
+
writeFileSync(file, cached);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
89
106
|
if (packedFiles.has(file)) return;
|
|
90
107
|
packedFiles.add(file);
|
|
91
108
|
console.log("→ Begin packing: " + file);
|
|
@@ -123,6 +140,10 @@ async function packFile(file) {
|
|
|
123
140
|
);
|
|
124
141
|
if (debug) file += ".debug.glb";
|
|
125
142
|
await io.write(file, document);
|
|
143
|
+
|
|
144
|
+
if (allowCaching && cacheKey != undefined && existsSync(file))
|
|
145
|
+
addToCache(cacheKey, readFileSync(file));
|
|
146
|
+
|
|
126
147
|
const duration = Date.now() - startTime;
|
|
127
148
|
const sizeInMB = statSync(file).size / 1024 / 1024;
|
|
128
149
|
const filename = path.basename(file);
|
|
@@ -148,8 +169,17 @@ let startTime = Date.now();
|
|
|
148
169
|
const inputArgs = args.slice(2);
|
|
149
170
|
foreachGLTF(inputArgs, async (file) => {
|
|
150
171
|
await packFile(file);
|
|
151
|
-
}).then(() => {
|
|
172
|
+
}).then(async () => {
|
|
152
173
|
const duration = Date.now() - startTime;
|
|
153
174
|
const durationInMinutes = duration / 1000 / 60;
|
|
175
|
+
if (allowCaching) {
|
|
176
|
+
// if the user's cache directory has more than 25GB free space, we allow a cache of 2 GB - otherwise 1 GB
|
|
177
|
+
// const freeSpaceInMB = (await getCacheAvailableSpaceOnDisc().catch(() => 0)) / 1024 / 1024;
|
|
178
|
+
// const maxCacheSize = freeSpaceInMB > 25000 ? 2000 : 1000;
|
|
179
|
+
ensureMaxCacheSize(1000);
|
|
180
|
+
}
|
|
154
181
|
console.log(`[Needle Build Pipeline] v${getVersion()} — Finished packing ${totalFilesProcessed} files in ${durationInMinutes.toFixed(1)} minutes; total size ${totalFileSizeInMBBefore.toFixed(1)} MB → ${totalFileSizeInMB.toFixed(1)} MB, total GPU memory: ${totalGPUMemoryInMB.toFixed(1)} MB`);
|
|
155
|
-
})
|
|
182
|
+
}).catch(err => {
|
|
183
|
+
console.error("ERR: packing failed: " + err.message);
|
|
184
|
+
if (debug) throw err;
|
|
185
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "1.4.1-beta.
|
|
3
|
+
"version": "1.4.1-beta.6",
|
|
4
4
|
"description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"type": "module",
|