rayzee 7.7.0 → 7.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 (39) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{BVHSubtreeWorker-D9GImjGj.js → BVHSubtreeWorker-sNzvxn66.js} +2 -2
  3. package/dist/assets/{BVHSubtreeWorker-D9GImjGj.js.map → BVHSubtreeWorker-sNzvxn66.js.map} +1 -1
  4. package/dist/assets/{BVHWorker-CNJ0UBQz.js → BVHWorker-CiVdFrwe.js} +2 -2
  5. package/dist/assets/{BVHWorker-CNJ0UBQz.js.map → BVHWorker-CiVdFrwe.js.map} +1 -1
  6. package/dist/rayzee.es.js +2057 -2035
  7. package/dist/rayzee.es.js.map +1 -1
  8. package/dist/rayzee.umd.js +55 -55
  9. package/dist/rayzee.umd.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/EngineDefaults.js +45 -1
  12. package/src/EngineEvents.js +1 -0
  13. package/src/Passes/OIDNDenoiser.js +25 -1
  14. package/src/PathTracerApp.js +36 -0
  15. package/src/Processor/AssetLoader.js +3 -0
  16. package/src/Processor/GeometryExtractor.js +35 -9
  17. package/src/Processor/PackedRayBuffer.js +15 -1
  18. package/src/Processor/QueueManager.js +13 -1
  19. package/src/Processor/SceneProcessor.js +176 -113
  20. package/src/Processor/ShaderBuilder.js +6 -24
  21. package/src/Processor/TextureCreator.js +16 -32
  22. package/src/Processor/Workers/BVHWorker.js +6 -1
  23. package/src/Stages/EdgeFilter.js +4 -3
  24. package/src/Stages/MotionVector.js +4 -4
  25. package/src/Stages/NormalDepth.js +20 -30
  26. package/src/Stages/PathTracer.js +20 -40
  27. package/src/TSL/DebugKernel.js +0 -2
  28. package/src/TSL/Debugger.js +1 -8
  29. package/src/TSL/Displacement.js +10 -10
  30. package/src/TSL/GenerateKernel.js +5 -1
  31. package/src/TSL/LightsDirect.js +8 -9
  32. package/src/TSL/LightsIndirect.js +15 -9
  33. package/src/TSL/LightsSampling.js +9 -19
  34. package/src/TSL/Random.js +11 -1
  35. package/src/TSL/ShadeKernel.js +1 -6
  36. package/src/TSL/TextureSampling.js +155 -34
  37. package/src/managers/EnvironmentManager.js +11 -0
  38. package/src/managers/MaterialDataManager.js +71 -39
  39. package/src/managers/RenderTargetManager.js +0 -522
@@ -74,9 +74,6 @@ export function buildShadeKernel( params ) {
74
74
  rayBufferRW, rngBufferRW, hitBufferRO, gBufferRW,
75
75
  counters,
76
76
  activeIndicesRO,
77
- albedoMaps, normalMaps, bumpMaps,
78
- metalnessMaps, roughnessMaps, emissiveMaps,
79
- displacementMaps,
80
77
  envTexture, environmentIntensity, envMatrix,
81
78
  enableEnvironmentLight, useEnvMapIS,
82
79
  groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight, groundProjectionLevel,
@@ -533,7 +530,7 @@ export function buildShadeKernel( params ) {
533
530
  boxTests: int( 0 ), triTests: int( 0 ),
534
531
  } );
535
532
  const dispResult = DisplacementResult.wrap( refineDisplacedIntersection(
536
- dispRay, dispHit, triangleBuffer, displacementMaps, material, bounceIndex,
533
+ dispRay, dispHit, triangleBuffer, material, bounceIndex,
537
534
  ) ).toVar();
538
535
  samplingUV.assign( dispResult.uv );
539
536
  displacedNormal.assign( dispResult.normal );
@@ -543,8 +540,6 @@ export function buildShadeKernel( params ) {
543
540
  );
544
541
 
