@needle-tools/gltf-build-pipeline 1.3.1 → 1.4.1-beta

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 CHANGED
@@ -4,6 +4,14 @@ 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] - 2023-03-13
8
+ - change: update gltf-transform to `^3.10.0`
9
+ - change: progressive mesh transform does now set ratio for LODs (see [simplifier issue 661 on github](https://github.com/zeux/meshoptimizer/issues/661))
10
+ - fix: issue where progressive meshes were not generated if a mesh was multiple times in the glTF
11
+
12
+ ## [1.4.0-beta] - 2023-03-12
13
+ - add: progressive mesh transform to automatically generate mesh LOD levels (currently we only generate one low poly version)
14
+
7
15
  ## [1.3.1] - 2023-10-20
8
16
  - fix: meshopt with khr_animation_2 extension was not correctly processed: animated node and target were not passed to gltf-transform which caused a wrong processing of meshopt. We now pass this information to the gltf-transform AnimationChannel object
9
17
  - change: bump gltf-transform to 3.7.3
@@ -1,4 +1,4 @@
1
- import { Extension, ExtensionProperty, PropertyType, ReaderContext } from "@gltf-transform/core";
1
+ import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
2
2
  export declare class NEEDLE_mesh_compression_model_root {
3
3
  compression: "none" | "draco" | "meshopt";
4
4
  }
@@ -13,7 +13,7 @@ export declare class NEEDLE_mesh_compression extends Extension {
13
13
  extensionName: string;
14
14
  create(model: NEEDLE_mesh_compression_model | NEEDLE_mesh_compression_model_root): NEEDLE_mesh_compression_property;
15
15
  read(context: ReaderContext): this;
16
- write(ctx: any): this;
16
+ write(ctx: WriterContext): this;
17
17
  }
18
18
  export declare class NEEDLE_mesh_compression_property extends ExtensionProperty {
19
19
  static EXTENSION_NAME: string;
@@ -22,4 +22,5 @@ export declare class NEEDLE_mesh_compression_property extends ExtensionProperty
22
22
  propertyType: string;
23
23
  init(): void;
24
24
  model: NEEDLE_mesh_compression_model | NEEDLE_mesh_compression_model_root;
25
+ copy(other: this, resolve?: (property: ExtensionProperty) => ExtensionProperty): this;
25
26
  }
@@ -44,6 +44,28 @@ export class NEEDLE_mesh_compression extends Extension {
44
44
  return this;
45
45
  }
46
46
  write(ctx) {
47
+ const root = this.document.getRoot();
48
+ const rootExtension = root.getExtension(EXTENSION_NAME);
49
+ if (rootExtension) {
50
+ ctx.jsonDoc.json.extensions = ctx.jsonDoc.json.extensions || {};
51
+ ctx.jsonDoc.json.extensions[EXTENSION_NAME] = rootExtension.model;
52
+ }
53
+ for (const mesh of root.listMeshes()) {
54
+ const index = ctx.meshIndexMap.get(mesh);
55
+ const meshDef = ctx.jsonDoc.json.meshes[index];
56
+ const primitives = mesh.listPrimitives();
57
+ for (let i = 0; i < primitives.length; i++) {
58
+ const primitive = primitives[i];
59
+ const ext = primitive.getExtension(EXTENSION_NAME);
60
+ if (ext) {
61
+ const prim = meshDef.primitives[i];
62
+ if (prim) {
63
+ prim.extensions = meshDef.extensions || {};
64
+ prim.extensions[EXTENSION_NAME] = ext.model;
65
+ }
66
+ }
67
+ }
68
+ }
47
69
  return this;
48
70
  }
49
71
  }
@@ -54,4 +76,8 @@ export class NEEDLE_mesh_compression_property extends ExtensionProperty {
54
76
  propertyType = EXTENSION_NAME;
55
77
  init() { }
56
78
  model;
79
+ copy(other, resolve = null) {
80
+ this.model = { ...other.model };
81
+ return super.copy(other, resolve);
82
+ }
57
83
  }
@@ -415,7 +415,7 @@ class TextureHandler {
415
415
  }
416
416
  });
