rayzee 11.1.0 → 11.2.0

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.
Files changed (34) hide show
  1. package/README.md +29 -7
  2. package/dist/{ArchiveImporter-Hn35o6Z5.js → ArchiveImporter-eNlu0_6F.js} +882 -829
  3. package/dist/{BidirectionalIntegrator-CBw8hkeu.js → BidirectionalIntegrator-D1-6UySB.js} +226 -226
  4. package/dist/{NeuralRetouch-BzNpiVHZ.js → NeuralRetouch-DFGbnmLb.js} +2 -2
  5. package/dist/{NeuralSuperRes-Bk7bHg2B.js → NeuralSuperRes-BtXvyJQl.js} +2 -2
  6. package/dist/{PathTracer-72XufKAr.js → PathTracer-BHXGJtPN.js} +2959 -3001
  7. package/dist/{PhysicalSky-BgjdV88k.js → PhysicalSky-DN6u8UX4.js} +1 -1
  8. package/dist/{RayzeeRenderer-1QRO97eq.js → RayzeeRenderer-Dw1x8mVL.js} +401 -362
  9. package/dist/SceneMetadata-D2Sh-0e-.js +110 -0
  10. package/dist/addons/archives.es.js +1 -1
  11. package/dist/addons/bidirectional.es.js +1 -1
  12. package/dist/addons/physical-sky.es.js +1 -1
  13. package/dist/addons/storage.es.js +1 -1
  14. package/dist/{identity-DrQsGT2o.js → identity-C1cLD1C0.js} +14 -4
  15. package/dist/{openStorage-DwcBRUj7.js → openStorage-Fe9BoS1_.js} +1 -1
  16. package/dist/rayzee-core.es.js +6 -6
  17. package/dist/rayzee.es.js +828 -828
  18. package/dist/rayzee.umd.js +95 -94
  19. package/dist/{sceneCachePolicy-C67jbci-.js → sceneCachePolicy-DA7iGoL4.js} +71 -29
  20. package/package.json +1 -1
  21. package/src/EngineDefaults.js +1 -1
  22. package/src/PathTracerApp.js +1 -0
  23. package/src/Processor/ArchiveImporter.js +130 -96
  24. package/src/Processor/ArchiveReader.js +46 -3
  25. package/src/Processor/AssetLoader.js +26 -1
  26. package/src/Processor/archiveFormats.js +35 -0
  27. package/src/RayzeeRenderer.js +43 -12
  28. package/src/Storage/identity.js +22 -1
  29. package/src/Storage/index.js +1 -1
  30. package/src/TSL/MaterialEvaluation.js +4 -3
  31. package/src/core.js +1 -1
  32. package/src/index.js +2 -2
  33. package/dist/archiveFormats-tnAoUL1j.js +0 -94
  34. /package/dist/{ToneMapGPU-CjU3osDd.js → ToneMapGPU-BOjpul7A.js} +0 -0
@@ -124,7 +124,7 @@ function w(e, t, n) {
124
124
  let r = n ? new e(new SharedArrayBuffer(t * e.BYTES_PER_ELEMENT)) : new e(t);
125
125
  return S && r.byteLength > 0 && S(r.byteLength, n), r;
126
126
  }
