rayzee 11.8.0 → 11.9.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 (53) hide show
  1. package/README.md +18 -6
  2. package/dist/{ArchiveImporter-_sJm5uaK.js → ArchiveImporter-BJxszU_k.js} +1549 -1232
  3. package/dist/{BidirectionalIntegrator-Dzt4Z97x.js → BidirectionalIntegrator-DWg66vAd.js} +240 -240
  4. package/dist/{BufferLayout-xPRH9jYD.js → BufferLayout-Bzgccg1o.js} +1 -0
  5. package/dist/{ColorManagement-30MfpHwQ.js → ColorManagement-B_HjAiy2.js} +1 -1
  6. package/dist/{EngineIssues-xljoXM0E.js → EngineIssues-CfJiBaNj.js} +1 -0
  7. package/dist/{NeuralRetouch-BKQCsmEs.js → NeuralRetouch-BD9E-Yht.js} +1 -1
  8. package/dist/{NeuralSuperRes-C4ymq68e.js → NeuralSuperRes-C8Mf3tJX.js} +1 -1
  9. package/dist/{PathTracer-C_to_i40.js → PathTracer-DeE9UJ1n.js} +3433 -3363
  10. package/dist/{PhysicalSky-bMHbm_7H.js → PhysicalSky-cKy75-oR.js} +45 -45
  11. package/dist/{RayzeeRenderer-B_aB6VTJ.js → RayzeeRenderer-CzLyBA3G.js} +13 -8
  12. package/dist/{SceneMetadata-CSmlJW4c.js → SceneMetadata-C17UfOEp.js} +1 -1
  13. package/dist/{SolarLimb-bZE6y63S.js → SolarLimb-DRYy8tln.js} +184 -172
  14. package/dist/addons/archives.es.js +1 -1
  15. package/dist/addons/bidirectional.es.js +1 -1
  16. package/dist/addons/color.es.js +1 -1
  17. package/dist/addons/physical-sky.es.js +1 -1
  18. package/dist/addons/storage.es.js +1 -1
  19. package/dist/{openStorage-B085D9FR.js → openStorage-CdyVjmps.js} +1 -1
  20. package/dist/rayzee-core.es.js +7 -7
  21. package/dist/rayzee.es.js +567 -559
  22. package/dist/rayzee.umd.js +106 -101
  23. package/dist/{sceneCachePolicy-D_uBSbQF.js → sceneCachePolicy-ywbGKHD3.js} +9 -6
  24. package/package.json +1 -1
  25. package/src/EngineDefaults.js +1 -0
  26. package/src/EngineIssues.js +1 -0
  27. package/src/Passes/OIDNTemporalHistory.js +1 -1
  28. package/src/Processor/ArchiveImporter.js +83 -21
  29. package/src/Processor/AssetLoader.js +7 -0
  30. package/src/Processor/BufferLayout.js +3 -3
  31. package/src/Processor/GeometryExtractor.js +12 -4
  32. package/src/Processor/MaterialPacking.js +2 -2
  33. package/src/Processor/PBRT/PBRTMaterials.js +47 -12
  34. package/src/Processor/PBRT/PBRTParser.js +144 -22
  35. package/src/Processor/PBRT/PBRTSceneBuilder.js +287 -67
  36. package/src/Processor/PBRT/PBRTTextureBake.js +191 -9
  37. package/src/Processor/PBRT/index.js +19 -4
  38. package/src/Processor/SceneProcessor.js +42 -26
  39. package/src/RayzeeRenderer.js +4 -2
  40. package/src/Stages/DenoiserSettings.js +0 -4
  41. package/src/Stages/NRD.js +53 -24
  42. package/src/Stages/NormalDepth.js +5 -3
  43. package/src/Stages/PathTracer.js +2 -0
  44. package/src/Stages/PathTracerStage.js +5 -1
  45. package/src/Storage/SceneGraphCodec.js +6 -3
  46. package/src/TSL/BVHTraversal.js +33 -20
  47. package/src/TSL/Common.js +25 -8
  48. package/src/TSL/Displacement.js +66 -113
  49. package/src/TSL/LightsDirect.js +33 -11
  50. package/src/TSL/SceneResources.js +8 -0
  51. package/src/TSL/ShadeKernel.js +2 -2
  52. package/src/TSL/Struct.js +1 -0
  53. package/src/managers/MaterialDataManager.js +11 -0