417
417
  if (foundTexture === false) {
418
- if (this.key !== "EXT_texture_exr" || this.debug)
418
+ if (this.key !== "EXT_texture_exr" && this.debug)
419
419
  console.warn("WARN: failed to re-assign extension:", this.key, this.index, this.tryGetSourceIndex(this.textureDefinition), textureDefs.length);
420
420
  }
421
421
  // this.document
@@ -13,6 +13,6 @@ export const ALL_EXTENSIONS = [
13
13
  createOpaqueExtension("NEEDLE_lightmaps", PropertyType.ROOT, { ...opaqueOptions }),
14
14
  createOpaqueExtension("NEEDLE_lighting_settings", PropertyType.ROOT, { ...opaqueOptions }),
15
15
  createOpaqueExtension("NEEDLE_render_objects", PropertyType.ROOT, { ...opaqueOptions }),
16
- createOpaqueExtension("NEEDLE_progressive", [PropertyType.TEXTURE, PropertyType.MATERIAL], { ...opaqueOptions }),
16
+ createOpaqueExtension("NEEDLE_progressive", [PropertyType.TEXTURE, PropertyType.MATERIAL, PropertyType.MESH], { ...opaqueOptions }),
17
17
  createOpaqueExtension("EXT_texture_exr", [PropertyType.TEXTURE], { ...opaqueOptions }),
18
18
  ];
