@needle-tools/gltf-build-pipeline 2.14.0-alpha → 2.14.0-next.66c769b
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/README.md +94 -0
- package/dist/cli/index.js +54 -0
- package/dist/scripts/detailed-stats.d.ts +180 -0
- package/dist/scripts/detailed-stats.js +1268 -0
- package/dist/scripts/index.d.ts +2 -0
- package/dist/scripts/index.js +2 -0
- package/dist/scripts/mesh-merge.d.ts +5 -0
- package/dist/scripts/mesh-merge.js +26 -0
- package/dist/scripts/pack-gltf.js +65 -10
- package/dist/transforms/needleDedup.d.ts +46 -0
- package/dist/transforms/needleDedup.js +759 -0
- package/dist/transforms/needle_mesh_merge.d.ts +79 -0
- package/dist/transforms/needle_mesh_merge.js +564 -0
- package/dist/transforms/needle_mesh_transform.d.ts +7 -1
- package/dist/transforms/needle_mesh_transform.js +14 -11
- package/dist/transforms/needle_progressive.d.ts +34 -0
- package/dist/transforms/needle_progressive.js +471 -34
- package/dist/transforms/needle_toktx.d.ts +2 -0
- package/dist/transforms/needle_toktx.js +23 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/nodeio.js +21 -1
- package/dist/utils/progress.d.ts +57 -0
- package/dist/utils/progress.js +130 -0
- package/dist/utils/version.gen.d.ts +1 -1
- package/dist/utils/version.gen.js +1 -1
- package/package.json +3 -2
- package/scripts/perf-test-clean.mjs +308 -0
- package/scripts/perf-test-dedup.mjs +201 -0
- package/scripts/perf-test-large.mjs +195 -0
- package/scripts/prune-comparison.mjs +158 -0
package/README.md
CHANGED
|
@@ -23,9 +23,103 @@ To load progressively enhanced glTF files in any three.js based engine see examp
|
|
|
23
23
|
|
|
24
24
|
`npx @needle-tools/gltf-build-pipeline@latest`
|
|
25
25
|
|
|
26
|
+
### CLI Commands
|
|
27
|
+
|
|
28
|
+
- `stats <input> <output>` - Calculate basic stats
|
|
29
|
+
- `detailed-stats <input>` - Analyze file structure, component counts, and mesh merging potential
|
|
30
|
+
- `mesh-merge <input> <output> [options]` - Merge meshes with shared materials to reduce draw calls
|
|
31
|
+
- `--min-vertices <n>` - Only merge meshes with >n vertices
|
|
32
|
+
- `--max-vertices <n>` - Target max vertices per merged mesh (default: 65535)
|
|
33
|
+
- `--dry-run` - Analyze potential without writing file
|
|
34
|
+
- `transform <input> <output> [options]` - Generate LODs and compress
|
|
35
|
+
- `clear-caches` - Clear all caches
|
|
36
|
+
|
|
26
37
|
### Commands
|
|
27
38
|
- `help` - use help to view options
|
|
28
39
|
|
|
40
|
+
## Development
|
|
41
|
+
|
|
42
|
+
### Building
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd package
|
|
46
|
+
npm install
|
|
47
|
+
npx tsc
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Running the CLI locally
|
|
51
|
+
|
|
52
|
+
After building, you can run the CLI directly with node:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Transform a GLB file
|
|
56
|
+
node dist/cli/index.js transform <input.glb> <output-directory> [options]
|
|
57
|
+
|
|
58
|
+
# Example with verbose output
|
|
59
|
+
node dist/cli/index.js transform "path/to/model.glb" "path/to/output" --verbose True
|
|
60
|
+
|
|
61
|
+
# View help
|
|
62
|
+
node dist/cli/index.js help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Note:** On Windows, using `node` directly avoids path issues with special characters (spaces, `&`, etc.) that can occur with `npx`.
|
|
66
|
+
|
|
67
|
+
### Running Tests
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cd package
|
|
71
|
+
|
|
72
|
+
# Run all tests (uses Node.js built-in test runner)
|
|
73
|
+
npm test
|
|
74
|
+
|
|
75
|
+
# Run tests directly
|
|
76
|
+
node --test src/tests/
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Test Files
|
|
80
|
+
|
|
81
|
+
Test files are located in `src/tests/`:
|
|
82
|
+
- `transform-paths.test.mjs` - Tests path handling with special characters (spaces, commas, ampersands, parentheses)
|
|
83
|
+
- `path-handling.test.mjs` - Additional path handling tests
|
|
84
|
+
|
|
85
|
+
### Clearing the Cache
|
|
86
|
+
|
|
87
|
+
The build pipeline caches processed meshes and textures. To force reprocessing:
|
|
88
|
+
|
|
89
|
+
```powershell
|
|
90
|
+
# Windows
|
|
91
|
+
Remove-Item "$env:LOCALAPPDATA\Temp\Needle\build-pipeline-cache" -Recurse -Force
|
|
92
|
+
|
|
93
|
+
# Or find the cache location from the log output:
|
|
94
|
+
# "Limit cache size to X MB at C:\Users\...\Temp\Needle\build-pipeline-cache"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Performance Profiling
|
|
98
|
+
|
|
99
|
+
When processing large files, the pipeline outputs detailed performance statistics:
|
|
100
|
+
- Mesh processing throughput (meshes/s, vertices/s)
|
|
101
|
+
- Cache hit/miss rates
|
|
102
|
+
- Time breakdown (simplification, I/O, hashing)
|
|
103
|
+
|
|
104
|
+
Example output:
|
|
105
|
+
```
|
|
106
|
+
[NEEDLE_progressive]: ═══════════════════════════════════════════════════════════
|
|
107
|
+
[NEEDLE_progressive]: MESH PROCESSING SUMMARY for "model.glb"
|
|
108
|
+
[NEEDLE_progressive]: ───────────────────────────────────────────────────────────
|
|
109
|
+
[NEEDLE_progressive]: Total time: 45.23s
|
|
110
|
+
[NEEDLE_progressive]: Meshes: 150 processed, 20 skipped (of 170 total)
|
|
111
|
+
[NEEDLE_progressive]: Vertices: 1,234,567 input → 456,789 output
|
|
112
|
+
[NEEDLE_progressive]: LOD levels generated: 450
|
|
113
|
+
[NEEDLE_progressive]: Cache: 100 hits, 350 misses
|
|
114
|
+
[NEEDLE_progressive]: Time breakdown:
|
|
115
|
+
[NEEDLE_progressive]: - Simplification: 30.12s
|
|
116
|
+
[NEEDLE_progressive]: - I/O Write: 10.45s
|
|
117
|
+
[NEEDLE_progressive]: - I/O Read (cache): 2.34s
|
|
118
|
+
[NEEDLE_progressive]: - Hashing: 2.32s
|
|
119
|
+
[NEEDLE_progressive]: Throughput: 27,293 vertices/s
|
|
120
|
+
[NEEDLE_progressive]: ═══════════════════════════════════════════════════════════
|
|
121
|
+
```
|
|
122
|
+
|
|
29
123
|
# Contact
|
|
30
124
|
|
|
31
125
|
For licensing questions please contact us.
|
package/dist/cli/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { program } from '@donmccurdy/caporal';
|
|
3
3
|
import { calculateStats, packGLTF } from "../scripts/index.js";
|
|
4
4
|
import { printStats, writeFileStatsToFile } from '../utils/stats.js';
|
|
5
|
+
import { calculateDetailedStats, printDetailedStats } from '../scripts/detailed-stats.js';
|
|
6
|
+
import { runMeshMerge } from '../scripts/mesh-merge.js';
|
|
5
7
|
import { getVersion } from '../utils/version.js';
|
|
6
8
|
import { UsecaseOptions, createConfig, getConfig } from '../config/index.js';
|
|
7
9
|
import { isLOD, make_progressive } from '../transforms/needle_progressive.js';
|
|
@@ -50,6 +52,58 @@ Caches are limited to ${cacheSizeLimit} MB disc space by default.
|
|
|
50
52
|
}
|
|
51
53
|
logger.info("Writing stats to: " + output);
|
|
52
54
|
writeFileStatsToFile(filestats, output);
|
|
55
|
+
})
|
|
56
|
+
.command("detailed-stats", "Calculate detailed glTF stats with top N items")
|
|
57
|
+
.help(`
|
|
58
|
+
Analyzes a glTF/GLB/VRM file and prints detailed statistics including:
|
|
59
|
+
- File size and load time
|
|
60
|
+
- Extensions used
|
|
61
|
+
- Hierarchy depth/breadth
|
|
62
|
+
- Top 5 biggest meshes, accessors, textures
|
|
63
|
+
- Needle component counts
|
|
64
|
+
`)
|
|
65
|
+
.argument('<input>', "Path to a glTF, GLB or VRM file")
|
|
66
|
+
.action(async ({ logger, args }) => {
|
|
67
|
+
logger.level = "info";
|
|
68
|
+
const input = args.input.toString();
|
|
69
|
+
console.log(`\nAnalyzing: ${input}`);
|
|
70
|
+
console.log('Please wait...\n');
|
|
71
|
+
try {
|
|
72
|
+
const stats = await calculateDetailedStats(input);
|
|
73
|
+
printDetailedStats(stats);
|
|
74
|
+
// Explicitly exit to ensure hanging promises (like meshopt/draco) don't keep process alive
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
logger.error(`Failed to analyze file: ${error}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
.command("mesh-merge", "Merge meshes with shared materials")
|
|
83
|
+
.help(`
|
|
84
|
+
Merges primitives that share the same material and vertex attributes.
|
|
85
|
+
Reduces draw calls but may increase file size slightly due to less reuse of shared accessors.
|
|
86
|
+
`)
|
|
87
|
+
.argument('<input>', "Path to input glTF/GLB file")
|
|
88
|
+
.argument('<output>', "Path to output glTF/GLB file")
|
|
89
|
+
.option('--min-vertices <n>', 'Minimum vertices to include in merge', { validator: program.NUMBER, default: 0 })
|
|
90
|
+
.option('--max-vertices <n>', 'Maximum vertices per merged mesh', { validator: program.NUMBER, default: 65535 })
|
|
91
|
+
.option('--dry-run', 'Analyze only, do not merge', { validator: program.BOOLEAN, default: false })
|
|
92
|
+
.action(async ({ logger, args, options }) => {
|
|
93
|
+
logger.level = "info";
|
|
94
|
+
try {
|
|
95
|
+
await runMeshMerge(args.input.toString(), args.output.toString(), {
|
|
96
|
+
minVertices: Number(options.minVertices),
|
|
97
|
+
maxVerticesPerMesh: Number(options.maxVertices),
|
|
98
|
+
dryRun: !!options.dryRun,
|
|
99
|
+
logger
|
|
100
|
+
});
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
logger.error(`Failed to merge meshes: ${error}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
53
107
|
})
|
|
54
108
|
.command("transform", "Transform (progressive loading) and compress glTF, GLB or VRM files")
|
|
55
109
|
.help(`
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { type MeshMergeAnalysis } from "../transforms/needle_mesh_merge.js";
|
|
2
|
+
/**
|
|
3
|
+
* Distribution band for histogram display
|
|
4
|
+
*/
|
|
5
|
+
export interface DistributionBand {
|
|
6
|
+
label: string;
|
|
7
|
+
min: number;
|
|
8
|
+
max: number;
|
|
9
|
+
count: number;
|
|
10
|
+
percentage: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Detailed stats analysis for a glTF file
|
|
14
|
+
*/
|
|
15
|
+
export interface DetailedStats {
|
|
16
|
+
file: {
|
|
17
|
+
path: string;
|
|
18
|
+
sizeBytes: number;
|
|
19
|
+
sizeMB: number;
|
|
20
|
+
loadTimeMs: number;
|
|
21
|
+
};
|
|
22
|
+
extensions: {
|
|
23
|
+
used: string[];
|
|
24
|
+
required: string[];
|
|
25
|
+
};
|
|
26
|
+
hierarchy: {
|
|
27
|
+
nodeCount: number;
|
|
28
|
+
maxDepth: number;
|
|
29
|
+
maxBreadth: number;
|
|
30
|
+
sceneCount: number;
|
|
31
|
+
rootNodeCount: number;
|
|
32
|
+
};
|
|
33
|
+
meshes: {
|
|
34
|
+
count: number;
|
|
35
|
+
primitiveCount: number;
|
|
36
|
+
totalVertices: number;
|
|
37
|
+
totalTriangles: number;
|
|
38
|
+
memoryBytes: number;
|
|
39
|
+
top5ByVertices: {
|
|
40
|
+
name: string;
|
|
41
|
+
vertices: number;
|
|
42
|
+
triangles: number;
|
|
43
|
+
primitives: number;
|
|
44
|
+
}[];
|
|
45
|
+
distribution: DistributionBand[];
|
|
46
|
+
};
|
|
47
|
+
accessors: {
|
|
48
|
+
count: number;
|
|
49
|
+
totalBytes: number;
|
|
50
|
+
top5BySize: {
|
|
51
|
+
name: string;
|
|
52
|
+
type: string;
|
|
53
|
+
count: number;
|
|
54
|
+
bytes: number;
|
|
55
|
+
}[];
|
|
56
|
+
};
|
|
57
|
+
buffers: {
|
|
58
|
+
count: number;
|
|
59
|
+
totalBytes: number;
|
|
60
|
+
top5BySize: {
|
|
61
|
+
name: string;
|
|
62
|
+
bytes: number;
|
|
63
|
+
}[];
|
|
64
|
+
};
|
|
65
|
+
textures: {
|
|
66
|
+
count: number;
|
|
67
|
+
totalMemoryBytes: number;
|
|
68
|
+
totalGPUBytes: number;
|
|
69
|
+
mimeTypeMismatchCount: number;
|
|
70
|
+
top5ByPixelArea: {
|
|
71
|
+
name: string;
|
|
72
|
+
width: number;
|
|
73
|
+
height: number;
|
|
74
|
+
pixelArea: number;
|
|
75
|
+
mimeType: string;
|
|
76
|
+
mismatch: boolean;
|
|
77
|
+
bytes: number;
|
|
78
|
+
gpuBytes: number;
|
|
79
|
+
}[];
|
|
80
|
+
top5ByFileSize: {
|
|
81
|
+
name: string;
|
|
82
|
+
bytes: number;
|
|
83
|
+
mimeType: string;
|
|
84
|
+
}[];
|
|
85
|
+
formats: string[];
|
|
86
|
+
distribution: DistributionBand[];
|
|
87
|
+
};
|
|
88
|
+
materials: {
|
|
89
|
+
count: number;
|
|
90
|
+
top5ByTextureCount: {
|
|
91
|
+
name: string;
|
|
92
|
+
textureCount: number;
|
|
93
|
+
}[];
|
|
94
|
+
top5ByUsage: {
|
|
95
|
+
name: string;
|
|
96
|
+
usageCount: number;
|
|
97
|
+
}[];
|
|
98
|
+
};
|
|
99
|
+
animations: {
|
|
100
|
+
count: number;
|
|
101
|
+
channelCount: number;
|
|
102
|
+
totalBytes: number;
|
|
103
|
+
};
|
|
104
|
+
nodesWithExtensions: {
|
|
105
|
+
count: number;
|
|
106
|
+
extensionCounts: Record<string, number>;
|
|
107
|
+
};
|
|
108
|
+
needleComponents: {
|
|
109
|
+
nodeCount: number;
|
|
110
|
+
componentCount: number;
|
|
111
|
+
componentTypes: Record<string, number>;
|
|
112
|
+
};
|
|
113
|
+
validation: {
|
|
114
|
+
errorCount: number;
|
|
115
|
+
warningCount: number;
|
|
116
|
+
infoCount: number;
|
|
117
|
+
top5Errors: string[];
|
|
118
|
+
top5Warnings: string[];
|
|
119
|
+
};
|
|
120
|
+
emptyNodes: {
|
|
121
|
+
count: number;
|
|
122
|
+
examples: string[];
|
|
123
|
+
};
|
|
124
|
+
tinyMeshes: {
|
|
125
|
+
count: number;
|
|
126
|
+
examples: {
|
|
127
|
+
name: string;
|
|
128
|
+
vertices: number;
|
|
129
|
+
triangles: number;
|
|
130
|
+
}[];
|
|
131
|
+
dedupAnalysis?: {
|
|
132
|
+
uniqueGeometries: number;
|
|
133
|
+
uniqueStructural: number;
|
|
134
|
+
uniqueByteHashes: number;
|
|
135
|
+
dedupPotentialPct: string;
|
|
136
|
+
topDuplicates: {
|
|
137
|
+
count: number;
|
|
138
|
+
uniqueMaterials: number;
|
|
139
|
+
materials: string[];
|
|
140
|
+
}[];
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
allMeshesDedupAnalysis?: {
|
|
144
|
+
totalMeshes: number;
|
|
145
|
+
totalVertices: number;
|
|
146
|
+
uniqueByContent: number;
|
|
147
|
+
uniqueByBytes: number;
|
|
148
|
+
uniqueByStructure: number;
|
|
149
|
+
dedupPotentialPct: string;
|
|
150
|
+
topDuplicates: {
|
|
151
|
+
hash: string;
|
|
152
|
+
count: number;
|
|
153
|
+
}[];
|
|
154
|
+
perBucketStats: {
|
|
155
|
+
bucket: string;
|
|
156
|
+
meshes: number;
|
|
157
|
+
uniqueContent: number;
|
|
158
|
+
uniqueStructural: number;
|
|
159
|
+
dedupPotentialPct: string;
|
|
160
|
+
}[];
|
|
161
|
+
};
|
|
162
|
+
meshMergeAnalysis?: MeshMergeAnalysis;
|
|
163
|
+
materialBuckets: {
|
|
164
|
+
bucketCount: number;
|
|
165
|
+
totalMaterialsInBuckets: number;
|
|
166
|
+
top5Buckets: {
|
|
167
|
+
baseColor: string;
|
|
168
|
+
materialCount: number;
|
|
169
|
+
names: string[];
|
|
170
|
+
}[];
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Calculate detailed stats for a glTF file
|
|
175
|
+
*/
|
|
176
|
+
export declare function calculateDetailedStats(inputFile: string): Promise<DetailedStats>;
|
|
177
|
+
/**
|
|
178
|
+
* Print detailed stats to console
|
|
179
|
+
*/
|
|
180
|
+
export declare function printDetailedStats(stats: DetailedStats): void;
|