@needle-tools/gltf-build-pipeline 1.4.1-beta.9 → 1.4.2
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 +23 -0
- package/dist/cache/cache.d.ts +38 -1
- package/dist/cache/cache.js +70 -0
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.d.ts +1 -0
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.js +2 -0
- package/dist/extensions/NEEDLE_opaque/index.js +2 -0
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.d.ts +24 -10
- 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 +7 -5
- package/dist/transforms/needle_progressive.js +154 -53
- 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,29 @@ 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.2] - 2023-04-02
|
|
8
|
+
- release
|
|
9
|
+
|
|
10
|
+
## [1.4.1-beta.15] - 2023-04-02
|
|
11
|
+
- fix: issue where discarding all LODs might result in broken mesh due to simplify having modified the original mesh
|
|
12
|
+
- fix: don't produce LODs if GLB/glTF was produced with old generator
|
|
13
|
+
|
|
14
|
+
## [1.4.1-beta.13] - 2023-03-26
|
|
15
|
+
- change: passthrough `NEEDLE_editor` & `NEEDLE_gltf_dependencies` extensions
|
|
16
|
+
|
|
17
|
+
## [1.4.1-beta.12] - 2023-03-23
|
|
18
|
+
- add: NEEDLE_progressive LODs can now contain more info per texture or mesh. Meshes contain vertex- and index counts
|
|
19
|
+
- change: the cache can now store and retrieve meta information
|
|
20
|
+
|
|
21
|
+
## [1.4.1-beta.11] - 2023-03-20
|
|
22
|
+
- add: NEEDLE_progressive LOD meshes morph targets data
|
|
23
|
+
- 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)
|
|
24
|
+
- change: Unity NEEDLE_progressive logs
|
|
25
|
+
|
|
26
|
+
## [1.4.1-beta.10] - 2023-03-19
|
|
27
|
+
- change: NEEDLE_progressive calculates density per mesh level
|
|
28
|
+
- change: NEEDLE_progressive `uri` field is removed and added `lod` array of objects
|
|
29
|
+
|
|
7
30
|
## [1.4.1-beta.8] - 2023-03-18
|
|
8
31
|
- change: if an asset doesnt have a guid we use a uuid package now
|
|
9
32
|
- change: ignore mesh LODs with vertex count < 4
|
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) {
|
|
@@ -20,5 +20,6 @@ export interface IExtensionWriter {
|
|
|
20
20
|
assignTo?(obj: object): any;
|
|
21
21
|
write(context: WriterContext, prop: IExtensibleProperty): void;
|
|
22
22
|
}
|
|
23
|
+
export declare let currentGenerator: string;
|
|
23
24
|
export declare function createOpaqueExtension(name: string, types: PropertyType | PropertyType[], opts?: Options): IOpaqueExtension;
|
|
24
25
|
export {};
|
|
@@ -4,6 +4,7 @@ const ALL_PROPERTY_TYPES = [];
|
|
|
4
4
|
for (const key in PropertyType) {
|
|
5
5
|
ALL_PROPERTY_TYPES.push(PropertyType[key]);
|
|
6
6
|
}
|
|
7
|
+
export let currentGenerator = "";
|
|
7
8
|
// an opaque extension can be used to pass data through the gltf transform process
|
|
8
9
|
// it should also be able to re-write/fix json pointers after the transformation which might have shuffled them around
|
|
9
10
|
export function createOpaqueExtension(name, types, opts) {
|
|
@@ -18,6 +19,7 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
18
19
|
// private roots: Array<OpaqueProperty> = [];
|
|
19
20
|
jsonPointerHandler;
|
|
20
21
|
read(context) {
|
|
22
|
+
currentGenerator = context.jsonDoc.json.asset?.generator;
|
|
21
23
|
if (debugLog)
|
|
22
24
|
console.log("READ " + EXTENSION_NAME);
|
|
23
25
|
this.jsonPointerHandler = new JsonPointerHandler(this.document, EXTENSION_NAME);
|
|
@@ -15,4 +15,6 @@ export const ALL_EXTENSIONS = [
|
|
|
15
15
|
createOpaqueExtension("NEEDLE_render_objects", PropertyType.ROOT, { ...opaqueOptions }),
|
|
16
16
|
createOpaqueExtension("NEEDLE_progressive", [PropertyType.TEXTURE, PropertyType.MATERIAL, PropertyType.MESH], { ...opaqueOptions }),
|
|
17
17
|
createOpaqueExtension("EXT_texture_exr", [PropertyType.TEXTURE], { ...opaqueOptions }),
|
|
18
|
+
createOpaqueExtension("NEEDLE_editor", [PropertyType.NODE, PropertyType.MATERIAL], { ...opaqueOptions }),
|
|
19
|
+
createOpaqueExtension("NEEDLE_gltf_dependencies", [PropertyType.ROOT], { ...opaqueOptions }),
|
|
18
20
|
];
|
|
@@ -1,25 +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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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;
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
10
23
|
export declare class NEEDLE_progressive extends Extension {
|
|
11
24
|
static EXTENSION_NAME: string;
|
|
12
25
|
extensionName: string;
|
|
13
|
-
|
|
14
|
-
|
|
26
|
+
createTexture(data: ProgressiveTextureModel): NEEDLE_progressive_property;
|
|
27
|
+
createMesh(data: ProgressiveMeshModel): NEEDLE_progressive_property;
|
|
15
28
|
read(_context: ReaderContext): this;
|
|
16
29
|
write(context: WriterContext): this;
|
|
17
30
|
}
|
|
18
|
-
export declare class
|
|
31
|
+
export declare class NEEDLE_progressive_property extends ExtensionProperty {
|
|
19
32
|
static EXTENSION_NAME: string;
|
|
20
33
|
readonly extensionName: string;
|
|
21
34
|
parentTypes: PropertyType[];
|
|
22
35
|
propertyType: string;
|
|
23
36
|
init(): void;
|
|
24
|
-
model:
|
|
37
|
+
model: ProgressiveLoadingInfo;
|
|
25
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, 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>;
|
|
@@ -50,7 +50,9 @@ async function simplifierTransform(document, _context, _options) {
|
|
|
50
50
|
overwrite: true,
|
|
51
51
|
};
|
|
52
52
|
for (const mesh of document.getRoot().listMeshes()) {
|
|
53
|
+
let i = 0;
|
|
53
54
|
for (const prim of mesh.listPrimitives()) {
|
|
55
|
+
const index = i++;
|
|
54
56
|
// TODO: mesh compression caching
|
|
55
57
|
// const key = getKey(prim);
|
|
56
58
|
// const cached = tryGetFromCache(key);
|
|
@@ -84,14 +86,14 @@ async function simplifierTransform(document, _context, _options) {
|
|
|
84
86
|
logger.warn(`[${NAME}]: Skipping primitive of mesh "${mesh.getName()}": Requires TRIANGLES draw mode.`);
|
|
85
87
|
continue;
|
|
86
88
|
}
|
|
87
|
-
compressPrimitive(document, mesh, prim, weldOptions, simplifyOpts);
|
|
89
|
+
compressPrimitive(document, mesh, prim, index, weldOptions, simplifyOpts);
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
return true;
|
|
91
93
|
}
|
|
92
|
-
export async function compressPrimitive(document, mesh, prim, weldOptions, meshOptions) {
|
|
94
|
+
export async function compressPrimitive(document, mesh, prim, index, weldOptions, meshOptions) {
|
|
93
95
|
const logger = document.getLogger();
|
|
94
|
-
logger.debug(`[${
|
|
96
|
+
logger.debug(`weld: primitive[${index}] of mesh "${mesh.getName()}" → tolerance: ${weldOptions.tolerance}`);
|
|
95
97
|
weldPrimitive(document, prim, weldOptions);
|
|
96
98
|
// make sure we wait for the mesh simplifier to be ready
|
|
97
99
|
if (meshOptions.simplifier && meshOptions.simplifier?.ready instanceof Promise) {
|
|
@@ -107,8 +109,8 @@ export async function compressPrimitive(document, mesh, prim, weldOptions, meshO
|
|
|
107
109
|
if (!res)
|
|
108
110
|
return;
|
|
109
111
|
}
|
|
110
|
-
logger.debug(`[${
|
|
111
|
-
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);
|
|
112
114
|
}
|
|
113
115
|
// function updatePrimitive(prim: Primitive, simplifier: any, positionArray, dstIndicesArray) {
|
|
114
116
|
// const position = prim.getAttribute('POSITION')!;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { BufferUtils, Document, NodeIO, PropertyType } from '@gltf-transform/core';
|
|
2
2
|
import { SIMPLIFY_DEFAULTS, WELD_DEFAULTS, listTextureSlots, sparse } from '@gltf-transform/functions';
|
|
3
3
|
import { NEEDLE_PROGRESSIVE_EXTENSION_NAME, NEEDLE_progressive } from "../extensions/NEEDLE_progressive/index.js";
|
|
4
|
-
import { createOpaqueExtension, NEEDLE_compression_texture, NEEDLE_mesh_compression, NEEDLE_progressive_mesh_settings, NEEDLE_progressive_texture_settings, registerExtensions as registerDefaultExtensions } from '../extensions/index.js';
|
|
4
|
+
import { createOpaqueExtension, currentGenerator, 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,10 +150,17 @@ 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
|
-
|
|
153
|
+
const model = {
|
|
154
154
|
guid: textureGuid,
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
lods: [
|
|
156
|
+
{
|
|
157
|
+
path: relName,
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
// for backwards compatibility - this should be removed in the future
|
|
162
|
+
model["uri"] = relName;
|
|
163
|
+
texture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.createTexture(model));
|
|
157
164
|
const key = getKey(texture);
|
|
158
165
|
if (opts.useCache) {
|
|
159
166
|
const cached = tryGetFromCache(key);
|
|
@@ -227,7 +234,7 @@ function createTextureLod(basePath, level, texture, slots, guid, textureIndex) {
|
|
|
227
234
|
}
|
|
228
235
|
// add progressive texture extension
|
|
229
236
|
const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
|
|
230
|
-
newTexture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.
|
|
237
|
+
newTexture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.createTexture({
|
|
231
238
|
guid: guid,
|
|
232
239
|
}));
|
|
233
240
|
const newIo = new NodeIO();
|
|
@@ -295,6 +302,12 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
295
302
|
// const fileNameWithoutExtension = path.basename(filePath, path.extname(filePath));
|
|
296
303
|
const outputDirectory = directory + "/";
|
|
297
304
|
return createTransform("PROGRESSIVE_MESHES", async (doc, _context) => {
|
|
305
|
+
// this is to ensure we're not creating progressive meshes for engine versions that don't support loading them yet
|
|
306
|
+
// the generator should include the version or the app that produced the GLB too (needle engine repo: 2c00d0e304e205cf9d4ac0499faabb92a706a5c4)
|
|
307
|
+
if (currentGenerator == "Needle Engine") {
|
|
308
|
+
console.log("WARN: Skip creating mesh LODs - Unsupported generator found... Please update your editor integration");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
298
311
|
if (isMeshLOD(filePath)) {
|
|
299
312
|
console.log("Progressive Mesh Transform: skip lod at " + filePath);
|
|
300
313
|
return Promise.resolve();
|
|
@@ -302,7 +315,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
302
315
|
await MeshoptSimplifier.ready;
|
|
303
316
|
const logger = doc.getLogger();
|
|
304
317
|
const filename = path.basename(filePath);
|
|
305
|
-
console.log(
|
|
318
|
+
console.log(`[${NAME}]: Begin generating progressive meshes for \"${filename}\"`);
|
|
306
319
|
let index = 0;
|
|
307
320
|
// the lowest LOD (last entry) will be saved in the original file
|
|
308
321
|
const MAX_LODS = 6;
|
|
@@ -327,32 +340,38 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
327
340
|
}
|
|
328
341
|
/** Map of visited GUID -> extension */
|
|
329
342
|
const visited = new Map();
|
|
330
|
-
const
|
|
343
|
+
const root = doc.getRoot();
|
|
344
|
+
const meshes = root.listMeshes();
|
|
331
345
|
for (const mesh of meshes) {
|
|
332
|
-
|
|
333
|
-
|
|
346
|
+
await processMesh(mesh);
|
|
347
|
+
}
|
|
348
|
+
console.log(`[${NAME}]: Finished generating progressive meshes for \"${filename}\"`);
|
|
349
|
+
async function processMesh(mesh) {
|
|
350
|
+
/** The size and vertex count of the original mesh */
|
|
351
|
+
const originalMeshInformation = getMeshInformation(mesh);
|
|
352
|
+
const { totalVertexCount: originalTotalVertexCount, maxVertexCount: originalMaxVertexCount } = originalMeshInformation;
|
|
334
353
|
let settings = mesh.getExtension(NEEDLE_progressive_mesh_settings.EXTENSION_NAME);
|
|
335
354
|
if (!settings) {
|
|
336
|
-
if (
|
|
337
|
-
logger.debug(`[${NAME}] Skipping, no settings found and no primitive with more than 32 vertices. mesh[${index}] has ${
|
|
338
|
-
|
|
355
|
+
if (originalMaxVertexCount < 32) {
|
|
356
|
+
logger.debug(`[${NAME}] Skipping, no settings found and no primitive with more than 32 vertices. mesh[${index}] has ${originalTotalVertexCount} vertices (max: ${originalMaxVertexCount})`);
|
|
357
|
+
return;
|
|
339
358
|
}
|
|
340
359
|
else {
|
|
341
360
|
settings = {
|
|
342
361
|
guid: generateGuid(path + "_mesh_" + index.toString()),
|
|
343
362
|
generateLods: true,
|
|
344
363
|
};
|
|
345
|
-
logger.debug(`[${NAME}] No settings found for mesh[${index}] with ${
|
|
364
|
+
logger.debug(`[${NAME}] No settings found for mesh[${index}] with ${originalTotalVertexCount} vertices (max: ${originalMaxVertexCount}) - will use default settings (guid: ${settings.guid})`);
|
|
346
365
|
}
|
|
347
366
|
}
|
|
348
367
|
else if (settings.generateLods === false) {
|
|
349
368
|
logger.debug(`[${NAME}] progressive mesh disabled for mesh at index ${index++}`);
|
|
350
|
-
|
|
369
|
+
return;
|
|
351
370
|
}
|
|
352
371
|
const progressiveExt = doc.createExtension(NEEDLE_progressive);
|
|
353
|
-
if (!progressiveExt || !progressiveExt.
|
|
372
|
+
if (!progressiveExt || !progressiveExt.createMesh) {
|
|
354
373
|
logger.debug(`[${NAME}] Skipping because progressive extension is not available. ${index++}`);
|
|
355
|
-
|
|
374
|
+
return;
|
|
356
375
|
}
|
|
357
376
|
// check if we have already produced LODs for this mesh
|
|
358
377
|
let needsToGenerateLODs = true;
|
|
@@ -367,12 +386,13 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
367
386
|
/** async simplification tasks... */
|
|
368
387
|
const tasks = new Array();
|
|
369
388
|
if (needsToGenerateLODs) {
|
|
389
|
+
console.log(`[${NAME}] Begin generating mesh LODs for \"${mesh.getName()}\"`);
|
|
370
390
|
// produce LODS, skip the last level because it will be saved in the original file
|
|
371
391
|
for (let level = 0; level < LODS.length - 1; level++) {
|
|
372
392
|
const lod = LODS[level];
|
|
373
393
|
const guid = settings.guid;
|
|
374
394
|
// TODO: check if the LOD level did actually change the mesh - otherwise we can just skip generating lower resolutions
|
|
375
|
-
const lodTask = createMeshLod(doc, outputDirectory, level, mesh,
|
|
395
|
+
const lodTask = createMeshLod(doc, outputDirectory, level, mesh, originalMeshInformation, guid, lod, opts);
|
|
376
396
|
lodTask.then(res => {
|
|
377
397
|
if (res?.success) {
|
|
378
398
|
generatedLODInfos[level] = res;
|
|
@@ -402,11 +422,11 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
402
422
|
let shouldOmitLevel = false;
|
|
403
423
|
// if the mesh size on disc is identical to the last level, we can omit this level
|
|
404
424
|
if (sizeOnDisc <= 0) {
|
|
405
|
-
console.log(`[${NAME}] mesh LOD ${level} size on disc is 0
|
|
425
|
+
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}\"`);
|
|
406
426
|
shouldOmitLevel = true;
|
|
407
427
|
}
|
|
408
428
|
else if (diff == 0) {
|
|
409
|
-
console.log(`[${NAME}] mesh LOD ${level} size on disc did not change compared to previous level
|
|
429
|
+
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}\"`);
|
|
410
430
|
shouldOmitLevel = true;
|
|
411
431
|
}
|
|
412
432
|
else if (!shouldOmitLevel) {
|
|
@@ -415,17 +435,17 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
415
435
|
const lastMeshInfo = generatedLODInfos[i - 1]?.lodMeshInfo;
|
|
416
436
|
if (meshInfo && lastMeshInfo) {
|
|
417
437
|
const vertexDifference = lastMeshInfo.totalVertexCount - meshInfo.totalVertexCount;
|
|
418
|
-
const
|
|
438
|
+
const diff01 = vertexDifference / lastMeshInfo.totalVertexCount;
|
|
419
439
|
if (vertexDifference < 0) {
|
|
420
|
-
console.log(`WARN: [${NAME}] mesh LOD ${level} did increase
|
|
440
|
+
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)}`);
|
|
421
441
|
shouldOmitLevel = true;
|
|
422
442
|
}
|
|
423
|
-
else if (vertexDifference < 32 ||
|
|
424
|
-
console.log(`[${NAME}] mesh LOD ${level}
|
|
443
|
+
else if (vertexDifference < 32 || diff01 < 0.05) {
|
|
444
|
+
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}\"`);
|
|
425
445
|
shouldOmitLevel = true;
|
|
426
446
|
}
|
|
427
447
|
}
|
|
428
|
-
if (shouldOmitLevel) {
|
|
448
|
+
if (shouldOmitLevel && opts.useCache != false && lodInfo.cacheKey != undefined) {
|
|
429
449
|
// we invalidate the cached entry because the mesh change was too small
|
|
430
450
|
addToCache(lodInfo.cacheKey, new Uint8Array(0));
|
|
431
451
|
}
|
|
@@ -441,25 +461,59 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
441
461
|
}
|
|
442
462
|
}
|
|
443
463
|
let maxDensity = 0;
|
|
464
|
+
let primitiveIndex = 0;
|
|
465
|
+
const lodLevelInFile = generatedLODInfos.length;
|
|
466
|
+
const clonedData = new Array();
|
|
444
467
|
for (const prim of mesh.listPrimitives()) {
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
console.log(`[${NAME}] ${
|
|
468
|
+
const data = clonePrimitiveData(prim);
|
|
469
|
+
clonedData.push(data);
|
|
470
|
+
console.log(`[${NAME}] Simplify mesh[${index}] primitive[${primitiveIndex}] with ${data.indices.length / 3} triangles`);
|
|
471
|
+
const lastLod = LODS[lodLevelInFile];
|
|
472
|
+
// TODO: this should be run after simplification but then we need to change the runtime to use the LOD 0 density
|
|
448
473
|
const density = calculateMeshDensity(prim);
|
|
449
474
|
maxDensity = Math.max(maxDensity, density);
|
|
450
|
-
const task = simplifyMesh(doc, mesh, prim, lastLod);
|
|
475
|
+
const task = simplifyMesh(doc, mesh, prim, primitiveIndex++, lastLod);
|
|
451
476
|
tasks.push(task);
|
|
452
477
|
}
|
|
453
478
|
await Promise.all(tasks);
|
|
454
479
|
tasks.length = 0;
|
|
455
480
|
const { totalVertexCount: totalVertexCountAfter } = getMeshInformation(mesh);
|
|
481
|
+
const difference = originalTotalVertexCount - totalVertexCountAfter;
|
|
482
|
+
const diff01 = difference / originalTotalVertexCount;
|
|
456
483
|
if (totalVertexCountAfter <= 0) {
|
|
457
|
-
logger.error(`ERR: [${NAME}] Mesh[${index}] has no vertices after simplification: ${mesh.getName()}`);
|
|
484
|
+
logger.error(`ERR: [${NAME}]: Mesh[${index}] has no vertices after simplification: ${mesh.getName()}`);
|
|
485
|
+
}
|
|
486
|
+
else if (difference < 32 || diff01 <= 0.05) {
|
|
487
|
+
// 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
|
|
488
|
+
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()}\"`);
|
|
489
|
+
try {
|
|
490
|
+
for (const lodInfo of generatedLODInfos) {
|
|
491
|
+
rmSync(outputDirectory + "/" + lodInfo.path);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
catch (err) {
|
|
495
|
+
logger.error(`ERR: [${NAME}] Failed to remove LOD files for \"${mesh.getName()}\"`);
|
|
496
|
+
}
|
|
497
|
+
// reset data (simplify might have changed the mesh data that we cloned before)
|
|
498
|
+
const primitives = mesh.listPrimitives();
|
|
499
|
+
if (clonedData.length == primitives.length) {
|
|
500
|
+
for (let i = 0; i < primitives.length; i++) {
|
|
501
|
+
setPrimitiveData(primitives[i], clonedData[i]);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return;
|
|
458
505
|
}
|
|
459
506
|
if (generatedLODInfos.length > 0) {
|
|
460
|
-
const model = progressiveExt.
|
|
507
|
+
const model = progressiveExt.createMesh({
|
|
461
508
|
guid: settings.guid,
|
|
462
|
-
|
|
509
|
+
lods: generatedLODInfos.map(l => {
|
|
510
|
+
return {
|
|
511
|
+
path: l.path,
|
|
512
|
+
density: l.density,
|
|
513
|
+
vertexCount: l.lodMeshInfo?.totalVertexCount,
|
|
514
|
+
indexCount: l.lodMeshInfo?.totalIndexCount,
|
|
515
|
+
};
|
|
516
|
+
}),
|
|
463
517
|
density: maxDensity,
|
|
464
518
|
});
|
|
465
519
|
mesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, model);
|
|
@@ -472,9 +526,31 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
472
526
|
}
|
|
473
527
|
}
|
|
474
528
|
}
|
|
475
|
-
console.log("< Progressive Mesh Transform: finished");
|
|
476
529
|
});
|
|
477
530
|
}
|
|
531
|
+
function clonePrimitiveData(prim) {
|
|
532
|
+
const indices = new Uint32Array(prim.getIndices().getArray());
|
|
533
|
+
const attributes = {};
|
|
534
|
+
for (const attr of prim.listSemantics()) {
|
|
535
|
+
const accessor = prim.getAttribute(attr);
|
|
536
|
+
if (accessor) {
|
|
537
|
+
attributes[attr] = new Float32Array(accessor.getArray());
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return { indices, attributes };
|
|
541
|
+
}
|
|
542
|
+
function setPrimitiveData(prim, data) {
|
|
543
|
+
const indices = prim.getIndices();
|
|
544
|
+
if (indices) {
|
|
545
|
+
indices.setArray(data.indices);
|
|
546
|
+
}
|
|
547
|
+
for (const attr of prim.listSemantics()) {
|
|
548
|
+
const accessor = prim.getAttribute(attr);
|
|
549
|
+
if (accessor) {
|
|
550
|
+
accessor.setArray(data.attributes[attr]);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
478
554
|
/**
|
|
479
555
|
* @param basePath the base path to save the lod files
|
|
480
556
|
* @param level: 0 = original, 1 = first lod, 2 = second lod, ...
|
|
@@ -499,10 +575,11 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
499
575
|
hash += getHash(lodLevel);
|
|
500
576
|
cacheKey = `mesh_lod_${level}_${hash}`;
|
|
501
577
|
const cachedValue = tryGetFromCache(cacheKey);
|
|
502
|
-
|
|
578
|
+
const meta = tryGetMetaFromCache(cacheKey);
|
|
579
|
+
if (cachedValue && meta) {
|
|
503
580
|
console.log(`[${NAME}] LOD ${level} loaded ${(cachedValue.length / 1024).toFixed()} KB from cache for \"${mesh.getName()}\"`);
|
|
504
581
|
writeFileSync(outPath, cachedValue);
|
|
505
|
-
return { level, path: fileName, success: true, cacheKey: cacheKey };
|
|
582
|
+
return { level, path: fileName, success: true, cacheKey: cacheKey, lodMeshInfo: meta.meshInfo, density: meta.density };
|
|
506
583
|
}
|
|
507
584
|
}
|
|
508
585
|
const newDoc = new Document();
|
|
@@ -516,6 +593,10 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
516
593
|
newMesh.setName(mesh.getName() + " LOD " + level);
|
|
517
594
|
newNode.setMesh(newMesh);
|
|
518
595
|
const newMat = newDoc.createMaterial();
|
|
596
|
+
const weights = mesh.getWeights();
|
|
597
|
+
if (weights) {
|
|
598
|
+
newMesh.setWeights(weights);
|
|
599
|
+
}
|
|
519
600
|
const tasks = new Array();
|
|
520
601
|
// let didRunSimplification = false;
|
|
521
602
|
for (const prim of primitives) {
|
|
@@ -533,6 +614,9 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
533
614
|
newAttr.setType(attr.getType());
|
|
534
615
|
newPrim.setAttribute(semantics[i], newAttr);
|
|
535
616
|
}
|
|
617
|
+
else {
|
|
618
|
+
console.warn(`[${NAME}] Missing attribute ${semantics[i]} in mesh LOD ${level}`);
|
|
619
|
+
}
|
|
536
620
|
}
|
|
537
621
|
const indices = prim.getIndices();
|
|
538
622
|
if (indices) {
|
|
@@ -541,6 +625,21 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
541
625
|
newIndices.setType(indices.getType());
|
|
542
626
|
newPrim.setIndices(newIndices);
|
|
543
627
|
}
|
|
628
|
+
const targets = prim.listTargets();
|
|
629
|
+
for (const target of targets) {
|
|
630
|
+
const newTarget = newDoc.createPrimitiveTarget(target.getName());
|
|
631
|
+
newPrim.addTarget(newTarget);
|
|
632
|
+
for (const attr of target.listSemantics()) {
|
|
633
|
+
const accessor = target.getAttribute(attr);
|
|
634
|
+
if (accessor) {
|
|
635
|
+
const newAccessor = newDoc.createAccessor();
|
|
636
|
+
newAccessor.setArray(accessor.getArray());
|
|
637
|
+
newAccessor.setType(accessor.getType());
|
|
638
|
+
newTarget.setAttribute(attr, newAccessor);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
;
|
|
544
643
|
const extensions = prim.listExtensions();
|
|
545
644
|
for (const ext of extensions) {
|
|
546
645
|
const PropertyClass = ext.constructor;
|
|
@@ -550,11 +649,12 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
550
649
|
newPrim.setExtension(name, copy);
|
|
551
650
|
}
|
|
552
651
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
652
|
+
let primitiveIndex = 0;
|
|
653
|
+
if (lodLevel.error != undefined || lodLevel.ratio != undefined) {
|
|
654
|
+
if (lodLevel.error > 0 || lodLevel.ratio < 1) {
|
|
655
|
+
for (const newPrim of newMesh.listPrimitives()) {
|
|
556
656
|
// didRunSimplification = true;
|
|
557
|
-
const task = simplifyMesh(newDoc, newMesh, newPrim, lodLevel);
|
|
657
|
+
const task = simplifyMesh(newDoc, newMesh, newPrim, primitiveIndex++, lodLevel);
|
|
558
658
|
tasks.push(task);
|
|
559
659
|
}
|
|
560
660
|
}
|
|
@@ -564,15 +664,21 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
564
664
|
const newMeshInformation = getMeshInformation(newMesh);
|
|
565
665
|
if (newMeshInformation.totalVertexCount < 4) {
|
|
566
666
|
console.error(`[${NAME}] mesh LOD ${level} is has less than 4 vertices → skipping`);
|
|
567
|
-
return { level, path: fileName, success: false, cacheKey: cacheKey };
|
|
667
|
+
return { level, path: fileName, success: false, cacheKey: cacheKey, lodMeshInfo: null, density: null, };
|
|
668
|
+
}
|
|
669
|
+
let density = 0;
|
|
670
|
+
for (const prim of newMesh.listPrimitives()) {
|
|
671
|
+
const d = calculateMeshDensity(prim);
|
|
672
|
+
density = Math.max(density, d);
|
|
568
673
|
}
|
|
569
674
|
const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
|
|
570
|
-
if (!progressiveExt || !progressiveExt.
|
|
675
|
+
if (!progressiveExt || !progressiveExt.createMesh) {
|
|
571
676
|
console.error("Failed to create progressive extension");
|
|
572
677
|
return;
|
|
573
678
|
}
|
|
574
|
-
newMesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.
|
|
679
|
+
newMesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.createMesh({
|
|
575
680
|
guid: guid,
|
|
681
|
+
density: density,
|
|
576
682
|
}));
|
|
577
683
|
// we need to compress the mesh with the same compression method as the original file
|
|
578
684
|
const root = doc.getRoot();
|
|
@@ -585,17 +691,18 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
|
|
|
585
691
|
}
|
|
586
692
|
const newIo = new NodeIO();
|
|
587
693
|
registerExtensions(newIo);
|
|
588
|
-
console.log(`[${NAME}] Write mesh LOD ${level} with
|
|
694
|
+
console.log(`[${NAME}] Write mesh LOD ${level} with to \"${fileName}\"`);
|
|
589
695
|
await newIo.write(outPath, newDoc);
|
|
590
696
|
// const hasChanged = meshInfo.maxVertexCount == newMeshInformation.maxVertexCount && meshInfo.totalVertexCount == newMeshInformation.totalVertexCount;
|
|
591
697
|
// add to cache
|
|
592
698
|
if (cacheKey != undefined) {
|
|
593
699
|
const bytes = readFileSync(outPath);
|
|
594
700
|
addToCache(cacheKey, bytes);
|
|
701
|
+
addMetaToCache(cacheKey, { meshInfo: newMeshInformation, density: density });
|
|
595
702
|
}
|
|
596
|
-
return { level, path: fileName, success: true, lodMeshInfo: newMeshInformation, cacheKey: cacheKey };
|
|
703
|
+
return { level, path: fileName, success: true, lodMeshInfo: newMeshInformation, cacheKey: cacheKey, density: density };
|
|
597
704
|
}
|
|
598
|
-
async function simplifyMesh(doc, mesh, prim, lodinfo) {
|
|
705
|
+
async function simplifyMesh(doc, mesh, prim, index, lodinfo) {
|
|
599
706
|
const weldOptions = {
|
|
600
707
|
...WELD_DEFAULTS,
|
|
601
708
|
overwrite: true,
|
|
@@ -606,13 +713,7 @@ async function simplifyMesh(doc, mesh, prim, lodinfo) {
|
|
|
606
713
|
...lodinfo,
|
|
607
714
|
simplifier: MeshoptSimplifier,
|
|
608
715
|
};
|
|
609
|
-
|
|
610
|
-
// simplifyOptions.error = lodinfo.error;
|
|
611
|
-
// if (lodinfo.ratio != undefined)
|
|
612
|
-
// simplifyOptions.ratio = lodinfo.ratio;
|
|
613
|
-
// if (lodinfo.lockBorder != undefined)
|
|
614
|
-
// simplifyOptions.lockBorder = lodinfo.lockBorder;
|
|
615
|
-
return compressPrimitive(doc, mesh, prim, weldOptions, simplifyOptions);
|
|
716
|
+
return compressPrimitive(doc, mesh, prim, index, weldOptions, simplifyOptions);
|
|
616
717
|
}
|
|
617
718
|
export function isMeshLOD(filepath) {
|
|
618
719
|
return filepath.includes("mesh_lod_");
|
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.
|
|
3
|
+
"version": "1.4.2",
|
|
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",
|