545
542
  const matSamples = MaterialSamples.wrap( sampleAllMaterialTextures(
546
- albedoMaps, normalMaps, bumpMaps,
547
- metalnessMaps, roughnessMaps, emissiveMaps,
548
543
  material, samplingUV, N,
549
544
  ) ).toVar();
550
545
 
@@ -1,9 +1,129 @@
1
1
  import { Fn, wgslFn, float, vec2, vec3, vec4, int, If, normalize, cross, abs, mix, clamp, texture, textureSize } from 'three/tsl';
2
+ import { DataArrayTexture, LinearFilter } from 'three';
2
3
 
3
4
  import {
4
5
  UVCache,
5
6
  MaterialSamples,
6
7
  } from './Struct.js';
8
+ import { TEXTURE_CONSTANTS } from '../EngineDefaults.js';
9
+
10
+ // ================================================================================
11
+ // CONSOLIDATED SIZE-BUCKETED MATERIAL TEXTURES
12
+ // ================================================================================
13
+ // Material maps are packed into two colorSpace pools (sRGB: albedo+emissive; linear:
14
+ // normal/bump/roughness/metalness/displacement), each split into MATERIAL_BUCKET_COUNT
15
+ // longest-edge size buckets so a small map no longer pays a large neighbour's footprint.
16
+ // A map's stored index encodes (bucket, layer) as bucket * BUCKET_LAYER_STRIDE + layer.
17
+ //
18
+ // The bucket texture nodes live at module level (same pattern as gobo/IES/shadowAlbedo):
19
+ // each stage sets them via setMaterialBucketTextures() right before building its graph,
20
+ // so each pipeline bakes in its own fresh nodes (avoiding cross-pipeline TextureNode
21
+ // caching, just like the per-type nodes did before).
22
+
23
+ const _STRIDE = TEXTURE_CONSTANTS.BUCKET_LAYER_STRIDE;
24
+
25
+ // Array<MATERIAL_BUCKET_COUNT> of texture nodes (never null — empty buckets get placeholders).
26
+ let _srgbBuckets = null;
27
+ let _linearBuckets = null;
28
+
29
+ /**
30
+ * Set the bucket texture node arrays read by the sampling functions. Call before building
31
+ * any graph that samples material textures (Shade / NormalDepth / Debug kernels).
32
+ * @param {Array} srgb sRGB pool nodes (albedo + emissive)
33
+ * @param {Array} linear linear pool nodes (normal/bump/roughness/metalness/displacement)
34
+ */
35
+ export function setMaterialBucketTextures( srgb, linear ) {
36
+
37
+ _srgbBuckets = srgb;
38
+ _linearBuckets = linear;
39
+
40
+ }
41
+
42
+ // Run `fn(node, layer)` inside a runtime branch that selects the bucket node a packed
43
+ // index points to. Emits one If/ElseIf arm per bucket; only the matching arm executes.
44
+ // Caller must guard packedIndex >= 0.
45
+ const withBucket = ( buckets, packedIndex, fn ) => {
46
+
47
+ const bucket = packedIndex.div( int( _STRIDE ) ).toVar();
48
+ const layer = packedIndex.sub( bucket.mul( int( _STRIDE ) ) ).toVar();
49
+ let chain = If( bucket.equal( int( 0 ) ), () => fn( buckets[ 0 ], layer ) );
50
+ for ( let i = 1; i < buckets.length; i ++ ) {
51
+
52
+ chain = chain.ElseIf( bucket.equal( int( i ) ), () => fn( buckets[ i ], layer ) );
53
+
54
+ }
55
+
56
+ return chain;
57
+
58
+ };
59
+
60
+ // Sample the bucket a packed index points to. Caller must guard packedIndex >= 0.
61
+ export const sampleBucket = ( buckets, packedIndex, uv ) => {
62
+
63
+ const result = vec4( 0.0 ).toVar();
64
+ withBucket( buckets, packedIndex, ( node, layer ) => result.assign( texture( node, uv ).depth( layer ) ) );
65
+ return result;
66
+
67
+ };
68
+
69
+ // Per-axis texel size (1/dims) of the bucket a packed displacement/bump index points to,
70
+ // for finite-difference derivative steps. Caller must guard packedIndex >= 0.
71
+ export const bucketTexelSize = ( buckets, packedIndex ) => {
72
+
73
+ const ts = vec2( 1.0 ).toVar();
74
+ withBucket( buckets, packedIndex, node => ts.assign( vec2( 1.0 ).div( vec2( textureSize( node ) ) ) ) );
75
+ return ts;
76
+
77
+ };
78
+
79
+ // The linear-pool node set (displacement lives here) — for Displacement.js texel sizing.
80
+ export function getLinearBucketTextures() {
81
+
82
+ return _linearBuckets;
83
+
84
+ }
85
+
86
+ // 1×1 white placeholder array so empty-bucket branches always reference a valid node.
87
+ export function makeBucketPlaceholder() {
88
+
89
+ const t = new DataArrayTexture( new Uint8Array( [ 255, 255, 255, 255 ] ), 1, 1, 1 );
90
+ t.minFilter = LinearFilter;
91
+ t.magFilter = LinearFilter;
92
+ t.generateMipmaps = false;
93
+ t.needsUpdate = true;
94
+ return t;
95
+
96
+ }
97
+
98
+ // Build MATERIAL_BUCKET_COUNT texture nodes from a bucket array (DataArrayTexture | null per
99
+ // bucket). Null buckets get a fresh placeholder. Each caller builds its OWN nodes so each
100
+ // pipeline bakes in independent TextureNodes (no cross-pipeline caching).
101
+ export function buildBucketTextureNodes( bucketArrays ) {
102
+
103
+ const K = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT;
104
+ const nodes = [];
105
+ for ( let i = 0; i < K; i ++ ) {
106
+
107
+ const arr = bucketArrays && bucketArrays[ i ];
108
+ nodes.push( texture( arr || makeBucketPlaceholder() ) );
109
+
110
+ }
111
+
112
+ return nodes;
113
+
114
+ }
115
+
116
+ // Update in-place the .value of bucket nodes from a fresh bucket array (model change).
117
+ export function refreshBucketTextureNodes( nodes, bucketArrays ) {
118
+
119
+ if ( ! nodes || ! bucketArrays ) return;
120
+ for ( let i = 0; i < nodes.length; i ++ ) {
121
+
122
+ if ( bucketArrays[ i ] && nodes[ i ] ) nodes[ i ].value = bucketArrays[ i ];
123
+
124
+ }
125
+
126
+ }
7
127
 
