@needle-tools/gltf-build-pipeline 1.4.1-beta.10 → 1.4.1-beta.12
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 +9 -0
- package/dist/cache/cache.d.ts +38 -1
- package/dist/cache/cache.js +70 -0
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.d.ts +23 -13
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.js +7 -5
- package/dist/transforms/needle_mesh_transform.d.ts +1 -1
- package/dist/transforms/needle_mesh_transform.js +3 -3
- package/dist/transforms/needle_progressive.js +72 -30
- package/dist/utils/mesh.d.ts +1 -0
- package/dist/utils/mesh.js +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +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.12] - 2023-03-23
|
|
8
|
+
- add: NEEDLE_progressive LODs can now contain more info per texture or mesh. Meshes contain vertex- and index counts
|
|
9
|
+
- change: the cache can now store and retrieve meta information
|
|
10
|
+
|
|
11
|
+
## [1.4.1-beta.11] - 2023-03-20
|
|
12
|
+
- add: NEEDLE_progressive LOD meshes morph targets data
|
|
13
|
+
- fix: NEEDLE_progressive should check if simplifcation did result in improved meshes (> 32 verts removed && > 5 % improvement) and otherwise discard LODs (and the whole extension if no improvement was made)
|
|
14
|
+
- change: Unity NEEDLE_progressive logs
|
|
15
|
+
|
|
7
16
|
## [1.4.1-beta.10] - 2023-03-19
|
|
8
17
|
- change: NEEDLE_progressive calculates density per mesh level
|
|
9
18
|
- change: NEEDLE_progressive `uri` field is removed and added `lod` array of objects
|
package/dist/cache/cache.d.ts
CHANGED
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
import { ExtensibleProperty } from "@gltf-transform/core";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Clear the cache.
|
|
4
|
+
* This will remove all files in the cache directory.
|
|
4
5
|
*/
|
|
5
6
|
export declare function clearCache(): void;
|
|
7
|
+
/**
|
|
8
|
+
* Ensure that the cache size does not exceed the given maximum size in MB.
|
|
9
|
+
* @param maxSizeMB The maximum size in MB.
|
|
10
|
+
*/
|
|
6
11
|
export declare function ensureCacheSize(maxSizeMB: number): void;
|
|
12
|
+
/**
|
|
13
|
+
* Add data to the cache.
|
|
14
|
+
* @param key The key to store the data under.
|
|
15
|
+
* @param bytes The data to store as a Uint8Array.
|
|
16
|
+
*/
|
|
7
17
|
export declare function addToCache(key: string, bytes: Uint8Array): void;
|
|
18
|
+
/**
|
|
19
|
+
* Try to get the data from the cache.
|
|
20
|
+
* @param key The key to look up.
|
|
21
|
+
* @returns The data as a Uint8Array or null if not found.
|
|
22
|
+
* @throws If the cache file is corrupted.
|
|
23
|
+
*/
|
|
8
24
|
export declare function tryGetFromCache(key: string): Uint8Array | null;
|
|
25
|
+
/** Add meta data to the cache.
|
|
26
|
+
* @param key The key to store the meta data for.
|
|
27
|
+
* @param meta The meta data object to store. It will be serialized to JSON and stored in a file with the key as the name with the extension ".meta".
|
|
28
|
+
*/
|
|
29
|
+
export declare function addMetaToCache(key: string, meta: object): void;
|
|
30
|
+
/** Try to get the meta data from the cache.
|
|
31
|
+
* @param key The key to look up.
|
|
32
|
+
* @param ignoreVersion If true, the version check is skipped. If false, the version must match the current build pipeline version.
|
|
33
|
+
* @returns The meta data object or null if not found.
|
|
34
|
+
*/
|
|
35
|
+
export declare function tryGetMetaFromCache(key: string, ignoreVersion?: boolean): object | null;
|
|
36
|
+
/**
|
|
37
|
+
* Get the cache key for a property.
|
|
38
|
+
* @param property The property to get the key for.
|
|
39
|
+
* @returns The key as a string.
|
|
40
|
+
*/
|
|
9
41
|
export declare function getKey(property: ExtensibleProperty): string;
|
|
42
|
+
/**
|
|
43
|
+
* Get the hash for a property or an object.
|
|
44
|
+
* @param property The property to get the hash for.
|
|
45
|
+
* @returns The hash as a number.
|
|
46
|
+
*/
|
|
10
47
|
export declare function getHash(property: ExtensibleProperty | object): number;
|
package/dist/cache/cache.js
CHANGED
|
@@ -2,6 +2,7 @@ import { ExtensibleProperty, Texture, Primitive, Accessor, Mesh } from "@gltf-tr
|
|
|
2
2
|
import tmp from 'tmp';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import { getVersion } from "../utils/version.js";
|
|
5
6
|
// import * as CHECKDISCSPACE from 'check-disk-space'
|
|
6
7
|
/**
|
|
7
8
|
* Get the available space on the cache directory in bytes.
|
|
@@ -17,11 +18,20 @@ import path from 'path';
|
|
|
17
18
|
// }
|
|
18
19
|
// return 0;
|
|
19
20
|
// }
|
|
21
|
+
const buildPipelineVersion = getVersion(false);
|
|
22
|
+
/**
|
|
23
|
+
* Clear the cache.
|
|
24
|
+
* This will remove all files in the cache directory.
|
|
25
|
+
*/
|
|
20
26
|
export function clearCache() {
|
|
21
27
|
const dir = getCacheDirectory();
|
|
22
28
|
fs.rmSync(dir, { recursive: true });
|
|
23
29
|
console.log(`Cleared cache at ${dir}`);
|
|
24
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Ensure that the cache size does not exceed the given maximum size in MB.
|
|
33
|
+
* @param maxSizeMB The maximum size in MB.
|
|
34
|
+
*/
|
|
25
35
|
export function ensureCacheSize(maxSizeMB) {
|
|
26
36
|
const dir = getCacheDirectory();
|
|
27
37
|
let totalSize = 0;
|
|
@@ -50,12 +60,23 @@ export function ensureCacheSize(maxSizeMB) {
|
|
|
50
60
|
}
|
|
51
61
|
console.log(`Cache size reduced to ${(totalSize / (1024 * 1024)).toFixed(1)} MB`);
|
|
52
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Add data to the cache.
|
|
65
|
+
* @param key The key to store the data under.
|
|
66
|
+
* @param bytes The data to store as a Uint8Array.
|
|
67
|
+
*/
|
|
53
68
|
export function addToCache(key, bytes) {
|
|
54
69
|
const dir = getCacheDirectory();
|
|
55
70
|
const path = `${dir}/${key}.bin`;
|
|
56
71
|
console.log(`Added ${(bytes.byteLength / 1024).toFixed(1)} KB to cache at \"${path}\"`);
|
|
57
72
|
fs.writeFileSync(path, bytes);
|
|
58
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Try to get the data from the cache.
|
|
76
|
+
* @param key The key to look up.
|
|
77
|
+
* @returns The data as a Uint8Array or null if not found.
|
|
78
|
+
* @throws If the cache file is corrupted.
|
|
79
|
+
*/
|
|
59
80
|
export function tryGetFromCache(key) {
|
|
60
81
|
const dir = getCacheDirectory();
|
|
61
82
|
const path = `${dir}/${key}.bin`;
|
|
@@ -65,6 +86,50 @@ export function tryGetFromCache(key) {
|
|
|
65
86
|
}
|
|
66
87
|
return null;
|
|
67
88
|
}
|
|
89
|
+
/** Add meta data to the cache.
|
|
90
|
+
* @param key The key to store the meta data for.
|
|
91
|
+
* @param meta The meta data object to store. It will be serialized to JSON and stored in a file with the key as the name with the extension ".meta".
|
|
92
|
+
*/
|
|
93
|
+
export function addMetaToCache(key, meta) {
|
|
94
|
+
const dir = getCacheDirectory();
|
|
95
|
+
const metaPath = `${dir}/${key}.meta`;
|
|
96
|
+
meta['needle-build-pipeline:version'] = buildPipelineVersion;
|
|
97
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta));
|
|
98
|
+
}
|
|
99
|
+
/** Try to get the meta data from the cache.
|
|
100
|
+
* @param key The key to look up.
|
|
101
|
+
* @param ignoreVersion If true, the version check is skipped. If false, the version must match the current build pipeline version.
|
|
102
|
+
* @returns The meta data object or null if not found.
|
|
103
|
+
*/
|
|
104
|
+
export function tryGetMetaFromCache(key, ignoreVersion = false) {
|
|
105
|
+
try {
|
|
106
|
+
const dir = getCacheDirectory();
|
|
107
|
+
const path = `${dir}/${key}.meta`;
|
|
108
|
+
if (fs.existsSync(path)) {
|
|
109
|
+
const bytes = fs.readFileSync(path);
|
|
110
|
+
if (bytes?.length > 0) {
|
|
111
|
+
const text = bytes.toString('utf-8');
|
|
112
|
+
if (text) {
|
|
113
|
+
const obj = JSON.parse(text);
|
|
114
|
+
if (ignoreVersion)
|
|
115
|
+
return obj;
|
|
116
|
+
if (obj['needle-build-pipeline:version'] === buildPipelineVersion)
|
|
117
|
+
return obj;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
console.error(`Error reading cache meta: ${e.message}`);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get the cache key for a property.
|
|
130
|
+
* @param property The property to get the key for.
|
|
131
|
+
* @returns The key as a string.
|
|
132
|
+
*/
|
|
68
133
|
export function getKey(property) {
|
|
69
134
|
let prefix = "prop";
|
|
70
135
|
if (property instanceof Texture) {
|
|
@@ -74,6 +139,11 @@ export function getKey(property) {
|
|
|
74
139
|
const key = `${prefix}-${hash}`;
|
|
75
140
|
return key;
|
|
76
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Get the hash for a property or an object.
|
|
144
|
+
* @param property The property to get the hash for.
|
|
145
|
+
* @returns The hash as a number.
|
|
146
|
+
*/
|
|
77
147
|
export function getHash(property) {
|
|
78
148
|
let hash = 0;
|
|
79
149
|
if (property instanceof ExtensibleProperty) {
|
|
@@ -1,29 +1,39 @@
|
|
|
1
|
-
import { Extension, ExtensionProperty,
|
|
1
|
+
import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
|
|
2
2
|
export declare const NEEDLE_PROGRESSIVE_EXTENSION_NAME = "NEEDLE_progressive";
|
|
3
|
-
export
|
|
3
|
+
export type ProgressiveLoadingInfo = {
|
|
4
4
|
/** guid of the asset the LODs belong to */
|
|
5
5
|
guid: string;
|
|
6
|
-
/**
|
|
7
|
-
lods?: Array<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
/** lod infos and paths */
|
|
7
|
+
lods?: Array<LodInfo>;
|
|
8
|
+
};
|
|
9
|
+
type LodInfo = {
|
|
10
|
+
path: string;
|
|
11
|
+
};
|
|
12
|
+
export type ProgressiveTextureModel = ProgressiveLoadingInfo & {
|
|
13
|
+
lods?: Array<LodInfo & {}>;
|
|
14
|
+
};
|
|
15
|
+
export type ProgressiveMeshModel = ProgressiveLoadingInfo & {
|
|
16
|
+
density: number;
|
|
17
|
+
lods?: Array<LodInfo & {
|
|
18
|
+
density: number;
|
|
19
|
+
vertexCount: number;
|
|
20
|
+
indexCount: number;
|
|
11
21
|
}>;
|
|
12
|
-
|
|
13
|
-
}
|
|
22
|
+
};
|
|
14
23
|
export declare class NEEDLE_progressive extends Extension {
|
|
15
24
|
static EXTENSION_NAME: string;
|
|
16
25
|
extensionName: string;
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
createTexture(data: ProgressiveTextureModel): NEEDLE_progressive_property;
|
|
27
|
+
createMesh(data: ProgressiveMeshModel): NEEDLE_progressive_property;
|
|
19
28
|
read(_context: ReaderContext): this;
|
|
20
29
|
write(context: WriterContext): this;
|
|
21
30
|
}
|
|
22
|
-
export declare class
|
|
31
|
+
export declare class NEEDLE_progressive_property extends ExtensionProperty {
|
|
23
32
|
static EXTENSION_NAME: string;
|
|
24
33
|
readonly extensionName: string;
|
|
25
34
|
parentTypes: PropertyType[];
|
|
26
35
|
propertyType: string;
|
|
27
36
|
init(): void;
|
|
28
|
-
model:
|
|
37
|
+
model: ProgressiveLoadingInfo;
|
|
29
38
|
}
|
|
39
|
+
export {};
|
|
@@ -4,13 +4,15 @@ export const NEEDLE_PROGRESSIVE_EXTENSION_NAME = "NEEDLE_progressive";
|
|
|
4
4
|
export class NEEDLE_progressive extends Extension {
|
|
5
5
|
static EXTENSION_NAME = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
|
|
6
6
|
extensionName = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
|
|
7
|
-
|
|
8
|
-
const ext = new
|
|
7
|
+
createTexture(data) {
|
|
8
|
+
const ext = new NEEDLE_progressive_property(this.document.getGraph());
|
|
9
9
|
ext.model = data;
|
|
10
10
|
return ext;
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
createMesh(data) {
|
|
13
|
+
const ext = new NEEDLE_progressive_property(this.document.getGraph());
|
|
14
|
+
ext.model = data;
|
|
15
|
+
return ext;
|
|
14
16
|
}
|
|
15
17
|
read(_context) {
|
|
16
18
|
// console.log("READ " + NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME);
|
|
@@ -57,7 +59,7 @@ export class NEEDLE_progressive extends Extension {
|
|
|
57
59
|
return this;
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
|
-
export class
|
|
62
|
+
export class NEEDLE_progressive_property extends ExtensionProperty {
|
|
61
63
|
static EXTENSION_NAME = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
|
|
62
64
|
extensionName = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
|
|
63
65
|
parentTypes = [PropertyType.TEXTURE, PropertyType.MESH];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Transform, Document, Primitive, Mesh } from "@gltf-transform/core";
|
|
2
2
|
import { SimplifyOptions, WeldOptions } from "@gltf-transform/functions";
|
|
3
3
|
export declare const needle_mesh_transform: (_options: SimplifyOptions) => Transform;
|
|
4
|
-
export declare function compressPrimitive(document: Document, mesh: Mesh, prim: Primitive, index: number, weldOptions: Required<WeldOptions>, meshOptions: Required<SimplifyOptions>): Promise<
|
|
4
|
+
export declare function compressPrimitive(document: Document, mesh: Mesh, prim: Primitive, index: number, weldOptions: Required<WeldOptions>, meshOptions: Required<SimplifyOptions>): Promise<Primitive>;
|
|
@@ -93,7 +93,7 @@ async function simplifierTransform(document, _context, _options) {
|
|
|
93
93
|
}
|
|
94
94
|
export async function compressPrimitive(document, mesh, prim, index, weldOptions, meshOptions) {
|
|
95
95
|
const logger = document.getLogger();
|
|
96
|
-
logger.debug(`
|
|
96
|
+
logger.debug(`weld: primitive[${index}] of mesh "${mesh.getName()}" → tolerance: ${weldOptions.tolerance}`);
|
|
97
97
|
weldPrimitive(document, prim, weldOptions);
|
|
98
98
|
// make sure we wait for the mesh simplifier to be ready
|
|
99
99
|
if (meshOptions.simplifier && meshOptions.simplifier?.ready instanceof Promise) {
|
|
@@ -109,8 +109,8 @@ export async function compressPrimitive(document, mesh, prim, index, weldOptions
|
|
|
109
109
|
if (!res)
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
|
-
logger.debug(`
|
|
113
|
-
simplifyPrimitive(document, prim, meshOptions);
|
|
112
|
+
logger.debug(`simplify: primitive[${index}] of mesh "${mesh.getName()}" → ratio: ${meshOptions.ratio}, error: ${meshOptions.error}, lockBorder: ${meshOptions.lockBorder}`);
|
|
113
|
+
return simplifyPrimitive(document, prim, meshOptions);
|
|
114
114
|
}
|
|
115
115
|
// function updatePrimitive(prim: Primitive, simplifier: any, positionArray, dstIndicesArray) {
|
|
116
116
|
// const position = prim.getAttribute('POSITION')!;
|
|
@@ -4,7 +4,7 @@ import { NEEDLE_PROGRESSIVE_EXTENSION_NAME, NEEDLE_progressive } from "../extens
|
|
|
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
6
|
import { existsSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
7
|
-
import { addToCache, getHash, getKey, tryGetFromCache } from '../cache/index.js';
|
|
7
|
+
import { addMetaToCache, addToCache, getHash, getKey, tryGetFromCache, tryGetMetaFromCache } from '../cache/index.js';
|
|
8
8
|
import sharp from "sharp";
|
|
9
9
|
import { foreachGLTF, generateGuid } from '../utils/index.js';
|
|
10
10
|
import { needle_asset } from './needle_asset.js';
|
|
@@ -139,7 +139,7 @@ export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DE
|
|
|
139
139
|
continue;
|
|
140
140
|
}
|
|
141
141
|
const progressiveExt = doc.createExtension(NEEDLE_progressive);
|
|
142
|
-
if (!progressiveExt || !progressiveExt.
|
|
142
|
+
if (!progressiveExt || !progressiveExt.createTexture) {
|
|
143
143
|
logger.debug(`[${NAME}] Skipping because progressive extension is not available. ${textureIndex}`);
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
@@ -150,7 +150,7 @@ export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DE
|
|
|
150
150
|
}
|
|
151
151
|
const relName = path.relative(directory, outPath);
|
|
152
152
|
// TODO: this path should be added to the NEEDLE dependencies extension
|
|
153
|
-
texture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.
|
|
153
|
+
texture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.createTexture({
|
|
154
154
|
guid: textureGuid,
|
|
155
155
|
lods: [
|
|
156
156
|
{
|
|
@@ -231,7 +231,7 @@ function createTextureLod(basePath, level, texture, slots, guid, textureIndex) {
|
|
|
231
231
|
}
|
|
232
232
|
// add progressive texture extension
|
|
233
233
|
const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
|
|
234
|
-
newTexture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.
|
|
234
|
+
newTexture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.createTexture({
|
|
235
235
|
guid: guid,
|
|
236
236
|
}));
|
|
237
237
|
const newIo = new NodeIO();
|
|
@@ -306,7 +306,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
306
306
|
await MeshoptSimplifier.ready;
|
|
307
307
|
const logger = doc.getLogger();
|
|
308
308
|
const filename = path.basename(filePath);
|
|
309
|
-
console.log(
|
|
309
|
+
console.log(`[${NAME}]: Begin generating progressive meshes for \"${filename}\"`);
|
|
310
310
|
let index = 0;
|
|
311
311
|
// the lowest LOD (last entry) will be saved in the original file
|
|
312
312
|
const MAX_LODS = 6;
|
|
@@ -333,12 +333,13 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
333
333
|
const visited = new Map();
|
|
334
334
|
const meshes = doc.getRoot().listMeshes();
|
|
335
335
|
for (const mesh of meshes) {
|
|
336
|
-
|
|
337
|
-
const
|
|
336
|
+
/** The size and vertex count of the original mesh */
|
|
337
|
+
const originalMeshInformation = getMeshInformation(mesh);
|
|
338
|
+
const { totalVertexCount: originalTotalVertexCount, maxVertexCount: originalMaxVertexCount } = originalMeshInformation;
|
|
338
339
|
let settings = mesh.getExtension(NEEDLE_progressive_mesh_settings.EXTENSION_NAME);
|
|
339
340
|
if (!settings) {
|
|
340
|
-
if (
|
|
341
|
-
logger.debug(`[${NAME}] Skipping, no settings found and no primitive with more than 32 vertices. mesh[${index}] has ${
|
|
341
|
+
if (originalMaxVertexCount < 32) {
|
|
342
|
+
logger.debug(`[${NAME}] Skipping, no settings found and no primitive with more than 32 vertices. mesh[${index}] has ${originalTotalVertexCount} vertices (max: ${originalMaxVertexCount})`);
|
|
342
343
|
continue;
|
|
343
344
|
}
|
|
344
345
|
else {
|
|
@@ -346,7 +347,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
346
347
|
guid: generateGuid(path + "_mesh_" + index.toString()),
|
|
347
348
|
generateLods: true,
|
|
348
349
|
};
|
|
349
|
-
logger.debug(`[${NAME}] No settings found for mesh[${index}] with ${
|
|
350
|
+
logger.debug(`[${NAME}] No settings found for mesh[${index}] with ${originalTotalVertexCount} vertices (max: ${originalMaxVertexCount}) - will use default settings (guid: ${settings.guid})`);
|
|
350
351
|
}
|
|
351
352
|
}
|
|
352
353
|
else if (settings.generateLods === false) {
|
|
@@ -354,7 +355,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
354
355
|
continue;
|
|
355
356
|
}
|
|
356
357
|
const progressiveExt = doc.createExtension(NEEDLE_progressive);
|
|
357
|
-
if (!progressiveExt || !progressiveExt.
|
|
358
|
+
if (!progressiveExt || !progressiveExt.createMesh) {
|
|
358
359
|
logger.debug(`[${NAME}] Skipping because progressive extension is not available. ${index++}`);
|
|
359
360
|
continue;
|
|
360
361
|
}
|
|
@@ -371,12 +372,13 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
371
372
|
/** async simplification tasks... */
|
|
372
373
|
const tasks = new Array();
|
|
373
374
|
if (needsToGenerateLODs) {
|
|
375
|
+
console.log(`[${NAME}] Begin generating mesh LODs for \"${mesh.getName()}\"`);
|
|
374
376
|
// produce LODS, skip the last level because it will be saved in the original file
|
|
375
377
|
for (let level = 0; level < LODS.length - 1; level++) {
|
|
376
378
|
const lod = LODS[level];
|
|
377
379
|
const guid = settings.guid;
|
|
378
380
|
// TODO: check if the LOD level did actually change the mesh - otherwise we can just skip generating lower resolutions
|
|
379
|
-
const lodTask = createMeshLod(doc, outputDirectory, level, mesh,
|
|
381
|
+
const lodTask = createMeshLod(doc, outputDirectory, level, mesh, originalMeshInformation, guid, lod, opts);
|
|
380
382
|
lodTask.then(res => {
|
|
381
383
|
if (res?.success) {
|
|
382
384
|
generatedLODInfos[level] = res;
|
|
@@ -406,11 +408,11 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
406
408
|
let shouldOmitLevel = false;
|
|
407
409
|
// if the mesh size on disc is identical to the last level, we can omit this level
|
|
408
410
|
if (sizeOnDisc <= 0) {
|
|
409
|
-
console.log(`[${NAME}] mesh LOD ${level} size on disc is 0
|
|
411
|
+
console.log(`[${NAME}] Flag mesh LOD ${level}: size on disc is 0 → level will be omitted (if this mesh is is taken from cache it can mean that the LOD level was omitted before). \"${mesh.getName()}\" at \"${lodInfo.path}\"`);
|
|
410
412
|
shouldOmitLevel = true;
|
|
411
413
|
}
|
|
412
414
|
else if (diff == 0) {
|
|
413
|
-
console.log(`[${NAME}] mesh LOD ${level} size on disc did not change compared to previous level
|
|
415
|
+
console.log(`[${NAME}] Flag mesh LOD ${level}: size on disc did not change compared to previous level → level will be omitted. \"${mesh.getName()}\" at \"${lodInfo.path}\"`);
|
|
414
416
|
shouldOmitLevel = true;
|
|
415
417
|
}
|
|
416
418
|
else if (!shouldOmitLevel) {
|
|
@@ -419,13 +421,13 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
419
421
|
const lastMeshInfo = generatedLODInfos[i - 1]?.lodMeshInfo;
|
|
420
422
|
if (meshInfo && lastMeshInfo) {
|
|
421
423
|
const vertexDifference = lastMeshInfo.totalVertexCount - meshInfo.totalVertexCount;
|
|
422
|
-
const
|
|
424
|
+
const diff01 = vertexDifference / lastMeshInfo.totalVertexCount;
|
|
423
425
|
if (vertexDifference < 0) {
|
|
424
|
-
console.log(`WARN: [${NAME}] mesh LOD ${level} did increase
|
|
426
|
+
console.log(`WARN: ❌ [${NAME}] Flag mesh LOD ${level}: vertex count did increase (${-vertexDifference.toFixed(0)} vertices, ${(-diff01 * 100).toFixed(4)}%) → LOD ${level} will be omitted. \"${mesh.getName()}\" at \"${lodInfo.path}\" ${JSON.stringify(lastMeshInfo)} → ${JSON.stringify(meshInfo)}`);
|
|
425
427
|
shouldOmitLevel = true;
|
|
426
428
|
}
|
|
427
|
-
else if (vertexDifference < 32 ||
|
|
428
|
-
console.log(`[${NAME}] mesh LOD ${level}
|
|
429
|
+
else if (vertexDifference < 32 || diff01 < 0.05) {
|
|
430
|
+
console.log(`[${NAME}] Flag mesh LOD ${level}: vertex count difference below threshold (removed ${vertexDifference.toFixed(0)} vertices, ${(-diff01 * 100).toFixed(4)}%) → LOD ${level} will be omitted. \"${mesh.getName()}\" at \"${lodInfo.path}\"`);
|
|
429
431
|
shouldOmitLevel = true;
|
|
430
432
|
}
|
|
431
433
|
}
|
|
@@ -446,10 +448,9 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
446
448
|
}
|
|
447
449
|
let maxDensity = 0;
|
|
448
450
|
let primitiveIndex = 0;
|
|
451
|
+
const lodLevelInFile = generatedLODInfos.length;
|
|
449
452
|
for (const prim of mesh.listPrimitives()) {
|
|
450
|
-
const
|
|
451
|
-
const lastLod = LODS[lodsCount];
|
|
452
|
-
console.log(`[${NAME}] ${mesh.getName()} LOD ${lodsCount}`);
|
|
453
|
+
const lastLod = LODS[lodLevelInFile];
|
|
453
454
|
// TODO: this should be run after simplification but then we need to change the runtime to use the LOD 0 density
|
|
454
455
|
const density = calculateMeshDensity(prim);
|
|
455
456
|
maxDensity = Math.max(maxDensity, density);
|
|
@@ -459,16 +460,33 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
459
460
|
await Promise.all(tasks);
|
|
460
461
|
tasks.length = 0;
|
|
461
462
|
const { totalVertexCount: totalVertexCountAfter } = getMeshInformation(mesh);
|
|
463
|
+
const difference = originalTotalVertexCount - totalVertexCountAfter;
|
|
464
|
+
const diff01 = difference / originalTotalVertexCount;
|
|
462
465
|
if (totalVertexCountAfter <= 0) {
|
|
463
|
-
logger.error(`ERR: [${NAME}] Mesh[${index}] has no vertices after simplification: ${mesh.getName()}`);
|
|
466
|
+
logger.error(`ERR: [${NAME}]: Mesh[${index}] has no vertices after simplification: ${mesh.getName()}`);
|
|
467
|
+
}
|
|
468
|
+
else if (difference < 32 || diff01 <= 0.05) {
|
|
469
|
+
// the lowest LOD has the same vertex count as the original mesh → we don't need to write the extension and can also delete previously generated LODs
|
|
470
|
+
console.log(`[${NAME}]: Flag mesh LOD ${generatedLODInfos.length}: vertex count difference below threshold (removed ${difference} vertices, ${(-diff01 * 100).toFixed(2)} %) → will remove LODs. \"${mesh.getName()}\"`);
|
|
471
|
+
try {
|
|
472
|
+
for (const lodInfo of generatedLODInfos) {
|
|
473
|
+
rmSync(outputDirectory + "/" + lodInfo.path);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch (err) {
|
|
477
|
+
logger.error(`ERR: [${NAME}] Failed to remove LOD files for \"${mesh.getName()}\"`);
|
|
478
|
+
}
|
|
479
|
+
continue;
|
|
464
480
|
}
|
|
465
481
|
if (generatedLODInfos.length > 0) {
|
|
466
|
-
const model = progressiveExt.
|
|
482
|
+
const model = progressiveExt.createMesh({
|
|
467
483
|
guid: settings.guid,
|
|
468
484
|
lods: generatedLODInfos.map(l => {
|
|
469
485
|
return {
|
|
470
486
|
path: l.path,
|
|
471
487
|
density: l.density,
|
|
488
|
+
vertexCount: l.lodMeshInfo?.totalVertexCount,
|
|
489
|
+
indexCount: l.lodMeshInfo?.totalIndexCount,
|
|
472
490
|
};
|
|
473
491
|
}),
|
|
474
492
|
density: maxDensity,
|
|
@@ -483,7 +501,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
483
501
|
}
|
|
484
502
|
}
|
|
485
503
|
}
|
|
486
|
-
console.log(
|
|
504
|
+
console.log(`[${NAME}]: Finished generating progressive meshes for \"${filename}\"`);
|
|
487
505
|
});
|
|
488
506
|
}
|
|
489
507
|
/**
|
|
@@ -510,10 +528,11 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
510
528
|
hash += getHash(lodLevel);
|
|
511
529
|
cacheKey = `mesh_lod_${level}_${hash}`;
|
|
512
530
|
const cachedValue = tryGetFromCache(cacheKey);
|
|
513
|
-
|
|
531
|
+
const meta = tryGetMetaFromCache(cacheKey);
|
|
532
|
+
if (cachedValue && meta) {
|
|
514
533
|
console.log(`[${NAME}] LOD ${level} loaded ${(cachedValue.length / 1024).toFixed()} KB from cache for \"${mesh.getName()}\"`);
|
|
515
534
|
writeFileSync(outPath, cachedValue);
|
|
516
|
-
return { level, path: fileName, success: true, cacheKey: cacheKey };
|
|
535
|
+
return { level, path: fileName, success: true, cacheKey: cacheKey, lodMeshInfo: meta.meshInfo, density: meta.density };
|
|
517
536
|
}
|
|
518
537
|
}
|
|
519
538
|
const newDoc = new Document();
|
|
@@ -527,6 +546,10 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
527
546
|
newMesh.setName(mesh.getName() + " LOD " + level);
|
|
528
547
|
newNode.setMesh(newMesh);
|
|
529
548
|
const newMat = newDoc.createMaterial();
|
|
549
|
+
const weights = mesh.getWeights();
|
|
550
|
+
if (weights) {
|
|
551
|
+
newMesh.setWeights(weights);
|
|
552
|
+
}
|
|
530
553
|
const tasks = new Array();
|
|
531
554
|
// let didRunSimplification = false;
|
|
532
555
|
for (const prim of primitives) {
|
|
@@ -544,6 +567,9 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
544
567
|
newAttr.setType(attr.getType());
|
|
545
568
|
newPrim.setAttribute(semantics[i], newAttr);
|
|
546
569
|
}
|
|
570
|
+
else {
|
|
571
|
+
console.warn(`[${NAME}] Missing attribute ${semantics[i]} in mesh LOD ${level}`);
|
|
572
|
+
}
|
|
547
573
|
}
|
|
548
574
|
const indices = prim.getIndices();
|
|
549
575
|
if (indices) {
|
|
@@ -552,6 +578,21 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
552
578
|
newIndices.setType(indices.getType());
|
|
553
579
|
newPrim.setIndices(newIndices);
|
|
554
580
|
}
|
|
581
|
+
const targets = prim.listTargets();
|
|
582
|
+
for (const target of targets) {
|
|
583
|
+
const newTarget = newDoc.createPrimitiveTarget(target.getName());
|
|
584
|
+
newPrim.addTarget(newTarget);
|
|
585
|
+
for (const attr of target.listSemantics()) {
|
|
586
|
+
const accessor = target.getAttribute(attr);
|
|
587
|
+
if (accessor) {
|
|
588
|
+
const newAccessor = newDoc.createAccessor();
|
|
589
|
+
newAccessor.setArray(accessor.getArray());
|
|
590
|
+
newAccessor.setType(accessor.getType());
|
|
591
|
+
newTarget.setAttribute(attr, newAccessor);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
;
|
|
555
596
|
const extensions = prim.listExtensions();
|
|
556
597
|
for (const ext of extensions) {
|
|
557
598
|
const PropertyClass = ext.constructor;
|
|
@@ -576,7 +617,7 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
576
617
|
const newMeshInformation = getMeshInformation(newMesh);
|
|
577
618
|
if (newMeshInformation.totalVertexCount < 4) {
|
|
578
619
|
console.error(`[${NAME}] mesh LOD ${level} is has less than 4 vertices → skipping`);
|
|
579
|
-
return { level, path: fileName, success: false, cacheKey: cacheKey };
|
|
620
|
+
return { level, path: fileName, success: false, cacheKey: cacheKey, lodMeshInfo: null, density: null, };
|
|
580
621
|
}
|
|
581
622
|
let density = 0;
|
|
582
623
|
for (const prim of newMesh.listPrimitives()) {
|
|
@@ -584,11 +625,11 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
584
625
|
density = Math.max(density, d);
|
|
585
626
|
}
|
|
586
627
|
const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
|
|
587
|
-
if (!progressiveExt || !progressiveExt.
|
|
628
|
+
if (!progressiveExt || !progressiveExt.createMesh) {
|
|
588
629
|
console.error("Failed to create progressive extension");
|
|
589
630
|
return;
|
|
590
631
|
}
|
|
591
|
-
newMesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.
|
|
632
|
+
newMesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.createMesh({
|
|
592
633
|
guid: guid,
|
|
593
634
|
density: density,
|
|
594
635
|
}));
|
|
@@ -603,13 +644,14 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
603
644
|
}
|
|
604
645
|
const newIo = new NodeIO();
|
|
605
646
|
registerExtensions(newIo);
|
|
606
|
-
console.log(`[${NAME}] Write mesh LOD ${level} with
|
|
647
|
+
console.log(`[${NAME}] Write mesh LOD ${level} with to \"${fileName}\"`);
|
|
607
648
|
await newIo.write(outPath, newDoc);
|
|
608
649
|
// const hasChanged = meshInfo.maxVertexCount == newMeshInformation.maxVertexCount && meshInfo.totalVertexCount == newMeshInformation.totalVertexCount;
|
|
609
650
|
// add to cache
|
|
610
651
|
if (cacheKey != undefined) {
|
|
611
652
|
const bytes = readFileSync(outPath);
|
|
612
653
|
addToCache(cacheKey, bytes);
|
|
654
|
+
addMetaToCache(cacheKey, { meshInfo: newMeshInformation, density: density });
|
|
613
655
|
}
|
|
614
656
|
return { level, path: fileName, success: true, lodMeshInfo: newMeshInformation, cacheKey: cacheKey, density: density };
|
|
615
657
|
}
|
package/dist/utils/mesh.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Mesh, Primitive } from "@gltf-transform/core";
|
|
|
2
2
|
export declare type MeshInformation = {
|
|
3
3
|
readonly maxVertexCount: number;
|
|
4
4
|
readonly totalVertexCount: number;
|
|
5
|
+
readonly totalIndexCount: number;
|
|
5
6
|
};
|
|
6
7
|
export declare function getMeshInformation(mesh: Mesh): MeshInformation;
|
|
7
8
|
export declare function calculateMeshDensity(prim: Primitive): number;
|
package/dist/utils/mesh.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export function getMeshInformation(mesh) {
|
|
2
2
|
let maxVertexCount = 0;
|
|
3
|
+
let totalIndexCount = 0;
|
|
3
4
|
const totalVertexCount = mesh.listPrimitives().reduce((acc, prim) => {
|
|
5
|
+
const indices = prim.getIndices()?.getCount() || 0;
|
|
6
|
+
totalIndexCount += indices;
|
|
4
7
|
const vertices = prim.getAttribute('POSITION')?.getCount() || 0;
|
|
5
8
|
maxVertexCount = Math.max(maxVertexCount, vertices);
|
|
6
9
|
return acc + vertices;
|
|
@@ -8,6 +11,7 @@ export function getMeshInformation(mesh) {
|
|
|
8
11
|
return {
|
|
9
12
|
maxVertexCount,
|
|
10
13
|
totalVertexCount,
|
|
14
|
+
totalIndexCount,
|
|
11
15
|
};
|
|
12
16
|
}
|
|
13
17
|
export function calculateMeshDensity(prim) {
|
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.12",
|
|
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",
|