@needle-tools/gltf-build-pipeline 2.15.8 → 2.15.9-next.53d13f4

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.
@@ -3,6 +3,8 @@ import { existsSync, readFileSync } from 'fs';
3
3
  import { join, dirname } from 'path';
4
4
  import { isLightmap } from '../../utils/texture.js';
5
5
  import { getExtensionInputFile } from '../index.js';
6
+ import { findSource, getTexture } from '../utils.js';
7
+ import { testAssert } from '../../utils/test-assert.js';
6
8
  const EXTENSION_NAME = 'NEEDLE_lightmaps';
7
9
  /**
8
10
  * Opaque-like ExtensionProperty that holds NEEDLE_lightmaps data on the Root.
@@ -66,7 +68,8 @@ export class NEEDLE_lightmaps_ext extends Extension {
66
68
  continue;
67
69
  const exrPath = inputDir ? join(inputDir, entry.pointer) : '';
68
70
  if (!exrPath || !existsSync(exrPath)) {
69
- this.document.getLogger().warn(`NEEDLE_lightmaps: External EXR not found: ${exrPath || entry.pointer}`);
71
+ this.document.getLogger().error(`NEEDLE_lightmaps: External EXR not found: ${exrPath || entry.pointer}`);
72
+ testAssert(false, `NEEDLE_lightmaps: External EXR not found: ${exrPath || entry.pointer}`);
70
73
  continue;
71
74
  }
72
75
  // Read EXR bytes and inject into JSON so the core reader creates a Texture
@@ -113,12 +116,7 @@ export class NEEDLE_lightmaps_ext extends Extension {
113
116
  const texDef = textureDefs[texIndex];
114
117
  if (texDef) {
115
118
  inputTextureDef = texDef;
116
- const imageIndex = texDef.source
117
- ?? texDef.extensions?.['EXT_texture_exr']?.source
118
- ?? texDef.extensions?.['KHR_texture_basisu']?.source;
119
- if (imageIndex !== undefined) {
120
- texture = context.textures[imageIndex] ?? null;
121
- }
119
+ texture = getTexture(context, texDef) ?? null;
122
120
  }
123
121
  }
124
122
  else if (entry.pointer.match(/\.exr$/i)) {
@@ -126,7 +124,19 @@ export class NEEDLE_lightmaps_ext extends Extension {
126
124
  const imageIndex = this.injectedImageIndices.get(entry.pointer);
127
125
  if (imageIndex !== undefined) {
128
126
  texture = context.textures[imageIndex] ?? null;
129
- inputTextureDef = textureDefs.find(t => t.source === imageIndex);
127
+ inputTextureDef = textureDefs.find(t => findSource(t) === imageIndex);
128
+ }
129
+ }
130
+ else if (!entry.pointer.startsWith('/')) {
131
+ // Already-processed external pointer (e.g. "sunrise.pmrem.ktx2")
132
+ // Find the texture by matching the URI in the existing images
133
+ const images = json.images ?? [];
134
+ for (let i = 0; i < images.length; i++) {
135
+ if (images[i].uri === entry.pointer) {
136
+ texture = context.textures[i] ?? null;
137
+ inputTextureDef = textureDefs.find(t => findSource(t) === i);
138
+ break;
139
+ }
130
140
  }
131
141
  }
132
142
  if (texture) {
@@ -141,7 +151,8 @@ export class NEEDLE_lightmaps_ext extends Extension {
141
151
  }
142
152
  }
143
153
  else {
144
- logger.warn(`NEEDLE_lightmaps: Could not resolve texture for pointer "${entry.pointer}"`);
154
+ logger.error(`NEEDLE_lightmaps: Could not resolve texture for pointer "${entry.pointer}"`);
155
+ testAssert(false, `NEEDLE_lightmaps: Could not resolve texture for pointer "${entry.pointer}"`);
145
156
  }
146
157
  }
147
158
  // Lightmap workaround: prevent compression on lightmap textures (non-EXR)
@@ -217,11 +228,7 @@ export class NEEDLE_lightmaps_ext extends Extension {
217
228
  const textures = context.jsonDoc.json.textures = context.jsonDoc.json.textures || [];
218
229
  // Try to find an existing texture def pointing to the same image
219
230
  for (let i = 0; i < textures.length; i++) {
220
- const texDef = textures[i];
221
- const source = texDef.source
222
- ?? texDef.extensions?.['KHR_texture_basisu']?.source
223
- ?? texDef.extensions?.['NEEDLE_pmrem']?.source;
224
- if (source === imageIndex) {
231
+ if (findSource(textures[i]) === imageIndex) {
225
232
  return i;
226
233
  }
227
234
  }
@@ -1,5 +1,5 @@
1
1
  import { Extension, ExtensionProperty, PropertyType, Node, TextureChannel } from '@gltf-transform/core';
2
- import { getTexture, tryGetInArray, writeExtension } from '../utils.js';
2
+ import { findSource, getTexture, tryGetInArray, writeExtension } from '../utils.js';
3
3
  import { isLightmap } from '../../utils/texture.js';
4
4
  import { NEEDLE_progressive_texture_settings } from '../NEEDLE_progressive_texture_settings/index.js';
5
5
  import { testAssert } from '../../utils/test-assert.js';
@@ -655,7 +655,8 @@ class JsonPointerHandler {
655
655
  this.externalExrPointers.push({ obj, key, texture });
656
656
  }
657
657
  else if (exrPath) {
658
- console.warn(`WARN: External EXR not found: ${exrPath}`);
658
+ this.document.getLogger().error(`NEEDLE_opaque: External EXR not found: ${exrPath}`);
659
+ testAssert(false, `NEEDLE_opaque: External EXR not found: ${exrPath}`);
659
660
  }
660
661
  }
661
662
  if (value.startsWith("/materials/")) {
@@ -999,22 +1000,6 @@ class TextureIndexResolver extends PointerResolver {
999
1000
  return json.samplers.length - 1;
1000
1001
  }
1001
1002
  }
1002
- function findSource(texDef) {
1003
- if (texDef.source !== undefined) {
1004
- return texDef.source;
1005
- }
1006
- if (texDef.extensions) {
1007
- const keys = Object.keys(texDef.extensions);
1008
- for (let i = 0; i < keys.length; i++) {
1009
- const key = keys[i];
1010
- const ext = texDef.extensions[key];
1011
- if (ext["source"] !== undefined) {
1012
- return ext["source"];
1013
- }
1014
- }
1015
- }
1016
- return undefined;
1017
- }
1018
1003
  class MaterialIndexResolver extends PointerResolver {
1019
1004
  init(context) {
1020
1005
  }
@@ -3,4 +3,7 @@ import { GLTF } from '@gltf-transform/core';
3
3
  export declare function tryGetInArray<T>(arr: Array<T> | undefined | null, index: number | undefined): T | null;
4
4
  export declare function writeExtension(prop: GLTF.IProperty | null, name: string, ext: any): void;
5
5
  export declare function getTexture(context: ReaderContext, tex: GLTF.ITexture): Texture;
6
+ /** Resolve the image source index from a texture definition, checking `texDef.source`
7
+ * and then iterating all extension keys for a `.source` property. */
8
+ export declare function findSource(texDef: GLTF.ITexture): number | undefined;
6
9
  export declare function getTextureDefinition(context: WriterContext | ReaderContext, tex: Texture): GLTF.ITexture | null;