@@ -1,14 +1,14 @@
1
- import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
2
- export declare const NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME = "NEEDLE_progressive";
1
+ import { Extension, ExtensionProperty, Graph, IProperty, Property, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
2
+ export declare const NEEDLE_PROGRESSIVE_EXTENSION_NAME = "NEEDLE_progressive";
3
3
  export declare interface IProgressiveTextureInfo {
4
- uri?: string;
5
4
  guid: string;
6
- level?: number;
5
+ uri?: string | Array<string>;
7
6
  }
8
7
  export declare function generateGuid(): string;
9
8
  export declare class NEEDLE_progressive extends Extension {
10
9
  static EXTENSION_NAME: string;
11
10
  extensionName: string;
11
+ static create(graph: Graph<Property<IProperty>>, data: IProgressiveTextureInfo): NEEDLE_progressive_texture_property;
12
12
  create(data: IProgressiveTextureInfo): NEEDLE_progressive_texture_property;
13
13
  read(_context: ReaderContext): this;
14
14
  write(context: WriterContext): this;
@@ -1,52 +1,70 @@
1
1
  import { Extension, ExtensionProperty, PropertyType, uuid } from "@gltf-transform/core";
2
2
  // TODO: rename to NEEDLE_progressive_texture
3
- export const NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME = "NEEDLE_progressive";
3
+ export const NEEDLE_PROGRESSIVE_EXTENSION_NAME = "NEEDLE_progressive";
4
4
  export function generateGuid() {
5
5
  // TODO: replace with same uuid package as in needle engine
6
6
  return uuid();
7
7
  }
8
8
  export class NEEDLE_progressive extends Extension {
9
- static EXTENSION_NAME = NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME;
10
- extensionName = NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME;
11
- create(data) {
12
- const ext = new NEEDLE_progressive_texture_property(this.document.getGraph());
9
+ static EXTENSION_NAME = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
10
+ extensionName = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
11
+ static create(graph, data) {
12
+ const ext = new NEEDLE_progressive_texture_property(graph);
13
13
  ext.model = data;
14
14
  return ext;
15
15
  }
16
+ create(data) {
17
+ return NEEDLE_progressive.create(this.document.getGraph(), data);
18
+ }
16
19
  read(_context) {
17
20
  // console.log("READ " + NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME);
18
21
  return this;
19
22
  }
20
23
  write(context) {
21
24
  // console.log("WRITE " + NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME);
22
- const textureDefinitions = context.jsonDoc.json.textures;
23
- if (!textureDefinitions)
24
- return;
25
25
  const root = this.document.getRoot();
26
- const textures = root.listTextures();
27
- // we use the texture image map to find the texture definition to write the extension to
28
- const textureImageIndexMap = context.imageIndexMap;
29
- for (const tex of textures) {
30
- const prop = tex.getExtension(NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME);
31
- if (prop && prop.model) {
32
- // get the image this texture is using
33
- const imageIndex = textureImageIndexMap.get(tex);
34
- // find the texture definition that uses this image and add the extension
35
- for (const def of textureDefinitions) {
36
- if (def.source === imageIndex) {
37
- def.extensions = def.extensions || {};
38
- def.extensions[NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME] = prop.model;
26
+ const textureDefinitions = context.jsonDoc.json.textures;
27
+ if (textureDefinitions.length) {
28
+ const textures = root.listTextures();
29
+ // we use the texture image map to find the texture definition to write the extension to
30
+ const textureImageIndexMap = context.imageIndexMap;
31
+ for (const tex of textures) {
32
+ const prop = tex.getExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME);
33
+ if (prop && prop.model) {
34
+ // get the image this texture is using
35
+ const imageIndex = textureImageIndexMap.get(tex);
36
+ // find the texture definition that uses this image and add the extension
37
+ for (const def of textureDefinitions) {
38
+ if (def.source === imageIndex) {
39
+ def.extensions = def.extensions || {};
40
+ def.extensions[NEEDLE_PROGRESSIVE_EXTENSION_NAME] = prop.model;
41
+ }
39
42
  }
40
43
  }
41
44
  }
42
45
  }
46
+ const meshDefinitions = context.jsonDoc.json.meshes;
47
+ if (meshDefinitions?.length) {
48
+ const meshIndexMap = context.meshIndexMap;
49
+ const meshes = root.listMeshes();
50
+ for (let i = 0; i < meshes.length; i++) {
51
+ const mesh = meshes[i];
52
+ const meshIndex = meshIndexMap.get(mesh);
53
+ const meshExt = mesh.getExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME);
54
+ if (meshExt && meshExt.model) {
55
+ const meshDef = meshDefinitions[meshIndex];
56
+ meshDef.extensions = meshDef.extensions || {};
57
+ meshDef.extensions[NEEDLE_PROGRESSIVE_EXTENSION_NAME] = meshExt.model;
58
+ }
59
+ }
60
+ }
43
61
  return this;
44
62
  }
45
63
  }
46
64
  export class NEEDLE_progressive_texture_property extends ExtensionProperty {
47
- static EXTENSION_NAME = NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME;
48
- extensionName = NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME;
49
- parentTypes = [PropertyType.TEXTURE];
65
+ static EXTENSION_NAME = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
66
+ extensionName = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
67
+ parentTypes = [PropertyType.TEXTURE, PropertyType.MESH];
50
68
  propertyType = "progressive_texture";
51
69
  init() { }
52
70
  model;
@@ -0,0 +1,20 @@
1
+ import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
2
+ export declare interface IProgressiveMeshSettings {
3
+ guid: string;
4
+ }
5
+ export declare class NEEDLE_progressive_mesh_settings extends Extension {
6
+ static EXTENSION_NAME: string;
7
+ extensionName: string;
8
+ read(context: ReaderContext): this;
9
+ write(context: WriterContext): this;
10
+ }
11
+ export declare class NEEDLE_progressive_mesh_settings_property extends ExtensionProperty implements IProgressiveMeshSettings {
12
+ static EXTENSION_NAME: string;
13
+ readonly extensionName: string;
14
+ parentTypes: PropertyType[];
15
+ propertyType: string;
16
+ init(): void;
17
+ get isValid(): boolean;
18
+ maxSize: number;
19
+ guid: string;
20
+ }
@@ -0,0 +1,44 @@
1
+ import { Extension, ExtensionProperty, PropertyType } from "@gltf-transform/core";
2
+ const EXTENSION_NAME = "NEEDLE_progressive_mesh_settings";
3
+ export class NEEDLE_progressive_mesh_settings extends Extension {
4
+ static EXTENSION_NAME = EXTENSION_NAME;
5
+ extensionName = EXTENSION_NAME;
6
+ read(context) {
7
+ const definitions = context.jsonDoc.json.meshes;
8
+ if (!definitions)
9
+ return this;
10
+ const root = this.document.getRoot();
11
+ const meshes = root.listMeshes();
12
+ for (let i = 0; i < definitions.length; i++) {
13
+ const def = definitions[i];
14
+ if (def.extensions && def.extensions[EXTENSION_NAME]) {
15
+ const ext = def.extensions[EXTENSION_NAME];
16
+ const prop = new NEEDLE_progressive_mesh_settings_property(this.document.getGraph(), "");
17
+ Object.assign(prop, ext);
18
+ let index = i;
19
+ const mesh = meshes[index];
20
+ if (mesh)
21
+ mesh.setExtension(EXTENSION_NAME, prop);
22
+ else {
23
+ console.warn(`WARN: Progressive mesh at index ${i} was not found in the glTF file. This might result in falsely loaded textures for ${ext.guid} / name \"${def.name}\"`);
24
+ }
25
+ }
26
+ }
27
+ return this;
28
+ }
29
+ write(context) {
30
+ return this;
31
+ }
32
+ }
33
+ export class NEEDLE_progressive_mesh_settings_property extends ExtensionProperty {
34
+ static EXTENSION_NAME = EXTENSION_NAME;
35
+ extensionName = EXTENSION_NAME;
36
+ parentTypes = [PropertyType.MESH];
37
+ propertyType = EXTENSION_NAME;
38
+ init() { }
39
+ get isValid() {
40
+ return this.maxSize !== undefined && this.maxSize > 0;
41
+ }
42
+ maxSize;
43
+ guid;
44
+ }
@@ -3,4 +3,5 @@ export * from "./NEEDLE_mesh_compression/index.js";
3
3
  export * from "./NEEDLE_compression_texture/index.js";
4
4
  export * from "./NEEDLE_progressive/index.js";
5
5
  export * from "./NEEDLE_progressive_texture_settings/index.js";
6
+ export * from "./NEEDLE_progressive_mesh_settings/index.js";
6
7
  export declare function registerExtensions(node: any): void;
@@ -4,6 +4,7 @@ export * from "./NEEDLE_mesh_compression/index.js";
4
4
  export * from "./NEEDLE_compression_texture/index.js";
5
5
  export * from "./NEEDLE_progressive/index.js";
6
6
  export * from "./NEEDLE_progressive_texture_settings/index.js";
7
+ export * from "./NEEDLE_progressive_mesh_settings/index.js";
7
8
  import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
8
9
  import { ALL_EXTENSIONS as NEEDLE_EXTENSIONS } from "./NEEDLE_opaque/index.js";
9
10
  import { NEEDLE_progressive } from './NEEDLE_progressive/index.js';
@@ -1,3 +1,4 @@
1
- import { Transform } from "@gltf-transform/core";
2
- import { SimplifyOptions } from "@gltf-transform/functions";
1
+ import { Transform, Document, Primitive, Mesh } from "@gltf-transform/core";
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>): void;
@@ -64,6 +64,7 @@ async function simplifierTransform(document, _context, _options) {
64
64
  const simplifyOpts = { ...options };
65
65
  if (!ext) {
66
66
  // If no extension exists on the primitive dont apply any simplification
67
+ // console.log(`[${NAME}]: Skipping primitive simplification of mesh "${mesh.getName()}": No mesh compression extension found.`);
67
68
  continue;
68
69
  }
69
70
  if (mesh_compression) {
@@ -87,7 +88,7 @@ async function simplifierTransform(document, _context, _options) {
87
88
  }
88
89
  return true;
89
90
  }
90
- function compressPrimitive(document, mesh, prim, weldOptions, meshOptions) {
91
+ export function compressPrimitive(document, mesh, prim, weldOptions, meshOptions) {
91
92
  const logger = document.getLogger();
92
93
  logger.debug(`[${NAME}]: Welding primitive of mesh "${mesh.getName()}" → tolerance: ${weldOptions.tolerance}`);
93
94
  weldPrimitive(document, prim, weldOptions);
@@ -31,4 +31,5 @@ export declare enum TextureResizeFilter {
31
31
  }
32
32
  export declare const TEXTURE_RESIZE_DEFAULTS: TextureResizeOptions;
33
33
  export declare function resizeTextureToMax(texture: Texture, maxSize: number, logger: ILogger): Promise<void>;
34
+ export declare function make_progressive_meshes(filePath: string, _options: any, opts?: ProgressiveOptions): Transform;
34
35
  export {};
@@ -1,32 +1,40 @@
1
1
  import { BufferUtils, Document, NodeIO, PropertyType } from '@gltf-transform/core';
2
- import { listTextureSlots } from '@gltf-transform/functions';
3
- import { NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME, NEEDLE_progressive, generateGuid } from "../extensions/NEEDLE_progressive/index.js";
4
- import { createOpaqueExtension, NEEDLE_compression_texture, NEEDLE_mesh_compression, NEEDLE_progressive_texture_settings, registerExtensions as registerDefaultExtensions } from '../extensions/index.js';
2
+ import { SIMPLIFY_DEFAULTS, WELD_DEFAULTS, listTextureSlots } from '@gltf-transform/functions';
3
+ import { NEEDLE_PROGRESSIVE_EXTENSION_NAME, NEEDLE_progressive, generateGuid } 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';
5
5
  import path from 'path';
6
6
  import { existsSync } from 'fs';
7
7
  import { addToCache, getKey, tryGetFromCache } from '../cache/index.js';
8
8
  import sharp from "sharp";
9
9
  import { foreachGLTF } from '../utils/index.js';
10
10
  import { needle_asset } from './needle_asset.js';
11
- import { MeshoptDecoder } from 'meshoptimizer';
11
+ import { MeshoptDecoder, MeshoptSimplifier } from 'meshoptimizer';
12
+ import { compressPrimitive } from './needle_mesh_transform.js';
13
+ export async function make_progressive(opts) {
14
+ console.log("INFO: Run progressive transform: " + opts.path);
15
+ const file = opts.path;
16
+ return foreachGLTF(file, async (f) => {
17
+ // Don't re-process files that are already LODs
18
+ if (isMeshLOD(f) || isImageLOD(f)) {
19
+ console.log(`Progressive Transform: Ignore existing LOD file at ${f}`);
20
+ return;
21
+ }
22
+ await process(f, opts);
23
+ });
24
+ }
12
25
  function registerExtensions(io) {
13
26
  registerDefaultExtensions(io);
14
27
  io.registerExtensions([
15
28
  NEEDLE_progressive,
16
29
  NEEDLE_progressive_texture_settings,
30
+ NEEDLE_progressive_mesh_settings,
31
+ NEEDLE_mesh_compression,
17
32
  //@ts-ignore
18
33
  createOpaqueExtension(NEEDLE_compression_texture.EXTENSION_NAME, PropertyType.TEXTURE),
19
34
  //@ts-ignore
20
- createOpaqueExtension(NEEDLE_mesh_compression.EXTENSION_NAME, [PropertyType.ROOT, PropertyType.MESH, PropertyType.PRIMITIVE])
35
+ // createOpaqueExtension(NEEDLE_mesh_compression.EXTENSION_NAME, [PropertyType.ROOT, PropertyType.MESH, PropertyType.PRIMITIVE]),
21
36
  ]);
22
37
  }
23
- export async function make_progressive(opts) {
24
- console.log("INFO: Run progressive transform: " + opts.path);
25
- const file = opts.path;
26
- return foreachGLTF(file, async (f) => {
27
- await process(f, opts);
28
- });
29
- }
30
38
  async function process(file, opts) {
31
39
  if (!file.endsWith(".glb") && !file.endsWith(".gltf")) {
32
40
  console.log("ERR: file is not gltf/glb");
@@ -59,7 +67,7 @@ async function process(file, opts) {
59
67
  }
60
68
  //@ts-ignore
61
69
  document.getLogger().verbosity = 0;
62
- await document.transform(needle_asset(), make_progressive_textures(file, { size: [256, 256] }, opts));
70
+ await document.transform(needle_asset(), make_progressive_textures(file, { size: [256, 256] }, opts), make_progressive_meshes(file, {}, opts));
63
71
  let filename = file;
64
72
  if (opts.debug === true) {
65
73
  // get extension
@@ -84,7 +92,7 @@ export function createTransform(name, fn) {
84
92
  Object.defineProperty(fn, 'name', { value: name });
85
93
  return fn;
86
94
  }
87
- const NAME = 'textureResize';
95
+ const NAME = 'NEEDLE_progressive';
88
96
  export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DEFAULTS, opts) {
89
97
  console.log("Make progressive: " + filePath);
90
98
  const startTime = Date.now();
@@ -124,7 +132,7 @@ export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DE
124
132
  }
125
133
  options.size[0] = settings.maxSize;
126
134
  options.size[1] = settings.maxSize;
127
- const existingExtension = texture.getExtension(NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME);
135
+ const existingExtension = texture.getExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME);
128
136
  if (existingExtension !== undefined && existingExtension !== null) {
129
137
  logger.debug(`${NAME}: Skipping, texture is already progressive ${textureIndex}`);
130
138
  continue;
@@ -141,10 +149,9 @@ export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DE
141
149
  }
142
150
  const relName = path.relative(directory, outPath);
143
151
  // TODO: this path should be added to the NEEDLE dependencies extension
144
- texture.setExtension(NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME, progressiveExt.create({
145
- uri: relName,
152
+ texture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.create({
146
153
  guid: textureGuid,
147
- level: 0,
154
+ uri: relName,
148
155
  }));
149
156
  const key = getKey(texture);
150
157
  if (opts.useCache) {
@@ -219,14 +226,16 @@ function createTextureLod(basePath, level, texture, slots, guid, textureIndex) {
219
226
  }
220
227
  // add progressive texture extension
221
228
  const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
222
- newTexture.setExtension(NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME, progressiveExt.create({
229
+ newTexture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.create({
223
230
  guid: guid,
224
231
  }));
225
232
  const newIo = new NodeIO();
226
233
  registerExtensions(newIo);
227
234
  const ext = ".glb";
228
235
  const imageName = "image_" + level + "_" + guid;
229
- const outName = basePath + imageName + ext;
236
+ const filename = imageName + ext;
237
+ const outName = basePath + filename;
238
+ console.log(`INFO: Write texture LOD ${level} to \"${filename}\"`);
230
239
  newIo.write(outName, newDoc);
231
240
  return outName;
232
241
  }
@@ -280,3 +289,182 @@ async function resizeTexture(options, texture, logger, uri, slots) {
280
289
  .toBuffer();
281
290
  texture.setImage(BufferUtils.toView(buffer));
282
291
  }
292
+ export function make_progressive_meshes(filePath, _options, opts) {
293
+ const directory = path.dirname(filePath);
294
+ // const fileNameWithoutExtension = path.basename(filePath, path.extname(filePath));
295
+ const outputDirectory = directory + "/";
296
+ return createTransform("PROGRESSIVE_MESHES", async (doc, _context) => {
297
+ if (isMeshLOD(filePath)) {
298
+ console.log("Progressive Mesh Transform: skip lod at " + filePath);
299
+ return Promise.resolve();
300
+ }
301
+ const logger = doc.getLogger();
302
+ const filename = path.basename(filePath);
303
+ console.log("> Progressive Mesh Transform: " + filename);
304
+ let index = 0;
305
+ // the lowest LOD (last entry) will be saved in the original file
306
+ const LODS = [
307
+ { error: 0 },
308
+ { ratio: 0.5, error: 1 },
309
+ { ratio: 0.25, error: 1 },
310
+ ];
311
+ /** Map of visited GUID -> extension */
312
+ const visited = new Map();
313
+ const meshes = doc.getRoot().listMeshes();
314
+ for (const mesh of meshes) {
315
+ // TODO: skip meshes that are already below a certain threshold (E.g. just 4 vertices)
316
+ const settings = mesh.getExtension(NEEDLE_progressive_mesh_settings.EXTENSION_NAME);
317
+ if (!settings) {
318
+ logger.debug(`${NAME}: no ${NEEDLE_progressive_mesh_settings.EXTENSION_NAME} found for mesh at index ${index++}`);
319
+ continue;
320
+ }
321
+ const progressiveExt = doc.createExtension(NEEDLE_progressive);
322
+ if (!progressiveExt || !progressiveExt.create) {
323
+ logger.debug(`${NAME}: Skipping because progressive extension is not available. ${index++}`);
324
+ continue;
325
+ }
326
+ // check if we have already produced LODs for this mesh
327
+ let needsToGenerateLODs = true;
328
+ const generatedLODs = new Array();
329
+ if (visited.has(settings.guid)) {
330
+ // we can not just set the extension here...
331
+ logger.debug(`${NAME}: Mesh has already been processed ${index} - will use previously generated LODs`);
332
+ needsToGenerateLODs = false;
333
+ const existingPaths = visited.get(settings.guid).paths;
334
+ generatedLODs.push(...existingPaths);
335
+ }
336
+ if (needsToGenerateLODs) {
337
+ // produce LODS, skip the last level because it will be saved in the original file
338
+ for (let level = 0; level < LODS.length - 1; level++) {
339
+ const lod = LODS[level];
340
+ console.log(`${NAME}: Generate LOD ${level}`);
341
+ const guid = settings.guid;
342
+ const lodPath = createMeshLod(doc, outputDirectory, level, mesh, guid, lod);
343
+ generatedLODs.push(lodPath);
344
+ }
345
+ }
346
+ for (const prim of mesh.listPrimitives()) {
347
+ const lastLod = LODS[LODS.length - 1];
348
+ console.log(`${NAME}: ${filename} LOD ${LODS.length - 1}`);
349
+ simplifyMesh(doc, mesh, prim, lastLod);
350
+ }
351
+ const model = progressiveExt.create({
352
+ guid: settings.guid,
353
+ uri: generatedLODs.map(l => l),
354
+ });
355
+ mesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, model);
356
+ index++;
357
+ if (!visited.has(settings.guid)) {
358
+ visited.set(settings.guid, {
359
+ model: model,
360
+ paths: generatedLODs,
361
+ });
362
+ }
363
+ }
364
+ console.log("< Progressive Mesh Transform: finished");
365
+ });
366
+ }
367
+ /**
368
+ * @param basePath the base path to save the lod files
369
+ * @param level: 0 = original, 1 = first lod, 2 = second lod, ...
370
+ * @param mesh the mesh
371
+ * @param guid a unique identifier for the mesh
372
+ * @param error the error to use for simplification. Higher means more simplification
373
+ */
374
+ function createMeshLod(doc, basePath, level, mesh, guid, lodLevel) {
375
+ const newDoc = new Document();
376
+ const newScene = newDoc.createScene();
377
+ newDoc.createBuffer();
378
+ const newNode = newDoc.createNode();
379
+ newScene.addChild(newNode);
380
+ newNode.setName("node_" + level + "_" + guid);
381
+ newNode.setTranslation([0, 0, 0]);
382
+ const newMesh = newDoc.createMesh();
383
+ newNode.setMesh(newMesh);
384
+ const primitives = mesh.listPrimitives();
385
+ for (const prim of primitives) {
386
+ const newMat = newDoc.createMaterial();
387
+ const newPrim = newDoc.createPrimitive();
388
+ newMesh.addPrimitive(newPrim);
389
+ newPrim.setName(prim.getName());
390
+ newPrim.setMode(prim.getMode());
391
+ newPrim.setMaterial(newMat);
392
+ const semantics = prim.listSemantics();
393
+ for (let i = 0; i < semantics.length; i++) {
394
+ const attr = prim.getAttribute(semantics[i]);
395
+ if (attr) {
396
+ const newAttr = newDoc.createAccessor();
397
+ newAttr.setArray(attr.getArray());
398
+ newAttr.setType(attr.getType());
399
+ newPrim.setAttribute(semantics[i], newAttr);
400
+ }
401
+ }
402
+ const indices = prim.getIndices();
403
+ if (indices) {
404
+ const newIndices = newDoc.createAccessor();
405
+ newIndices.setArray(indices.getArray());
406
+ newIndices.setType(indices.getType());
407
+ newPrim.setIndices(newIndices);
408
+ }
409
+ const extensions = prim.listExtensions();
410
+ for (const ext of extensions) {
411
+ const PropertyClass = ext.constructor;
412
+ const newProp = new PropertyClass(newDoc.getGraph());
413
+ const copy = newProp.copy(ext);
414
+ const name = ext.extensionName;
415
+ console.log(`${NAME}: Copy primitive ${name} extension`);
416
+ newPrim.setExtension(name, copy);
417
+ }
418
+ }
419
+ if (lodLevel.error != undefined || lodLevel.ratio != undefined) {
420
+ for (const prim of newMesh.listPrimitives()) {
421
+ simplifyMesh(newDoc, newMesh, prim, lodLevel);
422
+ }
423
+ }
424
+ const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
425
+ if (!progressiveExt || !progressiveExt.create) {
426
+ console.error("Failed to create progressive extension");
427
+ return;
428
+ }
429
+ newMesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.create({
430
+ guid: guid,
431
+ }));
432
+ // we need to compress the mesh with the same compression method as the original file
433
+ const root = doc.getRoot();
434
+ const compressionExt = root.getExtension(NEEDLE_mesh_compression.EXTENSION_NAME);
435
+ if (compressionExt) {
436
+ const newExt = newDoc.createExtension(NEEDLE_mesh_compression);
437
+ const newProp = newExt.create(compressionExt.model);
438
+ console.log(`${NAME}: Pass compression format to mesh LOD ${level} → ${JSON.stringify(compressionExt.model)}`);
439
+ newDoc.getRoot().setExtension(NEEDLE_mesh_compression.EXTENSION_NAME, newProp);
440
+ }
441
+ const newIo = new NodeIO();
442
+ registerExtensions(newIo);
443
+ const ext = ".glb";
444
+ const name = "mesh_lod_" + level + "_" + guid;
445
+ const filename = name + ext;
446
+ const outName = basePath + filename;
447
+ console.log(`${NAME}: Write mesh LOD ${level} to \"${filename}\" with: ${JSON.stringify(lodLevel)}`);
448
+ newIo.write(outName, newDoc);
449
+ return filename;
450
+ }
451
+ function simplifyMesh(doc, mesh, prim, lodinfo) {
452
+ const weldOptions = {
453
+ ...WELD_DEFAULTS,
454
+ overwrite: true,
455
+ };
456
+ const simplifyOptions = {
457
+ ...SIMPLIFY_DEFAULTS, simplifier: MeshoptSimplifier,
458
+ };
459
+ if (lodinfo.error != undefined)
460
+ simplifyOptions.error = lodinfo.error;
461
+ if (lodinfo.ratio != undefined)
462
+ simplifyOptions.ratio = lodinfo.ratio;
463
+ compressPrimitive(doc, mesh, prim, weldOptions, simplifyOptions);
464
+ }
465
+ function isMeshLOD(filepath) {
466
+ return filepath.includes("mesh_lod_");
467
+ }
468
+ function isImageLOD(filepath) {
469
+ return filepath.includes("image_");
470
+ }
@@ -62,7 +62,7 @@ async function testEntry(entry, callback, context) {
62
62
  // Try resolve potential relative path
63
63
  const newPath = context.baseDirectory + "/" + entry;
64
64
  if (existsSync(newPath) === false) {
65
- console.log(`INFO: Resolved path failed: ${entry} -> ${newPath}`);
65
+ console.log(`INFO: Resolved path failed: ${entry} -> ${newPath} (doesnt exist or is locked by another process)`);
66
66
  return;
67
67
  }
68
68
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "1.3.1",
3
+ "version": "1.4.1-beta",
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",
@@ -16,9 +16,9 @@
16
16
  "compile": "tsc || exit 1"
17
17
  },
18
18
  "dependencies": {
19
- "@gltf-transform/core": "^3.7.3",
20
- "@gltf-transform/extensions": "^3.7.3",
21
- "@gltf-transform/functions": "^3.7.3",
19
+ "@gltf-transform/core": "^3.10.0",
20
+ "@gltf-transform/extensions": "^3.10.0",
21
+ "@gltf-transform/functions": "^3.10.0",
22
22
  "command-exists": "^1.2.9",
23
23
  "command-line-args": "^5.2.1",
24
24
  "draco3dgltf": "^1.5.3",