@@ -5,10 +5,15 @@
5
5
  * A layer is an image as pbrt evaluates it: linear values, top row first, `channels` 1 (a float texture reads
6
6
  * an image's alpha where it has one, else the mean of its colour) or 3, already scaled and inverted, and the uv
7
7
  * mapping pbrt applies before the lookup (st = scale · uv + delta, image row from the top = 1 − t).
8
+ *
9
+ * pbrt's position mappings (`planar`, `spherical`, `cylindrical`) read no uv: their (s, t) becomes a uv set generated per
10
+ * shape (`generateUV`), and a layer in one carries it as `uv` (`{ type, key, … }`; null for the mesh's own uv).
8
11
  */
9
12
 
10
13
  const MAX_BAKE_SIZE = 4096;
11
14
 
15
+ export const uvKey = ( uv ) => uv?.key ?? '';
16
+
12
17
  export const srgbToLinear = ( v ) => ( v <= 0.04045 ? v / 12.92 : ( ( v + 0.055 ) / 1.055 ) ** 2.4 );
13
18
  export const linearToSRGB = ( v ) => ( v <= 0.0031308 ? v * 12.92 : 1.055 * v ** ( 1 / 2.4 ) - 0.055 );
14
19
 
@@ -20,7 +25,7 @@ export const linearToSRGB = ( v ) => ( v <= 0.0031308 ? v * 12.92 : 1.055 * v **
20
25
  * @param {(v: number) => number} [options.decode] - 8-bit value / 255 → linear; omitted for float data
21
26
  * @param {number} [options.scale=1]
22
27
  * @param {boolean} [options.invert=false]
23
- * @param {{ su: number, sv: number, du: number, dv: number }} [options.mapping]
28
+ * @param {{ su: number, sv: number, du: number, dv: number, uv?: object }} [options.mapping]
24
29
  */
25
30
  export function makeLayer( pixels, { channels, decode = null, scale = 1, invert = false, mapping = null } ) {
26
31
 
@@ -29,7 +34,9 @@ export function makeLayer( pixels, { channels, decode = null, scale = 1, invert
29
34
  const data = new Float32Array( width * height * channels );
30
35
  const topDown = pixels.topDown !== false;
31
36
  // pbrt's encoding applies to every channel of an 8-bit image, alpha included.
32
- const read = ( i ) => ( decode ? decode( pixels.data[ i ] / 255 ) : pixels.data[ i ] );
37
+ const bytes = decode && ( pixels.data instanceof Uint8Array || pixels.data instanceof Uint8ClampedArray );
38
+ const lut = bytes ? Float64Array.from( { length: 256 }, ( _, v ) => decode( v / 255 ) ) : null;
39
+ const read = lut ? ( i ) => lut[ pixels.data[ i ] ] : ( i ) => ( decode ? decode( pixels.data[ i ] / 255 ) : pixels.data[ i ] );
33
40
  const fromAlpha = channels === 1 && stride === 4 && hasAlpha( pixels );
34
41
  const mean = channels === 1 && stride >= 3 && ! fromAlpha;
35
42
 
@@ -53,7 +60,7 @@ export function makeLayer( pixels, { channels, decode = null, scale = 1, invert
53
60
 
54
61
  }
55
62
 
56
- return { width, height, channels, data, su: 1, sv: 1, du: 0, dv: 0, ...mapping };
63
+ return { width, height, channels, data, su: 1, sv: 1, du: 0, dv: 0, uv: null, ...mapping };
57
64
 
58
65
  }
59
66
 
@@ -76,6 +83,15 @@ export function layerMean( layer ) {
76
83
 
77
84
  }
78
85
 
86
+ function layerMeanRGB( { data, channels } ) {
87
+
88
+ const sum = [ 0, 0, 0 ];
89
+ for ( let i = 0; i < data.length; i ++ ) sum[ i % channels ] += data[ i ];
90
+ const texels = data.length / channels || 1;
91
+ return [ 0, 1, 2 ].map( ( c ) => sum[ Math.min( c, channels - 1 ) ] / texels );
92
+
93
+ }
94
+
79
95
  // Bilinear, repeating, at pbrt's (s, t).
80
96
  function sample( layer, s, t, out ) {
81
97
 
@@ -102,20 +118,36 @@ function sample( layer, s, t, out ) {
102
118
  /**
103
119
  * Each term is `{ rgb }` (a constant) or `{ layer, tint? }` (an image, times `tint`); `clamp` clamps it to [0, 1]
104
120
  * first, as pbrt clamps a colour texture read as an albedo, before anything mixes it. Evaluates
105
- * `combine( values )` over the texel grid of the largest image among the terms, in that image's uv mapping;
106
- * a term with another mapping is sampled through it. The result is sRGB bytes laid out for a DataTexture
121
+ * `combine( values )` over the texel grid of the largest image among the terms in the first image's uv set, in that
122
+ * image's uv mapping; a term with another mapping is sampled through it, and one in another uv set counts as its mean
123
+ * (`averaged` says how many). The result is sRGB bytes laid out for a DataTexture
107
124
  * (bottom row first), clamped to [0, 1] as pbrt clamps an albedo. Null when every term is a constant.
108
125
  * @param {Array<{ rgb?: number[], layer?: object, tint?: number[] }>} terms
109
126
  * @param {(values: number[][]) => number[]} combine - linear RGB from each term's linear RGB
110
127
  * @param {object} [options]
111
128
  * @param {(values: number[][]) => number} [options.alphaOf] - the alpha channel, linear; opaque without it
112
129
  * @param {boolean} [options.linear=false] - linear bytes, for a data map
113
- * @returns {{ data: Uint8Array, width: number, height: number, mapping: { su: number, sv: number, du: number, dv: number } } | null}
130
+ * @returns {{ data: Uint8Array, width: number, height: number, mapping: { su: number, sv: number, du: number, dv: number, uv: object|null }, averaged: number } | null}
114
131
  */
115
132
  export function bake( terms, combine, { alphaOf = null, linear = false } = {} ) {
116
133
 
117
- let driver = null;
118
- for ( const term of terms ) if ( term.layer && ( ! driver || term.layer.width * term.layer.height > driver.width * driver.height ) ) driver = term.layer;
134
+ const set = uvKey( terms.find( ( term ) => term.layer )?.layer.uv );
135
+ let driver = null, averaged = 0;
136
+ terms = terms.map( ( term ) => {
137
+
138
+ if ( ! term.layer ) return term;
139
+ if ( uvKey( term.layer.uv ) !== set ) {
140
+
141
+ averaged ++;
142
+ const mean = layerMeanRGB( term.layer );
143
+ return { rgb: mean.map( ( v, c ) => v * ( term.tint?.[ c ] ?? 1 ) ), clamp: term.clamp };
144
+
145
+ }
146
+
147
+ if ( ! driver || term.layer.width * term.layer.height > driver.width * driver.height ) driver = term.layer;
148
+ return term;
149
+
150
+ } );
119
151
  if ( ! driver ) return null;
120
152
 
121
153
  const width = Math.min( driver.width, MAX_BAKE_SIZE ), height = Math.min( driver.height, MAX_BAKE_SIZE );
@@ -166,7 +198,7 @@ export function bake( terms, combine, { alphaOf = null, linear = false } = {} )
166
198
 
167
199
  }
168
200
 
169
- return { data: out, width, height, mapping: { su: driver.su, sv: driver.sv, du: driver.du, dv: driver.dv } };
201
+ return { data: out, width, height, mapping: { su: driver.su, sv: driver.sv, du: driver.du, dv: driver.dv, uv: driver.uv ?? null }, averaged };
170
202
 
171
203
  }
172
204
 
@@ -177,3 +209,153 @@ export const mixOf = ( [ a, b, amount ] ) => {
177
209
  return [ a[ 0 ] + ( b[ 0 ] - a[ 0 ] ) * t, a[ 1 ] + ( b[ 1 ] - a[ 1 ] ) * t, a[ 2 ] + ( b[ 2 ] - a[ 2 ] ) * t ];
178
210
 
179
211
  };
212
+
213
+ // The coordinate that goes once around the axis: spherical φ is t, cylindrical φ is s.
214
+ const WRAPPED = { spherical: 1, cylindrical: 0 };
215
+
216
+ /** pbrt-v4's (s, t) of a point in texture space (textures.h: PlanarMapping, SphericalMapping, CylindricalMapping). */
217
+ export function mapPoint( mapping, x, y, z, out ) {
218
+
219
+ if ( mapping.type === 'planar' ) {
220
+
221
+ const { vs, vt } = mapping;
222
+ out[ 0 ] = mapping.ds + x * vs[ 0 ] + y * vs[ 1 ] + z * vs[ 2 ];
223
+ out[ 1 ] = mapping.dt + x * vt[ 0 ] + y * vt[ 1 ] + z * vt[ 2 ];
224
+
225
+ } else if ( mapping.type === 'spherical' ) {
226
+
227
+ const length = Math.hypot( x, y, z ) || 1;
228
+ const phi = Math.atan2( y, x );
229
+ out[ 0 ] = Math.acos( Math.min( 1, Math.max( - 1, z / length ) ) ) / Math.PI;
230
+ out[ 1 ] = ( phi < 0 ? phi + 2 * Math.PI : phi ) / ( 2 * Math.PI );
231
+
232
+ } else {
233
+
234
+ out[ 0 ] = ( Math.PI + Math.atan2( y, x ) ) / ( 2 * Math.PI );
235
+ out[ 1 ] = z;
236
+
237
+ }
238
+
239
+ return out;
240
+
241
+ }
242
+
243
+ /**
244
+ * pbrt's (s, t) at each vertex as a uv set, `toTexture` (column-major) taking `positions` to texture space. A triangle
245
+ * across the seam gets copies of its vertices a turn on (`extra` names their sources, `index` the rewritten triangles,
246
+ * null when none); a vertex on the axis takes its triangle's mean angle.
247
+ * @param {Float32Array} positions
248
+ * @param {ArrayLike<number>|null} index
249
+ * @param {number[]} toTexture
250
+ * @param {{ type: string }} mapping
251
+ * @returns {{ uv: Float32Array, index: Uint32Array|null, extra: Uint32Array }}
252
+ */
253
+ export function generateUV( positions, index, toTexture, mapping ) {
254
+
255
+ const count = positions.length / 3;
256
+ const m = toTexture;
257
+ const k = WRAPPED[ mapping.type ];
258
+ const st = new Float64Array( count * 2 );
259
+ const radius = k === undefined ? null : new Float64Array( count );
260
+ const out = [ 0, 0 ];
261
+ let maxRadius = 0;
262
+
263
+ for ( let i = 0; i < count; i ++ ) {
264
+
265
+ const px = positions[ i * 3 ], py = positions[ i * 3 + 1 ], pz = positions[ i * 3 + 2 ];
266
+ const x = m[ 0 ] * px + m[ 4 ] * py + m[ 8 ] * pz + m[ 12 ];
267
+ const y = m[ 1 ] * px + m[ 5 ] * py + m[ 9 ] * pz + m[ 13 ];
268
+ const z = m[ 2 ] * px + m[ 6 ] * py + m[ 10 ] * pz + m[ 14 ];
269
+ mapPoint( mapping, x, y, z, out );
270
+ st[ i * 2 ] = out[ 0 ];
271
+ st[ i * 2 + 1 ] = out[ 1 ];
272
+ if ( ! radius ) continue;
273
+ radius[ i ] = Math.hypot( x, y );
274
+ if ( radius[ i ] > maxRadius ) maxRadius = radius[ i ];
275
+
276
+ }
277
+
278
+ const extra = [];
279
+ let triangles = index;
280
+
281
+ if ( radius ) {
282
+
283
+ const onAxis = maxRadius * 1e-6;
284
+ const copies = new Map();
285
+ const corner = [ 0, 0, 0 ], value = [ 0, 0, 0 ];
286
+ const triangleCount = ( index ? index.length : count ) / 3;
287
+
288
+ for ( let t = 0; t < triangleCount; t ++ ) {
289
+
290
+ let lo = Infinity, hi = - Infinity, sum = 0, around = 0;
291
+ for ( let c = 0; c < 3; c ++ ) {
292
+
293
+ const v = corner[ c ] = index ? index[ t * 3 + c ] : t * 3 + c;
294
+ if ( radius[ v ] <= onAxis ) continue;
295
+ lo = Math.min( lo, st[ v * 2 + k ] );
296
+ hi = Math.max( hi, st[ v * 2 + k ] );
297
+
298
+ }
299
+
300
+ const across = hi - lo > 0.5;
301
+ for ( let c = 0; c < 3; c ++ ) {
302
+
303
+ const w = st[ corner[ c ] * 2 + k ];
304
+ if ( radius[ corner[ c ] ] <= onAxis ) value[ c ] = NaN;
305
+ else {
306
+
307
+ value[ c ] = across && w < 0.5 ? w + 1 : w;
308
+ sum += value[ c ];
309
+ around ++;
310
+
311
+ }
312
+
313
+ }
314
+
315
+ for ( let c = 0; c < 3; c ++ ) {
316
+
317
+ const v = corner[ c ];
318
+ const w = Number.isNaN( value[ c ] ) ? ( around > 0 ? sum / around : st[ v * 2 + k ] ) : value[ c ];
319
+ if ( w === st[ v * 2 + k ] ) continue;
320
+
321
+ if ( ! index ) {
322
+
323
+ st[ v * 2 + k ] = w;
324
+ continue;
325
+
326
+ }
327
+
328
+ const key = `${v}|${w}`;
329
+ let copy = copies.get( key );
330
+ if ( copy === undefined ) {
331
+
332
+ copies.set( key, copy = count + extra.length / 2 );
333
+ extra.push( v, w );
334
+
335
+ }
336
+
337
+ if ( triangles === index ) triangles = Uint32Array.from( index );
338
+ triangles[ t * 3 + c ] = copy;
339
+
340
+ }
341
+
342
+ }
343
+
344
+ }
345
+
346
+ const added = extra.length / 2;
347
+ const uv = new Float32Array( ( count + added ) * 2 );
348
+ for ( let i = 0; i < count * 2; i ++ ) uv[ i ] = st[ i ];
349
+ const sources = new Uint32Array( added );
350
+ for ( let i = 0; i < added; i ++ ) {
351
+
352
+ const v = sources[ i ] = extra[ i * 2 ];
353
+ uv[ ( count + i ) * 2 ] = st[ v * 2 ];
354
+ uv[ ( count + i ) * 2 + 1 ] = st[ v * 2 + 1 ];
355
+ uv[ ( count + i ) * 2 + k ] = extra[ i * 2 + 1 ];
356
+
357
+ }
358
+
359
+ return { uv, index: triangles === index ? null : triangles, extra: sources };
360
+
361
+ }
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import { freeNow } from './buffers.js';
18
- import { PBRTParser } from './PBRTParser.js';
18
+ import { PBRTParser, ParamUsage } from './PBRTParser.js';
19
19
  import { PBRTSceneBuilder } from './PBRTSceneBuilder.js';
20
20
  import { findFrameSequence, FrameSequenceMerger, motionFromShutter, SEQUENCE_FPS } from './PBRTAnimation.js';
21
21
 
@@ -380,7 +380,8 @@ export async function listEntryPathsFrom( vfs ) {
380
380
  * @param {boolean} [args.convertHandedness=true]
381
381
  * @param {boolean} [args.animation=true] - load a frame sequence (`frame25.pbrt`, `frame35.pbrt`, …)
382
382
  * as one animated scene instead of its first frame alone. Ignored when `entryPath` names a file.
383
- * @returns {Promise<{group, camera, environment, animations, warnings, entryPath, frames}>}
383
+ * @returns {Promise<{group, camera, environment, animations, warnings, entryPath, frames, parameters}>} - `parameters`: what
384
+ * ParamUsage.finish() reports, the parameters this load ignored or approximated
384
385
  */
385
386
  export async function loadPBRTScene( args ) {
386
387
 
@@ -401,6 +402,7 @@ export async function loadPBRTScene( args ) {
401
402
  }
402
403
 
403
404
  const entryPath = requested || candidates[ 0 ];
405
+ const usage = new ParamUsage();
404
406
 
405
407
  // Parse (with Include resolution). Bytes go straight to the lexer — a scene file can
406
408
  // be larger than the longest string JavaScript will build.
@@ -415,7 +417,8 @@ export async function loadPBRTScene( args ) {
415
417
  // scene file at once — the difference between 5.7 GB resident and a few MB.
416
418
  releaseInclude: ( include, currentDir, bytes ) => vfs.releasePath( include, currentDir, bytes ),
417
419
  maxPlacements: args.maxPlacements,
418
- instanceIncludes: args.instanceIncludes
420
+ instanceIncludes: args.instanceIncludes,
421
+ usage
419
422
  } );
420
423
 
421
424
  const ir = await parser.parse( bytes, path.includes( '/' ) ? path.slice( 0, path.lastIndexOf( '/' ) ) : '' );
@@ -490,6 +493,7 @@ export async function loadPBRTScene( args ) {
490
493
  curveSteps: args.curveSteps,
491
494
  curveSides: args.curveSides,
492
495
  curveTolerance: args.curveTolerance,
496
+ usage,
493
497
  resolvePLY: async ( filename ) => {
494
498
 
495
499
  const rec = vfs.findRecord( filename );
@@ -515,9 +519,20 @@ export async function loadPBRTScene( args ) {
515
519
  } );
516
520
 
517
521
  const buildStart = performance.now();
518
- const result = await builder.build( ir );
522
+ let result, parameters;
523
+ try {
524
+
525
+ result = await builder.build( ir );
526
+
527
+ } finally {
528
+
529
+ parameters = usage.finish();
530
+
531
+ }
532
+
519
533
  return {
520
534
  ...result,
535
+ parameters,
521
536
  entryPath: sequence ? sequence[ 0 ].path : entryPath,
522
537
  frames: sequence ? sequence.map( f => f.path ) : null,
523
538
  candidates,
@@ -63,6 +63,14 @@ const GEOMETRY_QUEUE_BYTES = 256 * 1024 * 1024;
63
63
 
64
64
  const log = createLogger( 'scene' );
65
65
 
66
+ // The per-type texture lists an extraction hands over (GeometryExtractor.getExtractedData).
67
+ const TEXTURE_LISTS = Object.freeze( [
68
+ 'maps', 'normalMaps', 'bumpMaps', 'roughnessMaps', 'metalnessMaps', 'emissiveMaps', 'displacementMaps',
69
+ 'anisotropyMaps', 'transmissionMaps', 'clearcoatMaps', 'clearcoatRoughnessMaps', 'sheenColorMaps',
70
+ 'sheenRoughnessMaps', 'iridescenceMaps', 'iridescenceThicknessMaps', 'specularIntensityMaps', 'specularColorMaps',
71
+ 'diffuseTransmissionMaps', 'diffuseTransmissionColorMaps',
72
+ ] );
73
+
66
74
  // A BLAS's nodes: one array, or a split mesh's parts in order — never one array that large.
67
75
  const nodeParts = nodes => ( Array.isArray( nodes ) ? nodes : [ nodes ] );
68
76
  const nodeBytes = nodes => nodeParts( nodes ).reduce( ( sum, part ) => sum + part.byteLength, 0 );
@@ -913,25 +921,7 @@ export class SceneProcessor {
913
921
  this.materialTriangleCounts = extractedData.materialTriangleCounts; // Per-material tri count for sort-bin remap
914
922
  this.meshes = extractedData.meshes;
915
923
  this.meshTriangleRanges = extractedData.meshTriangleRanges; // Per-mesh { start, count } for TLAS/BLAS
916
- this.maps = extractedData.maps;
917
- this.normalMaps = extractedData.normalMaps;
918
- this.bumpMaps = extractedData.bumpMaps;
919
- this.roughnessMaps = extractedData.roughnessMaps;
920
- this.metalnessMaps = extractedData.metalnessMaps;
921
- this.emissiveMaps = extractedData.emissiveMaps;
922
- this.displacementMaps = extractedData.displacementMaps;
923
- this.anisotropyMaps = extractedData.anisotropyMaps;
924
- this.transmissionMaps = extractedData.transmissionMaps;
925
- this.clearcoatMaps = extractedData.clearcoatMaps;
926
- this.clearcoatRoughnessMaps = extractedData.clearcoatRoughnessMaps;
927
- this.sheenColorMaps = extractedData.sheenColorMaps;
928
- this.sheenRoughnessMaps = extractedData.sheenRoughnessMaps;
929
- this.iridescenceMaps = extractedData.iridescenceMaps;
930
- this.iridescenceThicknessMaps = extractedData.iridescenceThicknessMaps;
931
- this.specularIntensityMaps = extractedData.specularIntensityMaps;
932
- this.specularColorMaps = extractedData.specularColorMaps;
933
- this.diffuseTransmissionMaps = extractedData.diffuseTransmissionMaps ?? [];
934
- this.diffuseTransmissionColorMaps = extractedData.diffuseTransmissionColorMaps ?? [];
924
+ this._adoptTextureLists( extractedData );
935
925
  this.directionalLights = extractedData.directionalLights;
936
926
  this.cameras = extractedData.cameras;
937
927
 
@@ -2453,13 +2443,7 @@ export class SceneProcessor {
2453
2443
  // Update material arrays (but keep existing triangle data)
2454
2444
  this.materials = extractedData.materials;
2455
2445
  this.meshes = extractedData.meshes; // Update mesh data
2456
- this.maps = extractedData.maps;
2457
- this.normalMaps = extractedData.normalMaps;
2458
- this.bumpMaps = extractedData.bumpMaps;
2459
- this.roughnessMaps = extractedData.roughnessMaps;
2460
- this.metalnessMaps = extractedData.metalnessMaps;
2461
- this.emissiveMaps = extractedData.emissiveMaps;
2462
- this.displacementMaps = extractedData.displacementMaps;
2446
+ this._adoptTextureLists( extractedData );
2463
2447
 
2464
2448
  // Bucket textures, remap material indices, regenerate raw material data, and
2465
2449
  // build the consolidated bucket arrays — same path as the initial build.
@@ -2487,6 +2471,13 @@ export class SceneProcessor {
2487
2471
 
2488
2472
  }
2489
2473
 
2474
+ /** @private The extraction's material indices point into these lists, so every one is taken from it. */
2475
+ _adoptTextureLists( extractedData ) {
2476
+
2477
+ for ( const name of TEXTURE_LISTS ) this[ name ] = extractedData[ name ] ?? [];
2478
+
2479
+ }
2480
+
2490
2481
  /**
2491
2482
  * Dispose only material-related textures
2492
2483
  * @private
@@ -2726,6 +2717,7 @@ export class SceneProcessor {
2726
2717
  const FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
2727
2718
  const triRanges = [];
2728
2719
  const blasRanges = [];
2720
+ const refit = [];
2729
2721
  let owners = 0;
2730
2722
  for ( let i = 0; i < table.count; i ++ ) {
2731
2723
 
@@ -2736,6 +2728,7 @@ export class SceneProcessor {
2736
2728
  if ( ! p ) continue;
2737
2729
  this._updateMeshTrianglePositions( i, p );
2738
2730
  blasRanges.push( table.blasOffsetOf( i ), table.blasNodeCountOf( i ) );
2731
+ refit.push( i );
2739
2732
 
2740
2733
  // Smooth normals overwrite the face normals just computed, so they follow per mesh.
2741
2734
  if ( normalsFor ) {
@@ -2764,6 +2757,12 @@ export class SceneProcessor {
2764
2757
  const msg = e.data;
2765
2758
  if ( msg.type === 'refitComplete' ) {
2766
2759
 
2760
+ // Later moves and main-thread refits box placements by these.
2761
+ for ( const i of refit ) table.recomputeAABB( i, this.bvh, this.triangles );
2762
+ table.computeGroupAABBs();
2763
+ // The worker boxes a group copy by its shown members only.
2764
+ const leaves = this._hiddenGroupLeaves();
2765
+ if ( leaves.length ) this._refitTLAS( leaves );
2767
2766
  resolve( { refitTimeMs: msg.refitTimeMs, triRanges, bvhRanges } );
2768
2767
 
2769
2768
  } else if ( msg.type === 'error' ) {
@@ -2979,6 +2978,22 @@ export class SceneProcessor {
2979
2978
 
2980
2979
  }
2981
2980
 
2981
+ /** The TLAS leaves of every copy of a group with a hidden member. @private */
2982
+ _hiddenGroupLeaves() {
2983
+
2984
+ const table = this.instanceTable;
2985
+ const leaves = [];
2986
+ for ( const g of table.groups ) {
2987
+
2988
+ if ( g.members.every( ( _, j ) => table.visible[ g.starts[ j ] ] ) ) continue;
2989
+ for ( let c = 0; c < g.count; c ++ ) leaves.push( table.leafOfPlacement( g.starts[ 0 ] + c ) );
2990
+
2991
+ }
2992
+
2993
+ return leaves;
2994
+
2995
+ }
2996
+
2982
2997
  /**
2983
2998
  * A grouped mesh's parts are traced as one object, so a part moved alone takes its siblings with it: each sibling
2984
2999
  * is given the first moved member's world transform, and all of them are moved.
@@ -4276,6 +4291,7 @@ export class SceneProcessor {
4276
4291
  // scene's 4), producing a tree of a different size that the swap below rejects —
4277
4292
  // so every rebuild was thrown away.
4278
4293
  maxLeafSize: this.bvhBuilder.maxLeafSize,
4294
+ foldLeaves: this._foldLeaves === true,
4279
4295
  numBins: this.bvhBuilder.numBins,
4280
4296
  maxBins: this.bvhBuilder.maxBins,
4281
4297
  minBins: this.bvhBuilder.minBins,
@@ -1233,7 +1233,7 @@ export class RayzeeRenderer extends EventDispatcher {
1233
1233
  this._modelReplaced();
1234
1234
  // Before the scene's own environment starts loading: a spilling scene's is narrowed as it decodes.
1235
1235
  this._planSpill();
1236
- await this.loadSceneData( { pendingEnvironment: this._beginSceneMetadataEnvironment() } );
1236
+ const built = await this.loadSceneData( { pendingEnvironment: this._beginSceneMetadataEnvironment() } );
1237
1237
  this._maybeSpill();
1238
1238
  this.pipeline?.eventBus.emit( 'pipeline:lightingChanged' );
1239
1239
  // Not held: the first denoise of a new scene lands ~1.1 s after the load (upload and
@@ -1242,6 +1242,7 @@ export class RayzeeRenderer extends EventDispatcher {
1242
1242
  this.reset();
1243
1243
  this.dispatchEvent( eventPayload );
1244
1244
  this._modelAnnounced();
1245
+ if ( built ) this.assetLoader?.commitPendingGraph( this.sceneModel );
1245
1246
 
1246
1247
  } catch ( error ) {
1247
1248
 
@@ -3912,7 +3913,8 @@ export class RayzeeRenderer extends EventDispatcher {
3912
3913
 
3913
3914
  // Drag-drop / file load is a replace: clear any appended models first.
3914
3915
  this._clearAppendedModels();
3915
- await this.loadSceneData( { pendingEnvironment: this._beginSceneMetadataEnvironment() } );
3916
+ const built = await this.loadSceneData( { pendingEnvironment: this._beginSceneMetadataEnvironment() } );
3917
+ if ( built ) this.assetLoader?.commitPendingGraph( event.model );
3916
3918
 
3917
3919
  } else if ( event.texture ) {
3918
3920
 
@@ -4,10 +4,6 @@
4
4
  export const GBUFFER_MISS_DEPTH = 65504.0;
5
5
  export const GBUFFER_MISS_THRESHOLD = 6e4;
6
6
 
7
- // Albedo demodulation safety floor. Demod (`color / safeAlbedo`) and remod (`lighting * safeAlbedo`)
8
- // only round-trip exactly when both sides use the same value.
9
- export const ALBEDO_EPS = 0.01;
10
-
11
7
  // The live view's denoisers, for setDenoiserStrategy.
12
8
  export const DENOISER_STRATEGIES = Object.freeze( [ 'none', 'nrd', 'oidn' ] );
13
9