8
128
  // ================================================================================
9
129
  // FAST UTILITY FUNCTIONS
@@ -201,14 +321,14 @@ export const computeUVCache = Fn( ( [ baseUV, material ] ) => {
201
321
  // PROCESSING FUNCTIONS
202
322
  // ================================================================================
203
323
 
204
- export const processAlbedo = Fn( ( [ albedoMaps, material, uvCache ] ) => {
324
+ export const processAlbedo = Fn( ( [ material, uvCache ] ) => {
205
325
 
206
326
  const result = material.color.toVar();
207
327
 
208
328
  If( material.albedoMapIndex.greaterThanEqual( int( 0 ) ), () => {
209
329
 
210
- const albedoSample = texture( albedoMaps, uvCache.albedoUV ).depth( int( material.albedoMapIndex ) ).toVar();
211
- // sRGB→linear handled by GPU hardware (texture.colorSpace = SRGBColorSpace → rgba8unorm-srgb format)
330
+ const albedoSample = sampleBucket( _srgbBuckets, material.albedoMapIndex, uvCache.albedoUV ).toVar();
331
+ // sRGB→linear handled by GPU hardware (sRGB bucket arrays carry SRGBColorSpace → rgba8unorm-srgb)
212
332
  result.assign( vec4( material.color.rgb.mul( albedoSample.rgb ), material.color.a.mul( albedoSample.a ) ) );
213
333
 
214
334
  } );
@@ -217,15 +337,15 @@ export const processAlbedo = Fn( ( [ albedoMaps, material, uvCache ] ) => {
217
337
 
218
338
  } );
219
339
 
220
- export const processMetalnessRoughness = Fn( ( [ metalnessMaps, roughnessMaps, material, uvCache ] ) => {
340
+ export const processMetalnessRoughness = Fn( ( [ material, uvCache ] ) => {
221
341
 
222
342
  const metalness = material.metalness.toVar();
223
343
  const roughness = material.roughness.toVar();
224
344
 
225
345
  If( material.metalnessMapIndex.greaterThanEqual( int( 0 ) ).and( material.metalnessMapIndex.equal( material.roughnessMapIndex ) ), () => {
226
346
 
227
- // Same texture for both
228
- const sample = texture( metalnessMaps, uvCache.metalnessUV ).depth( int( material.metalnessMapIndex ) );
347
+ // Same packed index → same bucket layer (e.g. ORM) → sample once.
348
+ const sample = sampleBucket( _linearBuckets, material.metalnessMapIndex, uvCache.metalnessUV );
229
349
  metalness.assign( material.metalness.mul( sample.b ) );
230
350
  roughness.assign( material.roughness.mul( sample.g ) );
231
351
 
@@ -233,14 +353,14 @@ export const processMetalnessRoughness = Fn( ( [ metalnessMaps, roughnessMaps, m
233
353
 
234
354
  If( material.metalnessMapIndex.greaterThanEqual( int( 0 ) ), () => {
235
355
 
236
- const metSample = texture( metalnessMaps, uvCache.metalnessUV ).depth( int( material.metalnessMapIndex ) );
356
+ const metSample = sampleBucket( _linearBuckets, material.metalnessMapIndex, uvCache.metalnessUV );
237
357
  metalness.assign( material.metalness.mul( metSample.b ) );
238
358
 
239
359
  } );
240
360
 
241
361
  If( material.roughnessMapIndex.greaterThanEqual( int( 0 ) ), () => {
242
362
 
243
- const rghSample = texture( roughnessMaps, uvCache.roughnessUV ).depth( int( material.roughnessMapIndex ) );
363
+ const rghSample = sampleBucket( _linearBuckets, material.roughnessMapIndex, uvCache.roughnessUV );
244
364
  roughness.assign( material.roughness.mul( rghSample.g ) );
245
365
 
246
366
  } );
@@ -251,13 +371,13 @@ export const processMetalnessRoughness = Fn( ( [ metalnessMaps, roughnessMaps, m
251
371
 
252
372
  } );
253
373
 
254
- export const processNormal = Fn( ( [ normalMaps, geometryNormal, material, uvCache ] ) => {
374
+ export const processNormal = Fn( ( [ geometryNormal, material, uvCache ] ) => {
255
375
 
256
376
  const result = geometryNormal.toVar();
257
377
 
258
378
  If( material.normalMapIndex.greaterThanEqual( int( 0 ) ), () => {
259
379
 
260
- const normalSample = texture( normalMaps, uvCache.normalUV ).depth( int( material.normalMapIndex ) );
380
+ const normalSample = sampleBucket( _linearBuckets, material.normalMapIndex, uvCache.normalUV );
261
381
  const normalMap = normalSample.xyz.mul( 2.0 ).sub( 1.0 ).toVar();
262
382
  normalMap.x.mulAssign( material.normalScale.x );
263
383
  normalMap.y.assign( normalMap.y.negate().mul( material.normalScale.x ) );
@@ -277,21 +397,25 @@ export const processNormal = Fn( ( [ normalMaps, geometryNormal, material, uvCac
277
397
 
278
398
  } );
279
399
 
280
- export const processBump = Fn( ( [ bumpMaps, currentNormal, material, uvCache ] ) => {
400
+ export const processBump = Fn( ( [ currentNormal, material, uvCache ] ) => {
281
401
 
282
402
  const result = currentNormal.toVar();
283
403
 
284
404
  If( material.bumpMapIndex.greaterThanEqual( int( 0 ) ).and( material.bumpScale.greaterThan( 0.0 ) ), () => {
285
405
 
286
- // Texel size from the actual bump-array dimensions (finite-difference step).
287
- const texelSize = vec2( 1.0 ).div( vec2( textureSize( bumpMaps ) ) ).toVar();
406
+ // Taps + texel size come from the SELECTED bucket node (its real dimensions), so the
407
+ // finite-difference step is correct per bucket done inside one bucket-branch.
408
+ const bumpNormal = vec3( 0.0, 0.0, 1.0 ).toVar();
409
+ withBucket( _linearBuckets, material.bumpMapIndex, ( node, layer ) => {
288
410
 
289
- const h_c = texture( bumpMaps, uvCache.bumpUV ).depth( int( material.bumpMapIndex ) ).r;
290
- const h_u = texture( bumpMaps, vec2( uvCache.bumpUV.x.add( texelSize.x ), uvCache.bumpUV.y ) ).depth( int( material.bumpMapIndex ) ).r;
291
- const h_v = texture( bumpMaps, vec2( uvCache.bumpUV.x, uvCache.bumpUV.y.add( texelSize.y ) ) ).depth( int( material.bumpMapIndex ) ).r;
411
+ const texelSize = vec2( 1.0 ).div( vec2( textureSize( node ) ) ).toVar();
412
+ const h_c = texture( node, uvCache.bumpUV ).depth( layer ).r;
413
+ const h_u = texture( node, vec2( uvCache.bumpUV.x.add( texelSize.x ), uvCache.bumpUV.y ) ).depth( layer ).r;
414
+ const h_v = texture( node, vec2( uvCache.bumpUV.x, uvCache.bumpUV.y.add( texelSize.y ) ) ).depth( layer ).r;
415
+ const gradient = vec2( h_u.sub( h_c ), h_v.sub( h_c ) ).mul( material.bumpScale );
416
+ bumpNormal.assign( normalize( vec3( gradient.x.negate(), gradient.y.negate(), 1.0 ) ) );
292
417
 
293
- const gradient = vec2( h_u.sub( h_c ), h_v.sub( h_c ) ).mul( material.bumpScale );
294
- const bumpNormal = normalize( vec3( gradient.x.negate(), gradient.y.negate(), 1.0 ) );
418
+ } );
295
419
 
296
420
  // Build TBN matrix using current normal
297
421
  const up = abs( currentNormal.z ).lessThan( 0.999 ).select( vec3( 0.0, 0.0, 1.0 ), vec3( 1.0, 0.0, 0.0 ) );
@@ -307,14 +431,14 @@ export const processBump = Fn( ( [ bumpMaps, currentNormal, material, uvCache ]
307
431
 
308
432
  } );
309
433
 
310
- export const processEmissive = Fn( ( [ emissiveMaps, material, uvCache ] ) => {
434
+ export const processEmissive = Fn( ( [ material, uvCache ] ) => {
311
435
 
312
436
  const emissionBase = material.emissive.mul( material.emissiveIntensity ).toVar();
313
437
 
314
438
  If( material.emissiveMapIndex.greaterThanEqual( int( 0 ) ), () => {
315
439
 
316
- const emissiveSample = texture( emissiveMaps, uvCache.emissiveUV ).depth( int( material.emissiveMapIndex ) ).toVar();
317
- // sRGB→linear handled by GPU hardware (texture.colorSpace = SRGBColorSpace → rgba8unorm-srgb format)
440
+ const emissiveSample = sampleBucket( _srgbBuckets, material.emissiveMapIndex, uvCache.emissiveUV ).toVar();
441
+ // sRGB→linear handled by GPU hardware (sRGB bucket arrays carry SRGBColorSpace → rgba8unorm-srgb)
318
442
  emissionBase.assign( emissionBase.mul( emissiveSample.rgb ) );
319
443
 
320
444
  } );
@@ -327,10 +451,7 @@ export const processEmissive = Fn( ( [ emissiveMaps, material, uvCache ] ) => {
327
451
  // MAIN BATCHED SAMPLING FUNCTION
328
452
  // ================================================================================
329
453
 
330
- export const sampleAllMaterialTextures = Fn( ( [
331
- albedoMaps, normalMaps, bumpMaps, metalnessMaps, roughnessMaps, emissiveMaps,
332
- material, uv, geometryNormal
333
- ] ) => {
454
+ export const sampleAllMaterialTextures = Fn( ( [ material, uv, geometryNormal ] ) => {
334
455
 
335
456
  const albedo = vec4( 0.0 ).toVar();
336
457
  const emissive = vec3( 0.0 ).toVar();
@@ -351,17 +472,17 @@ export const sampleAllMaterialTextures = Fn( ( [
351
472
  // Compute optimized UV cache with redundancy detection
352
473
  const uvCache = UVCache.wrap( computeUVCache( uv, material ) ).toVar();
353
474
 
354
- // Process samples
355
- albedo.assign( processAlbedo( albedoMaps, material, uvCache ) );
475
+ // Process samples (bucket nodes read from module-level state)
476
+ albedo.assign( processAlbedo( material, uvCache ) );
356
477
 
357
- const metalRough = processMetalnessRoughness( metalnessMaps, roughnessMaps, material, uvCache );
478
+ const metalRough = processMetalnessRoughness( material, uvCache );
358
479
  metalness.assign( metalRough.x );
359
480
  roughness.assign( metalRough.y );
360
481
 
361
- const currentNormal = processNormal( normalMaps, geometryNormal, material, uvCache ).toVar();
362
- normal.assign( processBump( bumpMaps, currentNormal, material, uvCache ) );
482
+ const currentNormal = processNormal( geometryNormal, material, uvCache ).toVar();
483
+ normal.assign( processBump( currentNormal, material, uvCache ) );
363
484
 
364
- emissive.assign( processEmissive( emissiveMaps, material, uvCache ) );
485
+ emissive.assign( processEmissive( material, uvCache ) );
365
486
 
366
487
  } );
367
488
 
@@ -369,15 +490,15 @@ export const sampleAllMaterialTextures = Fn( ( [
369
490
 
370
491
  } );
371
492
 
372
- // Sample displacement map at given UV coordinates
373
- export const sampleDisplacementMap = Fn( ( [ displacementMaps, displacementMapIndex, uv, transform ] ) => {
493
+ // Sample displacement map (linear pool) at given UV coordinates.
494
+ export const sampleDisplacementMap = Fn( ( [ displacementMapIndex, uv, transform ] ) => {
374
495
 
375
496
  const result = float( 0.0 ).toVar();
376
497
 
377
498
  If( displacementMapIndex.greaterThanEqual( int( 0 ) ), () => {
378
499
 
379
500
  const transformedUV = getTransformedUV( { uv, transform } );
380
- result.assign( texture( displacementMaps, transformedUV ).depth( int( displacementMapIndex ) ).r );
501
+ result.assign( sampleBucket( _linearBuckets, displacementMapIndex, transformedUV ).r );
381
502
 
382
503
  } );
383
504
 
@@ -360,6 +360,17 @@ export class EnvironmentManager {
360
360
  */
361
361
  async setEnvironmentMap( envMap ) {
362
362
 
363
+ // Free the outgoing env texture's GPU memory before it is orphaned. Skip: the
364
+ // reusable placeholder, an idempotent re-set of the same texture, and the HDRI
365
+ // stashed by setMode() for a later sky→hdri restore. Only when actually
366
+ // installing a new non-null texture (the null-env branch keeps the old ref).
367
+ const oldTex = this.environmentTexture;
368
+ if ( envMap && oldTex && oldTex !== envMap && oldTex !== this._envPlaceholder && oldTex !== this._previousHDRI ) {
369
+
370
+ oldTex.dispose?.();
371
+
372
+ }
373
+
363
374
  this.scene.environment = envMap;
364
375
  this.setEnvironmentTexture( envMap );
365
376
 
@@ -34,14 +34,18 @@ export class MaterialDataManager {
34
34
  this.materialStorageNode = null;
35
35
  this.materialCount = 0;
36
36
 
37
- // Material texture arrays
38
- this.albedoMaps = null;
39
- this.emissiveMaps = null;
40
- this.normalMaps = null;
41
- this.bumpMaps = null;
42
- this.roughnessMaps = null;
43
- this.metalnessMaps = null;
44
- this.displacementMaps = null;
37
+ // Consolidated size-bucketed material texture arrays (see SceneProcessor._bucketTextures):
38
+ // srgbBuckets[K] — albedo + emissive (SRGBColorSpace)
39
+ // linearBuckets[K] normal/bump/roughness/metalness/displacement
40
+ // Each entry is a DataArrayTexture | null (null = empty bucket).
41
+ this.srgbBuckets = null;
42
+ this.linearBuckets = null;
43
+
44
+ // uuid → packed (bucket,layer) index maps for the current scene, handed over by the
45
+ // SceneProcessor that built the buckets. Let runtime material edits (updateMaterial)
46
+ // re-pack a texture's index against the current bucket layout.
47
+ this._srgbTexPacked = null;
48
+ this._linearTexPacked = null;
45
49
 
46
50
  // Compiled features cache (for change detection)
47
51
  this.compiledFeatures = null;
@@ -112,45 +116,60 @@ export class MaterialDataManager {
112
116
  */
113
117
  setMaterialTextures( textures ) {
114
118
 
115
- if ( textures.albedoMaps ) this.albedoMaps = textures.albedoMaps;
116
- if ( textures.emissiveMaps ) this.emissiveMaps = textures.emissiveMaps;
117
- if ( textures.normalMaps ) this.normalMaps = textures.normalMaps;
118
- if ( textures.bumpMaps ) this.bumpMaps = textures.bumpMaps;
119
- if ( textures.roughnessMaps ) this.roughnessMaps = textures.roughnessMaps;
120
- if ( textures.metalnessMaps ) this.metalnessMaps = textures.metalnessMaps;
121
- if ( textures.displacementMaps ) this.displacementMaps = textures.displacementMaps;
119
+ if ( textures.srgbBuckets ) this.srgbBuckets = textures.srgbBuckets;
120
+ if ( textures.linearBuckets ) this.linearBuckets = textures.linearBuckets;
122
121
 
123
122
  }
124
123
 
125
124
  /**
126
- * Load texture arrays from sdfs.
125
+ * Receive the scene's uuid→packed texture-index maps (from the SceneProcessor that bucketed).
126
+ * @param {Map|null} srgb
127
+ * @param {Map|null} linear
128
+ */
129
+ setTexturePackMaps( srgb, linear ) {
130
+
131
+ this._srgbTexPacked = srgb || null;
132
+ this._linearTexPacked = linear || null;
133
+
134
+ }
135
+
136
+ /**
137
+ * Packed (bucket, layer) index for a Three.js texture against the current bucket layout,
138
+ * or -1 if it isn't bucketed (a genuinely new texture → needs rebuildMaterials).
139
+ * @param {import('three').Texture|null} texture
140
+ * @param {boolean} isSrgb - true for albedo/emissive pool, false for the linear pool
141
+ * @returns {number}
142
+ */
143
+ getPackedTextureIndex( texture, isSrgb ) {
144
+
145
+ if ( ! texture ) return - 1;
146
+ const uuid = texture.source?.uuid ?? texture.uuid;
147
+ const map = isSrgb ? this._srgbTexPacked : this._linearTexPacked;
148
+ const packed = map?.get( uuid );
149
+ return packed === undefined ? - 1 : packed;
150
+
151
+ }
152
+
153
+ /**
154
+ * Load consolidated bucket arrays + pack maps from the SceneProcessor.
127
155
  */
128
156
  loadTexturesFromSdfs() {
129
157
 
130
- this.albedoMaps = this.sdfs.albedoTextures;
131
- this.emissiveMaps = this.sdfs.emissiveTextures;
132
- this.normalMaps = this.sdfs.normalTextures;
133
- this.bumpMaps = this.sdfs.bumpTextures;
134
- this.roughnessMaps = this.sdfs.roughnessTextures;
135
- this.metalnessMaps = this.sdfs.metalnessTextures;
136
- this.displacementMaps = this.sdfs.displacementTextures;
158
+ this.srgbBuckets = this.sdfs.srgbBucketTextures;
159
+ this.linearBuckets = this.sdfs.linearBucketTextures;
160
+ this.setTexturePackMaps( this.sdfs._srgbTexPacked, this.sdfs._linearTexPacked );
137
161
 
138
162
  }
139
163
 
140
164
  /**
141
- * Get all texture arrays.
142
- * @returns {Object}
165
+ * Get the consolidated bucket arrays.
166
+ * @returns {{ srgbBuckets: Array, linearBuckets: Array }}
143
167
  */
144
168
  getTextureArrays() {
145
169
 
146
170
  return {
147
- albedoMaps: this.albedoMaps,
148
- emissiveMaps: this.emissiveMaps,
149
- normalMaps: this.normalMaps,
150
- bumpMaps: this.bumpMaps,
151
- roughnessMaps: this.roughnessMaps,
152
- metalnessMaps: this.metalnessMaps,
153
- displacementMaps: this.displacementMaps,
171
+ srgbBuckets: this.srgbBuckets,
172
+ linearBuckets: this.linearBuckets,
154
173
  };
155
174
 
156
175
  }
@@ -550,6 +569,22 @@ export class MaterialDataManager {
550
569
  updateMaterial( materialIndex, material ) {
551
570
 
552
571
  const completeMaterialData = this.sdfs.geometryExtractor.createMaterialObject( material );
572
+
573
+ // createMaterialObject returns stale per-type indices; re-pack each map to the packed
574
+ // (bucket, layer) index for the CURRENT bucket layout. -1 for a texture not yet bucketed
575
+ // (a genuinely new map → the caller must rebuildMaterials to add it to a bucket array).
576
+ if ( this._srgbTexPacked || this._linearTexPacked ) {
577
+
578
+ completeMaterialData.map = this.getPackedTextureIndex( material.map, true );
579
+ completeMaterialData.emissiveMap = this.getPackedTextureIndex( material.emissiveMap, true );
580
+ completeMaterialData.normalMap = this.getPackedTextureIndex( material.normalMap, false );
581
+ completeMaterialData.bumpMap = this.getPackedTextureIndex( material.bumpMap, false );
582
+ completeMaterialData.roughnessMap = this.getPackedTextureIndex( material.roughnessMap, false );
583
+ completeMaterialData.metalnessMap = this.getPackedTextureIndex( material.metalnessMap, false );
584
+ completeMaterialData.displacementMap = this.getPackedTextureIndex( material.displacementMap, false );
585
+
586
+ }
587
+
553
588
  this.updateMaterialDataFromObject( materialIndex, completeMaterialData );
554
589
 
555
590
  }
@@ -813,13 +848,10 @@ export class MaterialDataManager {
813
848
  this.materialStorageAttr = null;
814
849
  this.materialStorageNode = null;
815
850
  this.materialCount = 0;
816
- this.albedoMaps = null;
817
- this.emissiveMaps = null;
818
- this.normalMaps = null;
819
- this.bumpMaps = null;
820
- this.roughnessMaps = null;
821
- this.metalnessMaps = null;
822
- this.displacementMaps = null;
851
+ this.srgbBuckets = null;
852
+ this.linearBuckets = null;
853
+ this._srgbTexPacked = null;
854
+ this._linearTexPacked = null;
823
855
  this.compiledFeatures = null;
824
856
 
825
857
  }