127
- var T = class e {
127
+ var ee = class e {
128
128
  constructor(e, t, n, r = b, i = !1) {
129
129
  let a = t * n.BYTES_PER_ELEMENT, o = Math.max(1, Math.floor(r / a));
130
130
  this.recordCount = e, this.lanesPerRecord = t, this.LaneType = n, this.recordsPerChunk = e <= o ? Math.max(e, 1) : o, this.shared = i, this.chunks = [];
@@ -288,31 +288,73 @@ var T = class e {
288
288
  let n = (e) => new t(e.buffer, e.byteOffset, e.byteLength / t.BYTES_PER_ELEMENT), r = e.adopt(this.chunks.map((e) => e ? n(e) : void 0), this.recordCount, this.lanesPerRecord, this.recordsPerChunk);
289
289
  return r.LaneType = t, r._owner = this, this._views ? this._views.push(r) : (this._weakViews ??= []).push(new WeakRef(r)), r._spilled = this._spilled ??= /* @__PURE__ */ new Set(), r;
290
290
  }
291
- }, E = 5;
292
- function D(e, t) {
291
+ }, T = {
292
+ Worker: null,
293
+ decodeImage: null
294
+ };
295
+ function E(e) {
296
+ e && Object.assign(T, e);
297
+ }
298
+ function D() {
299
+ return T;
300
+ }
301
+ function O() {
302
+ return typeof navigator < "u" && navigator.hardwareConcurrency || 4;
303
+ }
304
+ function k() {
305
+ return !!T.decodeImage || typeof self < "u" && (typeof createImageBitmap < "u" || typeof document < "u");
306
+ }
307
+ function A() {
308
+ return !!T.Worker || typeof Worker < "u";
309
+ }
310
+ var j = 0;
311
+ async function M(e) {
312
+ if (!T.Worker || j === 0 && globalThis.Worker !== void 0) return e();
313
+ j++ === 0 && (globalThis.Worker = T.Worker);
314
+ try {
315
+ return await e();
316
+ } finally {
317
+ --j === 0 && globalThis.Worker === T.Worker && delete globalThis.Worker;
318
+ }
319
+ }
320
+ function N(e, t) {
321
+ let n = T.Worker;
322
+ if (!n) return new e(t);
323
+ let r = Object.prototype.hasOwnProperty.call(globalThis, "Worker"), i = globalThis.Worker;
324
+ globalThis.Worker = n;
325
+ try {
326
+ return new e(t);
327
+ } finally {
328
+ r ? globalThis.Worker = i : delete globalThis.Worker;
329
+ }
330
+ }
331
+ //#endregion
332
+ //#region src/Storage/CDFCache.js
333
+ var P = 5;
334
+ function F(e, t) {
293
335
  e && t && (e.userData.__rayzeeSource = t);
294
336
  }
295
- function O(e) {
337
+ function I(e) {
296
338
  let t = e?.userData?.__rayzeeSource;
297
339
  if (!t) return null;
298
340
  let { width: n = 0, height: r = 0 } = e.image ?? {};
299
- return `cdf:${E}:${t}|${n}x${r}|${e.userData.__rayzeeColorSpace ?? "native"}`;
341
+ return `cdf:${P}:${t}|${n}x${r}|${e.userData.__rayzeeColorSpace ?? "native"}`;
300
342
  }
301
- var k = () => n(i().cacheNamespace)?.area(r.CDF) ?? null, A = [
343
+ var L = () => n(i().cacheNamespace)?.area(r.CDF) ?? null, R = [
302
344
  "exactConditional",
303
345
  "exactMarginal",
304
346
  "exactRowGuide",
305
347
  "exactMarginalGuide"
306
- ], j = A.map((e) => `${e}.f32`);
307
- async function M(e) {
308
- let t = O(e), n = t ? await k()?.open(t) : null;
348
+ ], z = R.map((e) => `${e}.f32`);
349
+ async function B(e) {
350
+ let t = I(e), n = t ? await L()?.open(t) : null;
309
351
  if (!n) return null;
310
352
  try {
311
- let e = await Promise.all(j.map((e) => n.file(e)));
353
+ let e = await Promise.all(z.map((e) => n.file(e)));
312
354
  if (e.some((e) => !e)) return null;
313
355
  let t = await Promise.all(e.map(async (e) => new Float32Array(await e.arrayBuffer()))), { totalSum: r, width: i, height: a, exactWidth: o, exactHeight: s, radianceIntegral: c } = n.extra;
314
356
  return {
315
- ...Object.fromEntries(A.map((e, n) => [e, t[n]])),
357
+ ...Object.fromEntries(R.map((e, n) => [e, t[n]])),
316
358
  totalSum: r,
317
359
  width: i,
318
360
  height: a,
@@ -324,8 +366,8 @@ async function M(e) {
324
366
  n.release();
325
367
  }
326
368
  }
327
- async function N(e, t) {
328
- let { totalSum: n, width: r, height: i, exactWidth: a, exactHeight: o, radianceIntegral: s } = t, c = A.map((e) => t[e]), l = O(e), u = l ? k() : null;
369
+ async function te(e, t) {
370
+ let { totalSum: n, width: r, height: i, exactWidth: a, exactHeight: o, radianceIntegral: s } = t, c = R.map((e) => t[e]), l = I(e), u = l ? L() : null;
329
371
  if (!u || c.some((e) => !e)) return !1;
330
372
  let d = await u.create(l, {
331
373
  label: e.name || "environment",
@@ -333,7 +375,7 @@ async function N(e, t) {
333
375
  });
334
376
  if (!d) return !1;
335
377
  try {
336
- for (let e = 0; e < j.length; e++) await d.write(j[e], c[e]);
378
+ for (let e = 0; e < z.length; e++) await d.write(z[e], c[e]);
337
379
  return await d.commit({
338
380
  totalSum: n,
339
381
  width: r,
@@ -348,8 +390,8 @@ async function N(e, t) {
348
390
  }
349
391
  //#endregion
350
392
  //#region src/Processor/HostMemory.js
351
- var P = t.FLOATS_PER_TRIANGLE * 4, F = 64, I = .9, L = 64, R = 4, z = 1536 * 1024 * 1024, B = 7040 * 1024 * 1024, V = 9216 * 1024 * 1024, H = 6e7, U = 8e6;
352
- function W(e) {
393
+ var V = t.FLOATS_PER_TRIANGLE * 4, H = 64, U = .9, W = 64, G = 4, K = 1536 * 1024 * 1024, q = 7040 * 1024 * 1024, J = 9216 * 1024 * 1024, Y = 6e7, X = 8e6;
394
+ function Z(e) {
353
395
  if (e > 0) return {
354
396
  gb: e,
355
397
  assumed: !1
@@ -363,19 +405,19 @@ function W(e) {
363
405
  assumed: !0
364
406
  };
365
407
  }
366
- var G = 1.15, K = 12 * 1024 * 1024 * 1024;
367
- function q({ triangles: e, placements: t, geometryBytes: n = 0 }) {
408
+ var Q = 1.15, ne = 12 * 1024 * 1024 * 1024;
409
+ function re({ triangles: e, placements: t, geometryBytes: n = 0 }) {
368
410
  let r = {
369
- triangles: e * P,
370
- bvh: Math.ceil(e * I) * F,
411
+ triangles: e * V,
412
+ bvh: Math.ceil(e * U) * H,
371
413
  geometry: n,
372
- placements: t * L,
373
- orderMaps: e * R
414
+ placements: t * W,
415
+ orderMaps: e * G
374
416
  };
375
417
  return r.total = r.triangles + r.bvh + r.geometry + r.placements + r.orderMaps, r;
376
418
  }
377
- function J(e, { chunkBytes: t = b, shared: n = !1, allocate: r = null } = {}) {
378
- let i = Math.min(e, K), a = r ?? (n ? (e) => new SharedArrayBuffer(e) : (e) => new ArrayBuffer(e)), o = [], s = 0;
419
+ function ie(e, { chunkBytes: t = b, shared: n = !1, allocate: r = null } = {}) {
420
+ let i = Math.min(e, ne), a = r ?? (n ? (e) => new SharedArrayBuffer(e) : (e) => new ArrayBuffer(e)), o = [], s = 0;
379
421
  try {
380
422
  for (; s < i;) {
381
423
  let e = Math.min(t, i - s);
@@ -390,7 +432,7 @@ function J(e, { chunkBytes: t = b, shared: n = !1, allocate: r = null } = {}) {
390
432
  capped: e > i
391
433
  };
392
434
  }
393
- var Y = class {
435
+ var ae = class {
394
436
  constructor() {
395
437
  this.reset();
396
438
  }
@@ -435,9 +477,9 @@ var Y = class {
435
477
  samples: this.samples.slice()
436
478
  };
437
479
  }
438
- }, X = "11.1.0", Z = 1e4, Q = 15e5;
439
- function $(e, t, n = Z) {
440
- return e >= n && t / Q < e / 3;
480
+ }, oe = "11.2.0", $ = 1e4, se = 15e5;
481
+ function ce(e, t, n = $) {
482
+ return e >= n && t / se < e / 3;
441
483
  }
442
484
  //#endregion
443
- export { c as A, g as C, s as D, u as E, o as O, y as S, m as T, T as _, Y as a, C as b, B as c, W as d, q as f, D as g, N as h, V as i, l as j, _ as k, U as l, M as m, $ as n, z as o, J as p, X as r, G as s, Z as t, H as u, b as v, h as w, v as x, x as y };
485
+ export { g as A, M as C, C as D, x as E, o as F, _ as I, c as L, m as M, u as N, v as O, s as P, l as R, A as S, b as T, E as _, ae as a, O as b, q as c, Z as d, re as f, F as g, te as h, J as i, h as j, y as k, X as l, B as m, ce as n, K as o, ie as p, oe as r, Q as s, $ as t, Y as u, N as v, ee as w, k as x, D as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "11.1.0",
3
+ "version": "11.2.0",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -77,7 +77,7 @@ export const ENGINE_DEFAULTS = deepFreeze( {
77
77
 
78
78
  // Read when a model loads
79
79
  maxTextureSize: 4096, // longest edge of a material texture; clamped to the hardware ceiling
80
- areaLightIntensityScale: 0.1, // power of a glTF model's placeholder area lights (RectAreaLight extras)
80
+ areaLightIntensityScale: 1, // power of a glTF model's placeholder area lights (RectAreaLight extras)
81
81
 
82
82
  // Debug
83
83
  visMode: 0, // a debug view (TSL/Debugger.js); 0 renders normally
@@ -461,6 +461,7 @@ export class PathTracerApp extends RayzeeRenderer {
461
461
 
462
462
  this._syncControlsAfterLoad();
463
463
  this.cameraManager.currentCameraIndex = 0;
464
+ if ( this.cameraManager.cameras.length > 1 ) this.cameraManager.switchCamera( 1 );
464
465
 
465
466
  }
466
467
 
@@ -12,11 +12,11 @@ import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
12
12
  import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
13
13
  import { zipSync, strFromU8 } from 'three/addons/libs/fflate.module.js';
14
14
  import {
15
- detectArchiveKind, readTarGz, elementFilter, listArchiveElements, openTar, indexTarHeaders } from './ArchiveReader.js';
15
+ detectArchiveKind, readTarGz, elementFilter, listArchiveElements, openTar, indexTarHeaders, openFolder } from './ArchiveReader.js';
16
16
  import { openZip, readZipDirectory } from './ZipReader.js';
17
17
  import { unpackTarGz, loadTarIndex, saveTarIndex } from './ArchiveCache.js';
18
18
  import { setEnvironmentSource } from '../Storage/CDFCache.js';
19
- import { fileIdentity, identityKey, sampleHash } from '../Storage/identity.js';
19
+ import { fileIdentity, folderIdentity, identityKey, sampleHash } from '../Storage/identity.js';
20
20
  import { ENGINE_AREAS } from '../Storage/areas.js';
21
21
  import { encodeSceneGraph, writeSceneGraph, decodeSceneGraph, SceneGraphUnsupported, SCENE_GRAPH_FORMAT, ARCHIVE_PATH, ARCHIVE_LOADER } from '../Storage/SceneGraphCodec.js';
22
22
  import { worthStoring } from '../Storage/sceneCachePolicy.js';
@@ -27,12 +27,40 @@ import { pfmTexture } from './PBRT/PFM.js';
27
27
  import { extractSceneMetadata } from './SceneMetadata.js';
28
28
  import { ISSUE_CODES, ISSUE_SEVERITY } from '../EngineIssues.js';
29
29
  import { ARCHIVE_FORMATS } from './archiveFormats.js';
30
+ import { withHostWorker } from '../Platform.js';
30
31
 
31
32
  // Loose USD layers inside a ZIP compose into one scene; these pick out the
32
33
  // layers and the image assets they reference.
33
34
  const USD_LAYER_RE = /\.(usd|usda|usdc)$/i;
34
35
  const USD_IMAGE_RE = /\.(png|jpg|jpeg|avif)$/i;
35
36
  const MTL_TEXTURE_TIMEOUT_MS = 30000;
37
+ const MAIN_MODEL_FILES = [ 'scene.gltf', 'scene.glb', 'model.gltf', 'model.glb', 'main.gltf', 'main.glb', 'asset.gltf', 'asset.glb' ];
38
+
39
+ // An entry is bytes already read, or a Blob read only when a loader asks for it.
40
+ const asBlob = entry => ( entry instanceof Blob ? entry : new Blob( [ entry ], { type: 'application/octet-stream' } ) );
41
+ const bytesOf = async entry => ( entry instanceof Blob ? new Uint8Array( await entry.arrayBuffer() ) : entry );
42
+ const textOf = async entry => ( entry instanceof Blob ? entry.text() : strFromU8( entry ) );
43
+ const joinPath = ( dir, ref ) => {
44
+
45
+ const out = [];
46
+ for ( const part of `${dir}/${ref}`.split( '/' ) ) {
47
+
48
+ if ( part === '..' ) out.pop();
49
+ else if ( part && part !== '.' ) out.push( part );
50
+
51
+ }
52
+
53
+ return out.join( '/' );
54
+
55
+ };
56
+
57
+ const bufferOf = async entry => {
58
+
59
+ const bytes = await bytesOf( entry );
60
+ return bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength ? bytes.buffer : bytes.slice().buffer;
61
+
62
+ };
63
+
36
64
  /**
37
65
  * Unpacked size past which a multi-part scene archive asks which parts to load rather than
38
66
  * taking all of them. Not a hard limit — picking every part is a valid answer.
@@ -227,6 +255,29 @@ export class ArchiveImporter {
227
255
 
228
256
  }
229
257
 
258
+ /**
259
+ * Loads a folder as it would the same folder zipped — a glTF with its .bin and textures, an OBJ with its
260
+ * materials, a pbrt scene — reading each file only when the scene asks for it.
261
+ * @param {{name: string, files: Array<{path: string, file: Blob}>}} folder - from `localFolder`
262
+ * @param {object} [options] - `element`, `pbrtEntry`, `promptBytes` and the pbrt options, as loadArchiveFromFile
263
+ */
264
+ async loadFolder( folder, { pbrtEntry = null, element = null, promptBytes, ...pbrt } = {} ) {
265
+
266
+ const chosen = ( Array.isArray( element ) ? element : [ element ] ).filter( Boolean );
267
+ const source = openFolder( folder, { filter: chosen.length ? elementFilter( chosen ) : null } );
268
+ const totalBytes = source.listing.reduce( ( n, e ) => n + e.size, 0 );
269
+ if ( chosen.length === 0 ) this._requireElementChoice( folder.name, source.listing, totalBytes, promptBytes );
270
+
271
+ console.info(
272
+ `Folder "${folder.name}": ${source.listing.length} files, ${( totalBytes / 1e9 ).toFixed( 1 )} GB` +
273
+ ( chosen.length ? `, ${chosen.length} part${chosen.length > 1 ? 's' : ''} selected.` : '.' )
274
+ );
275
+
276
+ const archiveId = this.loader.storage ? identityKey( await folderIdentity( folder ) ) : null;
277
+ return await this._loadSource( source, folder.name, { element, pbrtEntry, pbrt, archiveId, kind: 'folder' } );
278
+
279
+ }
280
+
230
281
  async _loadNonPBRTArchive( entries, filename ) {
231
282
 
232
283
  const result = await this.processObjMtlPairsInZip( entries, filename );
@@ -243,6 +294,13 @@ export class ArchiveImporter {
243
294
  async _loadSeekable( file, filename, { element, promptBytes, pbrtEntry, pbrt, index = null, zip = false, archiveId = null } ) {
244
295
 
245
296
  const source = await this._openSeekableArchive( file, filename, element, promptBytes, { index, zip } );
297
+ return await this._loadSource( source, filename, { element, pbrtEntry, pbrt, archiveId } );
298
+
299
+ }
300
+
301
+ /** Loads what an opened archive or folder holds. @private */
302
+ async _loadSource( source, filename, { element, pbrtEntry, pbrt, archiveId, kind = 'archive' } ) {
303
+
246
304
  source.archiveId = archiveId;
247
305
  source.elements = ( Array.isArray( element ) ? element : [ element ] ).filter( Boolean );
248
306
  if ( source.listing.some( e => e.path.toLowerCase().endsWith( '.pbrt' ) ) ) {
@@ -251,10 +309,10 @@ export class ArchiveImporter {
251
309
 
252
310
  }
253
311
 
254
- // Not a pbrt scene: fall back to materialising it, which those paths still expect.
255
- for ( const e of source.listing ) if ( e.offset !== undefined ) source.entries[ e.path ] ??= await source.read( e.path );
256
- const result = await this._loadNonPBRTArchive( source.entries, filename );
257
- this.loader._sourceKey = this.loader._keyed( 'archive', archiveId );
312
+ const entries = Object.create( null );
313
+ for ( const e of source.listing ) if ( e.offset !== undefined ) entries[ e.path ] = source.entries[ e.path ] ?? await source.slice( e.path );
314
+ const result = await this._loadNonPBRTArchive( entries, filename );
315
+ this.loader._sourceKey = this.loader._keyed( kind, archiveId );
258
316
  return result;
259
317
 
260
318
  }
@@ -691,7 +749,7 @@ export class ArchiveImporter {
691
749
  updateLoading( { isLoading: true, status: 'Processing PBRT geometry...', progress: 10 } );
692
750
  await this.loader.onModelLoad( this.loader.targetModel );
693
751
 
694
- this.loader.dispatchEvent( { type: 'load', model: group, filename: `${loadedEntry} (from ZIP)` } );
752
+ this.loader.dispatchEvent( { type: 'load', model: group, filename: `${loadedEntry} (from ${filename})` } );
695
753
  return group;
696
754
 
697
755
  }
@@ -833,42 +891,38 @@ export class ArchiveImporter {
833
891
 
834
892
  }
835
893
 
836
- async findAndLoadModelFromZip( zip ) {
894
+ async findAndLoadModelFromZip( zip, filename = 'the ZIP archive' ) {
837
895
 
838
- const mainModelFiles = [
839
- 'scene.gltf', 'scene.glb', 'model.gltf', 'model.glb',
840
- 'main.gltf', 'main.glb', 'asset.gltf', 'asset.glb'
841
- ];
842
-
843
- for ( const mainFile of mainModelFiles ) {
896
+ const paths = Object.keys( zip );
897
+ const top = paths[ 0 ]?.split( '/' )[ 0 ];
898
+ const root = top !== undefined && paths.every( p => p.startsWith( top + '/' ) ) ? top + '/' : '';
899
+ const models = paths.filter( p => p.split( '.' ).pop().toLowerCase() === 'obj' || this.loader.getFileFormat( p )?.type === 'model' );
900
+ if ( models.length === 0 ) throw new Error( `No supported model files found in ${filename}` );
844
901
 
845
- if ( zip[ mainFile ] ) {
902
+ // Shallowest first, glTF before other formats at the same depth.
903
+ const rank = p => p.split( '/' ).length * 2 + ( /\.(gltf|glb)$/i.test( p ) ? 0 : 1 );
904
+ const main = MAIN_MODEL_FILES.map( name => root + name ).find( p => zip[ p ] );
905
+ const path = main ?? models.reduce( ( best, p ) => ( rank( p ) < rank( best ) ? p : best ) );
846
906
 
847
- console.log( `Found main model file: ${mainFile}` );
848
- const extension = mainFile.split( '.' ).pop().toLowerCase();
849
- return await this.loadModelFromZipEntry( zip[ mainFile ], mainFile, extension, zip );
850
-
851
- }
907
+ // A loose layer is only one slice of a USD scene — hand the whole
908
+ // archive over so its references and payloads can resolve.
909
+ if ( ! main && USD_LAYER_RE.test( path ) ) return await this.loadUSDHierarchyFromZip( zip );
852
910
 
853
- }
854
-
855
- for ( const path in zip ) {
856
-
857
- const extension = path.split( '.' ).pop().toLowerCase();
858
- if ( extension === 'obj' || this.loader.getFileFormat( path )?.type === 'model' ) {
859
-
860
- // A loose layer is only one slice of a USD scene — hand the whole
861
- // archive over so its references and payloads can resolve.
862
- if ( USD_LAYER_RE.test( path ) ) return await this.loadUSDHierarchyFromZip( zip );
863
-
864
- console.log( `Loading model file from ZIP: ${path}` );
865
- return await this.loadModelFromZipEntry( zip[ path ], path, extension, zip );
911
+ const others = models.filter( p => p !== path && ! USD_LAYER_RE.test( p ) );
912
+ if ( others.length > 0 ) {
866
913
 
867
- }
914
+ console.warn( `${filename} holds ${others.length + 1} models; loaded "${path}". Others: ${others.join( ', ' )}` );
915
+ this.loader._issues?.record(
916
+ ISSUE_CODES.ASSET_AMBIGUOUS_ENTRY,
917
+ `${filename} holds ${others.length + 1} models; loaded "${path}"`,
918
+ { loaded: path, alternatives: others.slice( 0, 50 ) },
919
+ ISSUE_SEVERITY.WARNING
920
+ );
868
921
 
869
922
  }
870
923
 
871
- throw new Error( 'No supported model files found in the ZIP archive' );
924
+ console.log( `Loading model file from ${filename}: ${path}` );
925
+ return await this.loadModelFromZipEntry( zip[ path ], path, path.split( '.' ).pop().toLowerCase(), zip, filename );
872
926
 
873
927
  }
874
928
 
@@ -883,11 +937,11 @@ export class ArchiveImporter {
883
937
  const root = ArchiveImporter._pickUSDRootLayer( layers );
884
938
  console.log( `Loading USD scene from ZIP: ${root} (${layers.length} layers)` );
885
939
 
886
- const packed = { [ root ]: [ zip[ root ], { level: 0 } ] };
940
+ const packed = { [ root ]: [ await bytesOf( zip[ root ] ), { level: 0 } ] };
887
941
  for ( const name of Object.keys( zip ) ) {
888
942
 
889
943
  if ( name === root ) continue;
890
- if ( USD_LAYER_RE.test( name ) || USD_IMAGE_RE.test( name ) ) packed[ name ] = [ zip[ name ], { level: 0 } ];
944
+ if ( USD_LAYER_RE.test( name ) || USD_IMAGE_RE.test( name ) ) packed[ name ] = [ await bytesOf( zip[ name ] ), { level: 0 } ];
891
945
 
892
946
  }
893
947
 
@@ -914,13 +968,11 @@ export class ArchiveImporter {
914
968
 
915
969
  }
916
970
 
917
- async loadModelFromZipEntry( fileContent, filePath, extension, zipContents ) {
971
+ async loadModelFromZipEntry( fileContent, filePath, extension, zipContents, from = 'ZIP' ) {
918
972
 
919
973
  try {
920
974
 
921
- updateLoading( { isLoading: true, status: `Processing ${extension.toUpperCase()} from ZIP...`, progress: 5 } );
922
- const blob = new Blob( [ fileContent.buffer ], { type: 'application/octet-stream' } );
923
- const blobUrl = URL.createObjectURL( blob );
975
+ updateLoading( { isLoading: true, status: `Processing ${extension.toUpperCase()} from ${from}...`, progress: 5 } );
924
976
  let result;
925
977
 
926
978
  switch ( extension ) {
@@ -937,17 +989,16 @@ export class ArchiveImporter {
937
989
 
938
990
  }
939
991
 
940
- URL.revokeObjectURL( blobUrl );
941
992
  this.loader.dispatchEvent( {
942
993
  type: 'load',
943
994
  model: this.loader.targetModel,
944
- filename: `${filePath} (from ZIP)`
995
+ filename: `${filePath} (from ${from})`
945
996
  } );
946
997
  return result;
947
998
 
948
999
  } catch ( error ) {
949
1000
 
950
- console.error( `Error loading ${extension} from ZIP:`, error );
1001
+ console.error( `Error loading ${extension} from ${from}:`, error );
951
1002
  this.loader.dispatchEvent( { type: 'error', message: error.message, filename: filePath } );
952
1003
  throw error;
953
1004
 
@@ -959,7 +1010,7 @@ export class ArchiveImporter {
959
1010
 
960
1011
  if ( extension === 'gltf' ) {
961
1012
 
962
- const gltfContent = strFromU8( fileContent );
1013
+ const gltfContent = await textOf( fileContent );
963
1014
  const manager = new LoadingManager();
964
1015
  const gltfDir = filePath.split( '/' ).slice( 0, - 1 ).join( '/' );
965
1016
 
@@ -969,32 +1020,13 @@ export class ArchiveImporter {
969
1020
 
970
1021
  try {
971
1022
 
972
- return await new Promise( ( resolve, reject ) => {
973
-
974
- loader.parse( gltfContent, '',
975
- gltf => {
976
-
977
- try {
978
-
979
- this.loader._throwDeferred();
980
-
981
- } catch ( error ) {
982
-
983
- reject( error );
984
- return;
985
-
986
- }
987
-
988
- this.loader.releaseTargetModel();
989
- this.loader.targetModel = gltf.scene;
990
- this.loader.sceneMetadata = extractSceneMetadata( gltf );
991
- this.loader.onModelLoad( this.loader.targetModel ).then( () => resolve( gltf ) );
992
-
993
- },
994
- error => reject( error )
995
- );
996
-
997
- } );
1023
+ const gltf = await withHostWorker( () => new Promise( ( resolve, reject ) => loader.parse( gltfContent, '', resolve, reject ) ) );
1024
+ this.loader._throwDeferred();
1025
+ this.loader.releaseTargetModel();
1026
+ this.loader.targetModel = gltf.scene;
1027
+ this.loader.sceneMetadata = extractSceneMetadata( gltf );
1028
+ await this.loader.onModelLoad( this.loader.targetModel );
1029
+ return gltf;
998
1030
 
999
1031
  } finally {
1000
1032
 
@@ -1004,7 +1036,7 @@ export class ArchiveImporter {
1004
1036
 
1005
1037
  } else {
1006
1038
 
1007
- return await this.loader.loadGLBFromArrayBuffer( fileContent.buffer, filePath );
1039
+ return await this.loader.loadGLBFromArrayBuffer( await bufferOf( fileContent ), filePath );
1008
1040
 
1009
1041
  }
1010
1042
 
@@ -1012,7 +1044,7 @@ export class ArchiveImporter {
1012
1044
 
1013
1045
  async handleObjFromZip( fileContent, filePath, zipContents ) {
1014
1046
 
1015
- const objContent = strFromU8( fileContent );
1047
+ const objContent = await textOf( fileContent );
1016
1048
  const mtlMatch = objContent.match( /mtllib\s+([^\s]+)/ );
1017
1049
  let materials = null;
1018
1050
 
@@ -1100,7 +1132,7 @@ export class ArchiveImporter {
1100
1132
  if ( zipContents[ path ] ) {
1101
1133
 
1102
1134
  const { MTLLoader } = await import( 'three/examples/jsm/loaders/MTLLoader.js' );
1103
- const mtlContent = strFromU8( zipContents[ path ] );
1135
+ const mtlContent = await textOf( zipContents[ path ] );
1104
1136
  const manager = new LoadingManager();
1105
1137
  manager.setURLModifier( url => this.resolveZipResource( url, objDir, zipContents ) );
1106
1138
  const mtlLoader = new MTLLoader( manager );
@@ -1118,25 +1150,30 @@ export class ArchiveImporter {
1118
1150
 
1119
1151
  resolveZipResource( url, baseDir, zipContents ) {
1120
1152
 
1121
- const normalizedUrl = url.replace( /^\.\/|^\//, '' );
1122
- const possiblePaths = [
1123
- normalizedUrl,
1124
- `${baseDir}/${normalizedUrl}`,
1125
- normalizedUrl.split( '/' ).pop()
1126
- ];
1153
+ if ( /^(data|blob|https?):/i.test( url ) ) return url;
1127
1154
 
1128
- for ( const path of possiblePaths ) {
1155
+ const raw = url.replace( /^\.\/|^\//, '' );
1156
+ let decoded = raw;
1157
+ try {
1129
1158
 
1130
- if ( zipContents[ path ] ) {
1159
+ decoded = decodeURIComponent( raw );
1131
1160
 
1132
- const fileBlob = new Blob( [ zipContents[ path ].buffer ], { type: 'application/octet-stream' } );
1133
- return URL.createObjectURL( fileBlob );
1161
+ } catch {
1134
1162
 
1135
- }
1163
+ // a literal '%'
1164
+
1165
+ }
1166
+
1167
+ for ( const ref of new Set( [ raw, decoded ] ) ) {
1168
+
1169
+ const name = ref.split( '/' ).pop();
1170
+ const path = [ joinPath( baseDir, ref ), ref, name ].find( p => zipContents[ p ] )
1171
+ ?? ( name ? Object.keys( zipContents ).find( p => p.endsWith( `/${name}` ) ) : undefined );
1172
+ if ( path ) return URL.createObjectURL( asBlob( zipContents[ path ] ) );
1136
1173
 
1137
1174
  }
1138
1175
 
1139
- console.warn( `Resource not found in ZIP: ${url}` );
1176
+ console.warn( `Resource not found in the archive: ${url}` );
1140
1177
  return url;
1141
1178
 
1142
1179
  }
@@ -1151,13 +1188,13 @@ export class ArchiveImporter {
1151
1188
  const mtlDir = mtlFile.path.split( '/' ).slice( 0, - 1 ).join( '/' );
1152
1189
 
1153
1190
  manager.setURLModifier( url => this.resolveTextureInZip( url, objDir, mtlDir, mtlFile, zip, createdUrls ) );
1154
- const mtlContent = this.prepareFixedMtlContent( mtlFile );
1191
+ const mtlContent = await this.prepareFixedMtlContent( mtlFile );
1155
1192
  const materials = new MTLLoader( manager ).parse( mtlContent, mtlDir );
1156
1193
  await this.preloadMtlTextures( materials );
1157
1194
 
1158
1195
  const objLoader = new OBJLoader( manager );
1159
1196
  objLoader.setMaterials( materials );
1160
- const objContent = strFromU8( objFile.content );
1197
+ const objContent = await textOf( objFile.content );
1161
1198
  const object = objLoader.parse( objContent );
1162
1199
 
1163
1200
  this.loader.releaseTargetModel();
@@ -1175,9 +1212,9 @@ export class ArchiveImporter {
1175
1212
 
1176
1213
  }
1177
1214
 
1178
- prepareFixedMtlContent( mtlFile ) {
1215
+ async prepareFixedMtlContent( mtlFile ) {
1179
1216
 
1180
- const mtlContent = strFromU8( mtlFile.content );
1217
+ const mtlContent = await textOf( mtlFile.content );
1181
1218
  return mtlContent
1182
1219
  .replace( new RegExp( `${mtlFile.path.split( '/' ).pop()}\\s+`, 'g' ), ' ' )
1183
1220
  .replace( /([a-zA-Z_]+)([\\/])/g, '$1 $2' );
@@ -1210,8 +1247,7 @@ export class ArchiveImporter {
1210
1247
 
1211
1248
  if ( zip[ location ] ) {
1212
1249
 
1213
- const blob = new Blob( [ zip[ location ].buffer ], { type: 'application/octet-stream' } );
1214
- const blobUrl = URL.createObjectURL( blob );
1250
+ const blobUrl = URL.createObjectURL( asBlob( zip[ location ] ) );
1215
1251
  createdUrls.push( blobUrl );
1216
1252
  return blobUrl;
1217
1253
 
@@ -1231,8 +1267,7 @@ export class ArchiveImporter {
1231
1267
 
1232
1268
  if ( zipPath.endsWith( textureFilename ) ) {
1233
1269
 
1234
- const blob = new Blob( [ zip[ zipPath ].buffer ], { type: 'application/octet-stream' } );
1235
- const blobUrl = URL.createObjectURL( blob );
1270
+ const blobUrl = URL.createObjectURL( asBlob( zip[ zipPath ] ) );
1236
1271
  createdUrls.push( blobUrl );
1237
1272
  return blobUrl;
1238
1273
 
@@ -1247,8 +1282,7 @@ export class ArchiveImporter {
1247
1282
  const zipFilename = zipPath.split( '/' ).pop();
1248
1283
  if ( zipFilename.includes( textureFilename ) || textureFilename.includes( zipFilename ) ) {
1249
1284
 
1250
- const blob = new Blob( [ zip[ zipPath ].buffer ], { type: 'application/octet-stream' } );
1251
- const blobUrl = URL.createObjectURL( blob );
1285
+ const blobUrl = URL.createObjectURL( asBlob( zip[ zipPath ] ) );
1252
1286
  createdUrls.push( blobUrl );
1253
1287
  return blobUrl;
1254
1288