@needle-tools/gltf-build-pipeline 2.13.0 → 2.13.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 +520 -517
- package/README.md +38 -38
- package/dist/cache/cache.d.ts +55 -55
- package/dist/cache/cache.js +326 -326
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +198 -198
- package/dist/config/index.d.ts +53 -53
- package/dist/config/index.js +152 -152
- package/dist/constants.d.ts +7 -7
- package/dist/constants.js +7 -7
- package/dist/extensions/NEEDLE_compression_texture/NEEDLE_compression_texture.d.ts +24 -24
- package/dist/extensions/NEEDLE_compression_texture/NEEDLE_compression_texture.js +54 -54
- package/dist/extensions/NEEDLE_compression_texture/index.d.ts +1 -1
- package/dist/extensions/NEEDLE_compression_texture/index.js +1 -1
- package/dist/extensions/NEEDLE_mesh_compression/NEEDLE_mesh_compression.d.ts +27 -27
- package/dist/extensions/NEEDLE_mesh_compression/NEEDLE_mesh_compression.js +88 -88
- package/dist/extensions/NEEDLE_mesh_compression/index.d.ts +1 -1
- package/dist/extensions/NEEDLE_mesh_compression/index.js +1 -1
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.d.ts +27 -27
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.js +980 -980
- package/dist/extensions/NEEDLE_opaque/index.d.ts +3 -3
- package/dist/extensions/NEEDLE_opaque/index.js +30 -30
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.d.ts +48 -48
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.js +69 -69
- package/dist/extensions/NEEDLE_progressive/index.d.ts +1 -1
- package/dist/extensions/NEEDLE_progressive/index.js +1 -1
- package/dist/extensions/NEEDLE_progressive_mesh_settings/index.d.ts +23 -23
- package/dist/extensions/NEEDLE_progressive_mesh_settings/index.js +41 -41
- package/dist/extensions/NEEDLE_progressive_texture_settings/index.d.ts +25 -25
- package/dist/extensions/NEEDLE_progressive_texture_settings/index.js +38 -38
- package/dist/extensions/index.d.ts +7 -7
- package/dist/extensions/index.js +17 -17
- package/dist/extensions/utils.d.ts +6 -6
- package/dist/extensions/utils.js +72 -72
- package/dist/functions/weld.3.d.ts +76 -76
- package/dist/functions/weld.3.js +414 -414
- package/dist/index.d.ts +8 -8
- package/dist/index.js +8 -8
- package/dist/scripts/index.d.ts +2 -2
- package/dist/scripts/index.js +2 -2
- package/dist/scripts/pack-gltf.d.ts +15 -15
- package/dist/scripts/pack-gltf.js +135 -133
- package/dist/scripts/stats.d.ts +2 -2
- package/dist/scripts/stats.js +19 -19
- package/dist/transforms/index.d.ts +9 -9
- package/dist/transforms/index.js +9 -9
- package/dist/transforms/needle_animation_transform.d.ts +2 -2
- package/dist/transforms/needle_animation_transform.js +5 -5
- package/dist/transforms/needle_asset.d.ts +3 -3
- package/dist/transforms/needle_asset.js +50 -50
- package/dist/transforms/needle_common.d.ts +17 -17
- package/dist/transforms/needle_common.js +1 -1
- package/dist/transforms/needle_mesh_transform.d.ts +15 -15
- package/dist/transforms/needle_mesh_transform.js +223 -223
- package/dist/transforms/needle_progressive.d.ts +45 -45
- package/dist/transforms/needle_progressive.js +1043 -1043
- package/dist/transforms/needle_texture_transform.d.ts +13 -13
- package/dist/transforms/needle_texture_transform.js +257 -257
- package/dist/transforms/needle_toktx.d.ts +26 -26
- package/dist/transforms/needle_toktx.js +243 -243
- package/dist/transforms/needle_webp.d.ts +15 -15
- package/dist/transforms/needle_webp.js +53 -53
- package/dist/transforms/toktx.d.ts +52 -52
- package/dist/transforms/toktx.js +221 -221
- package/dist/transforms/toktx_types.d.ts +78 -78
- package/dist/transforms/toktx_types.js +62 -62
- package/dist/transforms/util.d.ts +21 -21
- package/dist/transforms/util.js +90 -90
- package/dist/utils/compression_utils.d.ts +2 -2
- package/dist/utils/compression_utils.js +6 -6
- package/dist/utils/fileutils.d.ts +19 -19
- package/dist/utils/fileutils.js +177 -177
- package/dist/utils/guid.d.ts +1 -1
- package/dist/utils/guid.js +5 -5
- package/dist/utils/index.d.ts +10 -10
- package/dist/utils/index.js +10 -10
- package/dist/utils/merge.d.ts +12 -12
- package/dist/utils/merge.js +31 -31
- package/dist/utils/mesh.d.ts +13 -13
- package/dist/utils/mesh.js +124 -124
- package/dist/utils/nodeio.d.ts +6 -6
- package/dist/utils/nodeio.js +86 -86
- package/dist/utils/stats.d.ts +70 -70
- package/dist/utils/stats.js +258 -258
- package/dist/utils/texture.d.ts +5 -5
- package/dist/utils/texture.js +14 -14
- package/dist/utils/validate.d.ts +5 -5
- package/dist/utils/validate.js +17 -17
- package/dist/utils/version.d.ts +1 -1
- package/dist/utils/version.gen.d.ts +1 -1
- package/dist/utils/version.gen.js +1 -1
- package/dist/utils/version.js +4 -4
- package/package.json +79 -79
- package/scripts/clear-caches.mjs +3 -3
- package/scripts/defaults.mjs +11 -11
- package/scripts/fileutils.mjs +5 -5
- package/scripts/index.mjs +41 -41
- package/scripts/limit-caches.mjs +3 -3
- package/scripts/make-progressive.mjs +16 -16
- package/scripts/pack-gltf.mjs +106 -106
- package/tsconfig.json +19 -19
|
@@ -1,223 +1,223 @@
|
|
|
1
|
-
import { Primitive } from "@gltf-transform/core";
|
|
2
|
-
import { meshopt, simplifyPrimitive, SIMPLIFY_DEFAULTS, WELD_DEFAULTS, draco } from "@gltf-transform/functions";
|
|
3
|
-
import { MeshoptEncoder, MeshoptSimplifier } from "meshoptimizer";
|
|
4
|
-
import { NEEDLE_mesh_compression, NEEDLE_mesh_compression_model_root } from "../extensions/index.js";
|
|
5
|
-
import { weldPrimitive } from "../functions/weld.3.js";
|
|
6
|
-
import { calculateMeshDensity } from "../utils/mesh.js";
|
|
7
|
-
import { isMeshLOD } from "./needle_progressive.js";
|
|
8
|
-
const NAME = 'needle_mesh_transform';
|
|
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
|
|
11
|
-
*/
|
|
12
|
-
export function determineMeshCompression(document, filePath) {
|
|
13
|
-
const rootExtension = document.getRoot().getExtension(NEEDLE_mesh_compression.EXTENSION_NAME);
|
|
14
|
-
if (rootExtension) {
|
|
15
|
-
const rootModel = rootExtension.model;
|
|
16
|
-
if (rootModel) {
|
|
17
|
-
if (!rootModel.compression) {
|
|
18
|
-
throw new Error(`[${NAME}]: Invalid mesh compression model extension on root node of ${filePath}`);
|
|
19
|
-
}
|
|
20
|
-
return rootModel;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
if (filePath?.toLowerCase().endsWith(".vrm")) {
|
|
24
|
-
const hasBlendshapes = document.getRoot().listMeshes().some((mesh) => mesh.listPrimitives().some((prim) => prim.listTargets().length > 0));
|
|
25
|
-
if (hasBlendshapes) {
|
|
26
|
-
return new NEEDLE_mesh_compression_model_root("meshopt");
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// heuristic to detect if we should choose meshopt over draco
|
|
30
|
-
// because meshopt can compress blendshapes while draco can not
|
|
31
|
-
// we currently only consider the vertex count of the blendshapes
|
|
32
|
-
let sizeOfBlendshapes = 0;
|
|
33
|
-
let sizeOfMeshes = 0;
|
|
34
|
-
for (const mesh of document.getRoot().listMeshes()) {
|
|
35
|
-
for (const prim of mesh.listPrimitives()) {
|
|
36
|
-
for (const target of prim.listTargets()) {
|
|
37
|
-
sizeOfBlendshapes += target.getAttribute('POSITION').getArray().length;
|
|
38
|
-
}
|
|
39
|
-
sizeOfMeshes += prim.getAttribute('POSITION').getArray().length;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// TODO: make this more robus with progressive loading: when progressive loading is enabled we need to run this check on the root mesh and enforce the same mesh compression for all children. Until we can do that we can not use the code for choosing compression based on blendshape size
|
|
43
|
-
if (sizeOfBlendshapes > 0) {
|
|
44
|
-
document.getLogger().info(`[${NAME}]: will apply meshopt mesh compression because the mesh contains blendshapes`);
|
|
45
|
-
return new NEEDLE_mesh_compression_model_root("meshopt");
|
|
46
|
-
}
|
|
47
|
-
// consider meshopt if blendshape size is > 256 KB
|
|
48
|
-
// const sizeThreshold = 256 * 1024;
|
|
49
|
-
// if (sizeOfBlendshapes > sizeThreshold) {
|
|
50
|
-
// /** How big are the blendshapes compared to the overall mesh size */
|
|
51
|
-
// const blendshapeRatio = sizeOfBlendshapes / sizeOfMeshes;
|
|
52
|
-
// /** 25% of the size are blendshapres */
|
|
53
|
-
// const ratioThreshold = 0.1;
|
|
54
|
-
// if (blendshapeRatio >= ratioThreshold) {
|
|
55
|
-
// document.getLogger().info(`[${NAME}]: will apply meshopt mesh compression because blendshapes are ${(blendshapeRatio * 100).toFixed(2)}% of the mesh size`);
|
|
56
|
-
// const transform = meshopt({ encoder: MeshoptEncoder });
|
|
57
|
-
// return transform(document, transform_context);
|
|
58
|
-
// }
|
|
59
|
-
// }
|
|
60
|
-
return new NEEDLE_mesh_compression_model_root("draco");
|
|
61
|
-
}
|
|
62
|
-
export const needle_mesh_transform = function (context) {
|
|
63
|
-
return async (document, _context) => {
|
|
64
|
-
// TODO: needs to be moved into NEEDLE_progressive
|
|
65
|
-
await simplifierTransform(document, _context, context);
|
|
66
|
-
await applyMeshCompression(document, _context, context);
|
|
67
|
-
};
|
|
68
|
-
};
|
|
69
|
-
async function applyMeshCompression(document, transform_context, context) {
|
|
70
|
-
const isLOD = isMeshLOD(context.file);
|
|
71
|
-
const compressionModel = determineMeshCompression(document, context.file); // document.getRoot().getExtension(NEEDLE_mesh_compression.EXTENSION_NAME) as NEEDLE_mesh_compression_property;
|
|
72
|
-
if (compressionModel) {
|
|
73
|
-
// const rootModel = rootExtension.model as NEEDLE_mesh_compression_model_root;
|
|
74
|
-
if (compressionModel.compression) {
|
|
75
|
-
if (compressionModel.compression === "none") {
|
|
76
|
-
document.getLogger().info(`[${NAME}]: NONE → will not apply mesh compression`);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
document.getLogger().info(`[${NAME}]: ${compressionModel.compression.toUpperCase()} → will apply ${compressionModel.compression} mesh compression`);
|
|
80
|
-
if (compressionModel.compression === "draco") {
|
|
81
|
-
const transform = draco({
|
|
82
|
-
encodeSpeed: 8,
|
|
83
|
-
decodeSpeed: 8,
|
|
84
|
-
quantizationVolume: isLOD ? "scene" : undefined
|
|
85
|
-
});
|
|
86
|
-
return transform(document, transform_context);
|
|
87
|
-
}
|
|
88
|
-
else if (compressionModel.compression === "meshopt") {
|
|
89
|
-
const transform = meshopt({ encoder: MeshoptEncoder });
|
|
90
|
-
return transform(document, transform_context);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
// by default we still want to run draco if the root doesnt have a compression model configured
|
|
95
|
-
document.getLogger().info(`[${NAME}]: → will apply draco mesh compression (default)`);
|
|
96
|
-
const transform = draco({
|
|
97
|
-
encodeSpeed: 8,
|
|
98
|
-
decodeSpeed: 8,
|
|
99
|
-
quantizationVolume: isLOD ? "scene" : undefined
|
|
100
|
-
});
|
|
101
|
-
return transform(document, transform_context);
|
|
102
|
-
}
|
|
103
|
-
async function simplifierTransform(document, _context, meshContext) {
|
|
104
|
-
const simplifier = MeshoptSimplifier;
|
|
105
|
-
if (!simplifier) {
|
|
106
|
-
console.log(`WARN: [${NAME}] simplifier dependency required — install "meshoptimizer".`);
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
const logger = document.getLogger();
|
|
110
|
-
const isVRM = meshContext.file?.toLowerCase().endsWith(".vrm");
|
|
111
|
-
const options = {
|
|
112
|
-
...SIMPLIFY_DEFAULTS,
|
|
113
|
-
simplifier: simplifier,
|
|
114
|
-
ratio: 0.75,
|
|
115
|
-
error: 0.0005,
|
|
116
|
-
};
|
|
117
|
-
const weldOptions = {
|
|
118
|
-
...WELD_DEFAULTS,
|
|
119
|
-
overwrite: true,
|
|
120
|
-
};
|
|
121
|
-
// let meshInformation: MeshInformation | null = null;
|
|
122
|
-
// if (isVRM) {
|
|
123
|
-
// meshInformation = getMeshInformation(document);
|
|
124
|
-
// }
|
|
125
|
-
for (const mesh of document.getRoot().listMeshes()) {
|
|
126
|
-
let i = 0;
|
|
127
|
-
for (const prim of mesh.listPrimitives()) {
|
|
128
|
-
const index = i++;
|
|
129
|
-
// TODO: mesh compression caching
|
|
130
|
-
// const key = getKey(prim);
|
|
131
|
-
// const cached = tryGetFromCache(key);
|
|
132
|
-
// if (cached) {
|
|
133
|
-
// logger.debug(`[${NAME}]: Skipping primitive of mesh "${mesh.getName()}": Already cached.`);
|
|
134
|
-
// continue;
|
|
135
|
-
// }
|
|
136
|
-
// updatePrimitive(prim, simplifier, null, prim.getIndices());
|
|
137
|
-
// continue;
|
|
138
|
-
const ext = prim.getExtension(NEEDLE_mesh_compression.EXTENSION_NAME);
|
|
139
|
-
const mesh_compression = ext?.model;
|
|
140
|
-
const simplifyOpts = { ...options };
|
|
141
|
-
if (isVRM) {
|
|
142
|
-
const density = calculateMeshDensity(prim);
|
|
143
|
-
if (density > 0) {
|
|
144
|
-
const targetDensity = 2_000;
|
|
145
|
-
const t = (1 / density) * targetDensity;
|
|
146
|
-
simplifyOpts.ratio = Math.min(Math.max(.1, t), .95);
|
|
147
|
-
simplifyOpts.error = 15 / density;
|
|
148
|
-
simplifyOpts.lockBorder = true;
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
simplifyOpts.ratio = 0.8;
|
|
152
|
-
simplifyOpts.error = 0.001;
|
|
153
|
-
simplifyOpts.lockBorder = true;
|
|
154
|
-
}
|
|
155
|
-
logger.info(`${mesh.getName()}: Density: ${density.toFixed(2)}, Ratio: ${simplifyOpts.ratio.toFixed(2)}, Error: ${simplifyOpts.error}`);
|
|
156
|
-
}
|
|
157
|
-
if (!ext && !isVRM) {
|
|
158
|
-
// If no extension exists on the primitive dont apply any simplification
|
|
159
|
-
// AND if the file is not a VRM file
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
if (mesh_compression) {
|
|
163
|
-
// Dont run if useSimplifier is turned off explictly
|
|
164
|
-
if (mesh_compression.useSimplifier === false) {
|
|
165
|
-
logger.debug(`[${NAME}]: Skipping primitive of mesh "${mesh.getName()}": useSimplifier is false.`);
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
if (mesh_compression.error >= 0)
|
|
169
|
-
simplifyOpts.error = mesh_compression.error;
|
|
170
|
-
if (mesh_compression.ratio >= 0)
|
|
171
|
-
simplifyOpts.ratio = mesh_compression.ratio;
|
|
172
|
-
simplifyOpts.lockBorder = mesh_compression.lockBorder;
|
|
173
|
-
}
|
|
174
|
-
if (prim.getMode() !== Primitive.Mode.TRIANGLES) {
|
|
175
|
-
logger.warn(`[${NAME}]: Skipping primitive of mesh "${mesh.getName()}": Requires TRIANGLES draw mode.`);
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
compressPrimitive(document, mesh, prim, index, weldOptions, simplifyOpts);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
export async function compressPrimitive(document, mesh, prim, index, weldOptions, meshOptions) {
|
|
184
|
-
const logger = document.getLogger();
|
|
185
|
-
logger.debug(`weld: primitive[${index}] of mesh "${mesh.getName()}"`);
|
|
186
|
-
weldPrimitive(prim, weldOptions);
|
|
187
|
-
// make sure we wait for the mesh simplifier to be ready
|
|
188
|
-
if (meshOptions.simplifier && meshOptions.simplifier?.ready instanceof Promise) {
|
|
189
|
-
const res = await meshOptions.simplifier?.ready
|
|
190
|
-
.then(() => {
|
|
191
|
-
// ready!
|
|
192
|
-
return true;
|
|
193
|
-
})
|
|
194
|
-
.catch((err) => {
|
|
195
|
-
console.error(`[${NAME}]: Simplifier failed to load...\n${err}`);
|
|
196
|
-
return false;
|
|
197
|
-
});
|
|
198
|
-
if (!res)
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
// TODO: enable regularize once it's available: https://github.com/zeux/meshoptimizer/pull/914/files
|
|
202
|
-
logger.debug(`simplify: primitive[${index}] of mesh "${mesh.getName()}" → ratio: ${meshOptions.ratio}, error: ${meshOptions.error}, lockBorder: ${meshOptions.lockBorder}, regularize: ${meshOptions.regularize}, normalWeight: ${meshOptions.normalWeight}, colorWeight: ${meshOptions.colorWeight}, textureWeight: ${meshOptions.textureWeight}`);
|
|
203
|
-
return simplifyPrimitive(prim, meshOptions);
|
|
204
|
-
}
|
|
205
|
-
// function updatePrimitive(prim: Primitive, simplifier: any, positionArray, dstIndicesArray) {
|
|
206
|
-
// const position = prim.getAttribute('POSITION')!;
|
|
207
|
-
// const srcIndices = prim.getIndices()!;
|
|
208
|
-
// const srcVertexCount = position.getCount();
|
|
209
|
-
// const [remap, unique] = simplifier.compactMesh(dstIndicesArray);
|
|
210
|
-
// // logger.debug(`${NAME}: ${formatDeltaOp(position.getCount(), unique)} vertices, error: ${error.toFixed(4)}.`);
|
|
211
|
-
// // (3) Write vertex attributes.
|
|
212
|
-
// for (const srcAttribute of deepListAttributes(prim)) {
|
|
213
|
-
// const dstAttribute = srcAttribute.clone();
|
|
214
|
-
// remapAttribute(dstAttribute, remap, unique);
|
|
215
|
-
// deepSwapAttribute(prim, srcAttribute, dstAttribute);
|
|
216
|
-
// if (srcAttribute.listParents().length === 1) srcAttribute.dispose();
|
|
217
|
-
// }
|
|
218
|
-
// // (4) Write indices.
|
|
219
|
-
// const dstIndices = srcIndices.clone();
|
|
220
|
-
// dstIndices.setArray(srcVertexCount <= 65534 ? new Uint16Array(dstIndicesArray) : dstIndicesArray);
|
|
221
|
-
// prim.setIndices(dstIndices);
|
|
222
|
-
// if (srcIndices.listParents().length === 1) srcIndices.dispose();
|
|
223
|
-
// }
|
|
1
|
+
import { Primitive } from "@gltf-transform/core";
|
|
2
|
+
import { meshopt, simplifyPrimitive, SIMPLIFY_DEFAULTS, WELD_DEFAULTS, draco } from "@gltf-transform/functions";
|
|
3
|
+
import { MeshoptEncoder, MeshoptSimplifier } from "meshoptimizer";
|
|
4
|
+
import { NEEDLE_mesh_compression, NEEDLE_mesh_compression_model_root } from "../extensions/index.js";
|
|
5
|
+
import { weldPrimitive } from "../functions/weld.3.js";
|
|
6
|
+
import { calculateMeshDensity } from "../utils/mesh.js";
|
|
7
|
+
import { isMeshLOD } from "./needle_progressive.js";
|
|
8
|
+
const NAME = 'needle_mesh_transform';
|
|
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
|
|
11
|
+
*/
|
|
12
|
+
export function determineMeshCompression(document, filePath) {
|
|
13
|
+
const rootExtension = document.getRoot().getExtension(NEEDLE_mesh_compression.EXTENSION_NAME);
|
|
14
|
+
if (rootExtension) {
|
|
15
|
+
const rootModel = rootExtension.model;
|
|
16
|
+
if (rootModel) {
|
|
17
|
+
if (!rootModel.compression) {
|
|
18
|
+
throw new Error(`[${NAME}]: Invalid mesh compression model extension on root node of ${filePath}`);
|
|
19
|
+
}
|
|
20
|
+
return rootModel;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (filePath?.toLowerCase().endsWith(".vrm")) {
|
|
24
|
+
const hasBlendshapes = document.getRoot().listMeshes().some((mesh) => mesh.listPrimitives().some((prim) => prim.listTargets().length > 0));
|
|
25
|
+
if (hasBlendshapes) {
|
|
26
|
+
return new NEEDLE_mesh_compression_model_root("meshopt");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// heuristic to detect if we should choose meshopt over draco
|
|
30
|
+
// because meshopt can compress blendshapes while draco can not
|
|
31
|
+
// we currently only consider the vertex count of the blendshapes
|
|
32
|
+
let sizeOfBlendshapes = 0;
|
|
33
|
+
let sizeOfMeshes = 0;
|
|
34
|
+
for (const mesh of document.getRoot().listMeshes()) {
|
|
35
|
+
for (const prim of mesh.listPrimitives()) {
|
|
36
|
+
for (const target of prim.listTargets()) {
|
|
37
|
+
sizeOfBlendshapes += target.getAttribute('POSITION').getArray().length;
|
|
38
|
+
}
|
|
39
|
+
sizeOfMeshes += prim.getAttribute('POSITION').getArray().length;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// TODO: make this more robus with progressive loading: when progressive loading is enabled we need to run this check on the root mesh and enforce the same mesh compression for all children. Until we can do that we can not use the code for choosing compression based on blendshape size
|
|
43
|
+
if (sizeOfBlendshapes > 0) {
|
|
44
|
+
document.getLogger().info(`[${NAME}]: will apply meshopt mesh compression because the mesh contains blendshapes`);
|
|
45
|
+
return new NEEDLE_mesh_compression_model_root("meshopt");
|
|
46
|
+
}
|
|
47
|
+
// consider meshopt if blendshape size is > 256 KB
|
|
48
|
+
// const sizeThreshold = 256 * 1024;
|
|
49
|
+
// if (sizeOfBlendshapes > sizeThreshold) {
|
|
50
|
+
// /** How big are the blendshapes compared to the overall mesh size */
|
|
51
|
+
// const blendshapeRatio = sizeOfBlendshapes / sizeOfMeshes;
|
|
52
|
+
// /** 25% of the size are blendshapres */
|
|
53
|
+
// const ratioThreshold = 0.1;
|
|
54
|
+
// if (blendshapeRatio >= ratioThreshold) {
|
|
55
|
+
// document.getLogger().info(`[${NAME}]: will apply meshopt mesh compression because blendshapes are ${(blendshapeRatio * 100).toFixed(2)}% of the mesh size`);
|
|
56
|
+
// const transform = meshopt({ encoder: MeshoptEncoder });
|
|
57
|
+
// return transform(document, transform_context);
|
|
58
|
+
// }
|
|
59
|
+
// }
|
|
60
|
+
return new NEEDLE_mesh_compression_model_root("draco");
|
|
61
|
+
}
|
|
62
|
+
export const needle_mesh_transform = function (context) {
|
|
63
|
+
return async (document, _context) => {
|
|
64
|
+
// TODO: needs to be moved into NEEDLE_progressive
|
|
65
|
+
await simplifierTransform(document, _context, context);
|
|
66
|
+
await applyMeshCompression(document, _context, context);
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
async function applyMeshCompression(document, transform_context, context) {
|
|
70
|
+
const isLOD = isMeshLOD(context.file);
|
|
71
|
+
const compressionModel = determineMeshCompression(document, context.file); // document.getRoot().getExtension(NEEDLE_mesh_compression.EXTENSION_NAME) as NEEDLE_mesh_compression_property;
|
|
72
|
+
if (compressionModel) {
|
|
73
|
+
// const rootModel = rootExtension.model as NEEDLE_mesh_compression_model_root;
|
|
74
|
+
if (compressionModel.compression) {
|
|
75
|
+
if (compressionModel.compression === "none") {
|
|
76
|
+
document.getLogger().info(`[${NAME}]: NONE → will not apply mesh compression`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
document.getLogger().info(`[${NAME}]: ${compressionModel.compression.toUpperCase()} → will apply ${compressionModel.compression} mesh compression`);
|
|
80
|
+
if (compressionModel.compression === "draco") {
|
|
81
|
+
const transform = draco({
|
|
82
|
+
encodeSpeed: 8,
|
|
83
|
+
decodeSpeed: 8,
|
|
84
|
+
quantizationVolume: isLOD ? "scene" : undefined
|
|
85
|
+
});
|
|
86
|
+
return transform(document, transform_context);
|
|
87
|
+
}
|
|
88
|
+
else if (compressionModel.compression === "meshopt") {
|
|
89
|
+
const transform = meshopt({ encoder: MeshoptEncoder });
|
|
90
|
+
return transform(document, transform_context);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// by default we still want to run draco if the root doesnt have a compression model configured
|
|
95
|
+
document.getLogger().info(`[${NAME}]: → will apply draco mesh compression (default)`);
|
|
96
|
+
const transform = draco({
|
|
97
|
+
encodeSpeed: 8,
|
|
98
|
+
decodeSpeed: 8,
|
|
99
|
+
quantizationVolume: isLOD ? "scene" : undefined
|
|
100
|
+
});
|
|
101
|
+
return transform(document, transform_context);
|
|
102
|
+
}
|
|
103
|
+
async function simplifierTransform(document, _context, meshContext) {
|
|
104
|
+
const simplifier = MeshoptSimplifier;
|
|
105
|
+
if (!simplifier) {
|
|
106
|
+
console.log(`WARN: [${NAME}] simplifier dependency required — install "meshoptimizer".`);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const logger = document.getLogger();
|
|
110
|
+
const isVRM = meshContext.file?.toLowerCase().endsWith(".vrm");
|
|
111
|
+
const options = {
|
|
112
|
+
...SIMPLIFY_DEFAULTS,
|
|
113
|
+
simplifier: simplifier,
|
|
114
|
+
ratio: 0.75,
|
|
115
|
+
error: 0.0005,
|
|
116
|
+
};
|
|
117
|
+
const weldOptions = {
|
|
118
|
+
...WELD_DEFAULTS,
|
|
119
|
+
overwrite: true,
|
|
120
|
+
};
|
|
121
|
+
// let meshInformation: MeshInformation | null = null;
|
|
122
|
+
// if (isVRM) {
|
|
123
|
+
// meshInformation = getMeshInformation(document);
|
|
124
|
+
// }
|
|
125
|
+
for (const mesh of document.getRoot().listMeshes()) {
|
|
126
|
+
let i = 0;
|
|
127
|
+
for (const prim of mesh.listPrimitives()) {
|
|
128
|
+
const index = i++;
|
|
129
|
+
// TODO: mesh compression caching
|
|
130
|
+
// const key = getKey(prim);
|
|
131
|
+
// const cached = tryGetFromCache(key);
|
|
132
|
+
// if (cached) {
|
|
133
|
+
// logger.debug(`[${NAME}]: Skipping primitive of mesh "${mesh.getName()}": Already cached.`);
|
|
134
|
+
// continue;
|
|
135
|
+
// }
|
|
136
|
+
// updatePrimitive(prim, simplifier, null, prim.getIndices());
|
|
137
|
+
// continue;
|
|
138
|
+
const ext = prim.getExtension(NEEDLE_mesh_compression.EXTENSION_NAME);
|
|
139
|
+
const mesh_compression = ext?.model;
|
|
140
|
+
const simplifyOpts = { ...options };
|
|
141
|
+
if (isVRM) {
|
|
142
|
+
const density = calculateMeshDensity(prim);
|
|
143
|
+
if (density > 0) {
|
|
144
|
+
const targetDensity = 2_000;
|
|
145
|
+
const t = (1 / density) * targetDensity;
|
|
146
|
+
simplifyOpts.ratio = Math.min(Math.max(.1, t), .95);
|
|
147
|
+
simplifyOpts.error = 15 / density;
|
|
148
|
+
simplifyOpts.lockBorder = true;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
simplifyOpts.ratio = 0.8;
|
|
152
|
+
simplifyOpts.error = 0.001;
|
|
153
|
+
simplifyOpts.lockBorder = true;
|
|
154
|
+
}
|
|
155
|
+
logger.info(`${mesh.getName()}: Density: ${density.toFixed(2)}, Ratio: ${simplifyOpts.ratio.toFixed(2)}, Error: ${simplifyOpts.error}`);
|
|
156
|
+
}
|
|
157
|
+
if (!ext && !isVRM) {
|
|
158
|
+
// If no extension exists on the primitive dont apply any simplification
|
|
159
|
+
// AND if the file is not a VRM file
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (mesh_compression) {
|
|
163
|
+
// Dont run if useSimplifier is turned off explictly
|
|
164
|
+
if (mesh_compression.useSimplifier === false) {
|
|
165
|
+
logger.debug(`[${NAME}]: Skipping primitive of mesh "${mesh.getName()}": useSimplifier is false.`);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (mesh_compression.error >= 0)
|
|
169
|
+
simplifyOpts.error = mesh_compression.error;
|
|
170
|
+
if (mesh_compression.ratio >= 0)
|
|
171
|
+
simplifyOpts.ratio = mesh_compression.ratio;
|
|
172
|
+
simplifyOpts.lockBorder = mesh_compression.lockBorder;
|
|
173
|
+
}
|
|
174
|
+
if (prim.getMode() !== Primitive.Mode.TRIANGLES) {
|
|
175
|
+
logger.warn(`[${NAME}]: Skipping primitive of mesh "${mesh.getName()}": Requires TRIANGLES draw mode.`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
compressPrimitive(document, mesh, prim, index, weldOptions, simplifyOpts);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
export async function compressPrimitive(document, mesh, prim, index, weldOptions, meshOptions) {
|
|
184
|
+
const logger = document.getLogger();
|
|
185
|
+
logger.debug(`weld: primitive[${index}] of mesh "${mesh.getName()}"`);
|
|
186
|
+
weldPrimitive(prim, weldOptions);
|
|
187
|
+
// make sure we wait for the mesh simplifier to be ready
|
|
188
|
+
if (meshOptions.simplifier && meshOptions.simplifier?.ready instanceof Promise) {
|
|
189
|
+
const res = await meshOptions.simplifier?.ready
|
|
190
|
+
.then(() => {
|
|
191
|
+
// ready!
|
|
192
|
+
return true;
|
|
193
|
+
})
|
|
194
|
+
.catch((err) => {
|
|
195
|
+
console.error(`[${NAME}]: Simplifier failed to load...\n${err}`);
|
|
196
|
+
return false;
|
|
197
|
+
});
|
|
198
|
+
if (!res)
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// TODO: enable regularize once it's available: https://github.com/zeux/meshoptimizer/pull/914/files
|
|
202
|
+
logger.debug(`simplify: primitive[${index}] of mesh "${mesh.getName()}" → ratio: ${meshOptions.ratio}, error: ${meshOptions.error}, lockBorder: ${meshOptions.lockBorder}, regularize: ${meshOptions.regularize}, normalWeight: ${meshOptions.normalWeight}, colorWeight: ${meshOptions.colorWeight}, textureWeight: ${meshOptions.textureWeight}`);
|
|
203
|
+
return simplifyPrimitive(prim, meshOptions);
|
|
204
|
+
}
|
|
205
|
+
// function updatePrimitive(prim: Primitive, simplifier: any, positionArray, dstIndicesArray) {
|
|
206
|
+
// const position = prim.getAttribute('POSITION')!;
|
|
207
|
+
// const srcIndices = prim.getIndices()!;
|
|
208
|
+
// const srcVertexCount = position.getCount();
|
|
209
|
+
// const [remap, unique] = simplifier.compactMesh(dstIndicesArray);
|
|
210
|
+
// // logger.debug(`${NAME}: ${formatDeltaOp(position.getCount(), unique)} vertices, error: ${error.toFixed(4)}.`);
|
|
211
|
+
// // (3) Write vertex attributes.
|
|
212
|
+
// for (const srcAttribute of deepListAttributes(prim)) {
|
|
213
|
+
// const dstAttribute = srcAttribute.clone();
|
|
214
|
+
// remapAttribute(dstAttribute, remap, unique);
|
|
215
|
+
// deepSwapAttribute(prim, srcAttribute, dstAttribute);
|
|
216
|
+
// if (srcAttribute.listParents().length === 1) srcAttribute.dispose();
|
|
217
|
+
// }
|
|
218
|
+
// // (4) Write indices.
|
|
219
|
+
// const dstIndices = srcIndices.clone();
|
|
220
|
+
// dstIndices.setArray(srcVertexCount <= 65534 ? new Uint16Array(dstIndicesArray) : dstIndicesArray);
|
|
221
|
+
// prim.setIndices(dstIndices);
|
|
222
|
+
// if (srcIndices.listParents().length === 1) srcIndices.dispose();
|
|
223
|
+
// }
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { ILogger, Texture, Transform, vec2 } from '@gltf-transform/core';
|
|
2
|
-
import { Config } from '../config/index.js';
|
|
3
|
-
declare interface ProgressiveOptions {
|
|
4
|
-
path: string | Array<string>;
|
|
5
|
-
/** Directory to output progressive assets to */
|
|
6
|
-
outpath?: string;
|
|
7
|
-
results: Array<string>;
|
|
8
|
-
config: Config;
|
|
9
|
-
debug?: boolean;
|
|
10
|
-
verbose?: boolean;
|
|
11
|
-
useCache?: boolean;
|
|
12
|
-
logger: ILogger;
|
|
13
|
-
}
|
|
14
|
-
export declare function make_progressive(opts: ProgressiveOptions): Promise<boolean>;
|
|
15
|
-
export declare function createTransform(name: string, fn: Transform): Transform;
|
|
16
|
-
export declare function make_progressive_textures(filePath: string, _options: TextureResizeOptions, opts: ProgressiveOptions): Transform;
|
|
17
|
-
/** Options for the {@link textureResize} function. */
|
|
18
|
-
export interface TextureResizeOptions {
|
|
19
|
-
/**
|
|
20
|
-
* Maximum width/height to enforce, preserving aspect ratio. For example,
|
|
21
|
-
* a 4096x8192 texture, resized with limit [2048, 2048] will be reduced
|
|
22
|
-
* to 1024x2048.
|
|
23
|
-
*/
|
|
24
|
-
size: vec2;
|
|
25
|
-
/** Resampling filter method. LANCZOS3 is sharper, LANCZOS2 is smoother. */
|
|
26
|
-
filter?: TextureResizeFilter;
|
|
27
|
-
/** Pattern identifying textures to resize, matched to name or URI. */
|
|
28
|
-
pattern?: RegExp | null;
|
|
29
|
-
/** Pattern to match slots usage for resizing. */
|
|
30
|
-
slots?: RegExp | null;
|
|
31
|
-
}
|
|
32
|
-
/** Resampling filter methods. LANCZOS3 is sharper, LANCZOS2 is smoother. */
|
|
33
|
-
export declare enum TextureResizeFilter {
|
|
34
|
-
/** Lanczos3 (sharp) */
|
|
35
|
-
LANCZOS3 = "lanczos3",
|
|
36
|
-
/** Lanczos2 (smooth) */
|
|
37
|
-
LANCZOS2 = "lanczos2"
|
|
38
|
-
}
|
|
39
|
-
export declare const TEXTURE_RESIZE_DEFAULTS: TextureResizeOptions;
|
|
40
|
-
export declare function resizeTextureToMax(texture: Texture, maxSize: number, logger: ILogger): Promise<void>;
|
|
41
|
-
export declare function make_progressive_meshes(filePath: string, _options: any, opts?: ProgressiveOptions): Transform;
|
|
42
|
-
export declare function isMeshLOD(filepath: string): boolean;
|
|
43
|
-
export declare function isImageLOD(filepath: string): boolean;
|
|
44
|
-
export declare function isLOD(filepath: string): boolean;
|
|
45
|
-
export {};
|
|
1
|
+
import { ILogger, Texture, Transform, vec2 } from '@gltf-transform/core';
|
|
2
|
+
import { Config } from '../config/index.js';
|
|
3
|
+
declare interface ProgressiveOptions {
|
|
4
|
+
path: string | Array<string>;
|
|
5
|
+
/** Directory to output progressive assets to */
|
|
6
|
+
outpath?: string;
|
|
7
|
+
results: Array<string>;
|
|
8
|
+
config: Config;
|
|
9
|
+
debug?: boolean;
|
|
10
|
+
verbose?: boolean;
|
|
11
|
+
useCache?: boolean;
|
|
12
|
+
logger: ILogger;
|
|
13
|
+
}
|
|
14
|
+
export declare function make_progressive(opts: ProgressiveOptions): Promise<boolean>;
|
|
15
|
+
export declare function createTransform(name: string, fn: Transform): Transform;
|
|
16
|
+
export declare function make_progressive_textures(filePath: string, _options: TextureResizeOptions, opts: ProgressiveOptions): Transform;
|
|
17
|
+
/** Options for the {@link textureResize} function. */
|
|
18
|
+
export interface TextureResizeOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Maximum width/height to enforce, preserving aspect ratio. For example,
|
|
21
|
+
* a 4096x8192 texture, resized with limit [2048, 2048] will be reduced
|
|
22
|
+
* to 1024x2048.
|
|
23
|
+
*/
|
|
24
|
+
size: vec2;
|
|
25
|
+
/** Resampling filter method. LANCZOS3 is sharper, LANCZOS2 is smoother. */
|
|
26
|
+
filter?: TextureResizeFilter;
|
|
27
|
+
/** Pattern identifying textures to resize, matched to name or URI. */
|
|
28
|
+
pattern?: RegExp | null;
|
|
29
|
+
/** Pattern to match slots usage for resizing. */
|
|
30
|
+
slots?: RegExp | null;
|
|
31
|
+
}
|
|
32
|
+
/** Resampling filter methods. LANCZOS3 is sharper, LANCZOS2 is smoother. */
|
|
33
|
+
export declare enum TextureResizeFilter {
|
|
34
|
+
/** Lanczos3 (sharp) */
|
|
35
|
+
LANCZOS3 = "lanczos3",
|
|
36
|
+
/** Lanczos2 (smooth) */
|
|
37
|
+
LANCZOS2 = "lanczos2"
|
|
38
|
+
}
|
|
39
|
+
export declare const TEXTURE_RESIZE_DEFAULTS: TextureResizeOptions;
|
|
40
|
+
export declare function resizeTextureToMax(texture: Texture, maxSize: number, logger: ILogger): Promise<void>;
|
|
41
|
+
export declare function make_progressive_meshes(filePath: string, _options: any, opts?: ProgressiveOptions): Transform;
|
|
42
|
+
export declare function isMeshLOD(filepath: string): boolean;
|
|
43
|
+
export declare function isImageLOD(filepath: string): boolean;
|
|
44
|
+
export declare function isLOD(filepath: string): boolean;
|
|
45
|
+
export {};
|