@@ -21,19 +21,26 @@ export function writeExtension(prop, name, ext) {
21
21
  export function getTexture(context, tex) {
22
22
  if (!context.jsonDoc.json.images)
23
23
  return null;
24
- if (tex.source !== undefined) {
25
- return context.textures[tex.source];
24
+ const source = findSource(tex);
25
+ if (source !== undefined) {
26
+ return context.textures[source];
26
27
  }
27
- else {
28
- for (const key of Object.keys(tex.extensions)) {
29
- const ext = tex.extensions[key];
30
- if (ext && typeof ext["source"] === "number") {
31
- return context.textures[ext["source"]];
32
- }
28
+ console.log("Could not find texture", tex);
29
+ return null;
30
+ }
31
+ /** Resolve the image source index from a texture definition, checking `texDef.source`
32
+ * and then iterating all extension keys for a `.source` property. */
33
+ export function findSource(texDef) {
34
+ if (texDef.source !== undefined)
35
+ return texDef.source;
36
+ if (texDef.extensions) {
37
+ for (const key of Object.keys(texDef.extensions)) {
38
+ const ext = texDef.extensions[key];
39
+ if (ext && ext["source"] !== undefined)
40
+ return ext["source"];
33
41
  }
34
42
  }
35
- console.log("Could not fnd texture", tex);
36
- return null;
43
+ return undefined;
37
44
  }
38
45
  export function getTextureDefinition(context, tex) {
39
46
  const textureDefinitions = context.jsonDoc.json.textures;
@@ -146,7 +146,7 @@ export class NeedlePmremContext {
146
146
  const dstSizestr = ktx2Bytes.length < 100 * 1024
147
147
  ? `${(ktx2Bytes.length / 1024).toFixed(0)} KB`
148
148
  : `${(ktx2Bytes.length / (1024 * 1024)).toFixed(2)} MB`;
149
- this.logger.info(`pmrem:texture[${index}] ${name}: ${sizeStr} EXR → ${dstSizestr} KTX2`);
149
+ this.logger.info(`pmrem:texture[${index}] ${name}: ${sizeStr} EXR → ${dstSizestr} KTX2 (${texture.getURI() || texture.getName() || name})`);
150
150
  this.convertedTextures.push(texture);
151
151
  this.processed++;
152
152
  return true;
@@ -172,16 +172,24 @@ export class NeedlePmremContext {
172
172
  return cached;
173
173
  }
174
174
  }
175
- // 1. Run PMREM on the EXR bytes
176
- this.logger.info(`pmrem:texture[${index}] ${name}: Running PMREM...`);
175
+ // 1. Run PMREM on the EXR bytes (synchronous WASM — may take a while)
176
+ const inputSize = exrBytes.byteLength < 100 * 1024
177
+ ? `${(exrBytes.byteLength / 1024).toFixed(0)} KB`
178
+ : `${(exrBytes.byteLength / (1024 * 1024)).toFixed(2)} MB`;
179
+ this.logger.info(`pmrem:texture[${index}] ${name}: Running PMREM WASM (${inputSize} EXR) — this may take a while...`);
180
+ // Yield to allow log to flush before blocking WASM call
181
+ await new Promise(r => setTimeout(r, 0));
182
+ const t0 = Date.now();
177
183
  const pmremExrBytes = this.wasmModule.pmrem_exr(exrBytes);
184
+ const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
185
+ this.logger.info(`pmrem:texture[${index}] ${name}: PMREM done in ${elapsed}s (${(pmremExrBytes.byteLength / 1024 / 1024).toFixed(1)} MB)`);
178
186
  // 2. Write PMREM EXR to temp file
179
187
  const id = uuid();
180
188
  const tempExr = join(this.tmpDir, `${id}.pmrem.exr`);
181
189
  const tempKtx2 = join(this.tmpDir, `${id}.ktx2`);
182
190
  writeFileSync(tempExr, Buffer.from(pmremExrBytes));
183
191
  // 3. Encode to KTX2 HDR via basisu
184
- this.logger.debug(`pmrem:texture[${index}] ${name}: Encoding KTX2 HDR...`);
192
+ this.logger.info(`pmrem:texture[${index}] ${name}: Encoding KTX2 HDR...`);
185
193
  const variant = 'hdr4x4'; // 6x6 needs an update in Needle
186
194
  await encodeKTX2WithBasisu(this.basisuExe, tempExr, tempKtx2, variant, true);
187
195
  if (!existsSync(tempKtx2)) {
@@ -1 +1 @@
1
- export declare const version = "2.15.8";
1
+ export declare const version = "2.15.9-next.53d13f4";
@@ -1 +1 @@
1
- export const version = "2.15.8";
1
+ export const version = "2.15.9-next.53d13f4";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "2.15.8",
3
+ "version": "2.15.9-next.53d13f4",
4
4
  "description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",