rayzee 10.0.0 → 11.1.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 (176) hide show
  1. package/README.md +252 -44
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-Hn35o6Z5.js +5145 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-CBw8hkeu.js +946 -0
  6. package/dist/BufferLayout-D_IBo2ci.js +150 -0
  7. package/dist/ColorManagement-oeEk7oSu.js +1488 -0
  8. package/dist/EngineEvents-Ci8dVWCP.js +73 -0
  9. package/dist/EngineIssues-CIZnXHsF.js +72 -0
  10. package/dist/FileFormats-sPGJeCRc.js +102 -0
  11. package/dist/Logger-dDDGj9Av.js +97 -0
  12. package/dist/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-BzNpiVHZ.js} +8 -6
  13. package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-Bk7bHg2B.js} +55 -120
  14. package/dist/PathTracer-72XufKAr.js +29976 -0
  15. package/dist/PhysicalSky-BgjdV88k.js +1219 -0
  16. package/dist/RayzeeRenderer-1QRO97eq.js +3551 -0
  17. package/dist/SolarLimb-CPaeZVwz.js +721 -0
  18. package/dist/ToneMapGPU-CjU3osDd.js +177 -0
  19. package/dist/ViewTransforms-CYN7bq2w.js +255 -0
  20. package/dist/WorkingMatrix-C6y233hT.js +27 -0
  21. package/dist/ZipReader-gDSWr0I5.js +98 -0
  22. package/dist/addons/archives.es.js +3 -0
  23. package/dist/addons/bidirectional.es.js +2 -0
  24. package/dist/addons/color.es.js +3 -0
  25. package/dist/addons/formats.es.js +2 -0
  26. package/dist/addons/physical-sky.es.js +2 -0
  27. package/dist/addons/storage.es.js +3 -0
  28. package/dist/archiveFormats-tnAoUL1j.js +94 -0
  29. package/dist/identity-DrQsGT2o.js +39 -0
  30. package/dist/openStorage-DwcBRUj7.js +820 -0
  31. package/dist/rayzee-core.es.js +12 -0
  32. package/dist/rayzee.es.js +2551 -44024
  33. package/dist/rayzee.umd.js +528 -487
  34. package/dist/sceneCachePolicy-C67jbci-.js +443 -0
  35. package/dist/shared-BGBcSxBQ.js +17 -0
  36. package/package.json +23 -2
  37. package/src/Color/ActiveColor.js +34 -0
  38. package/src/Color/BasicColor.js +147 -0
  39. package/src/Color/ColorManagement.js +7 -31
  40. package/src/Color/ViewTransforms.js +5 -1
  41. package/src/EngineDefaults.js +62 -1029
  42. package/src/EngineEvents.js +2 -0
  43. package/src/EngineIssues.js +2 -0
  44. package/src/Headless.js +6 -5
  45. package/src/Passes/AIUpscaler.js +58 -141
  46. package/src/Passes/OIDNDenoiser.js +92 -65
  47. package/src/Passes/OIDNTemporalHistory.js +1 -1
  48. package/src/PathTracerApp.js +620 -4026
  49. package/src/Pipeline/PipelineContext.js +0 -4
  50. package/src/Platform.js +36 -0
  51. package/src/Processor/ArchiveCache.js +20 -7
  52. package/src/Processor/ArchiveImporter.js +1265 -0
  53. package/src/Processor/ArchiveReader.js +22 -4
  54. package/src/Processor/AssetLoader.js +327 -1763
  55. package/src/Processor/AtmosphereModel.js +1 -2
  56. package/src/Processor/BVHBuilder.js +14 -126
  57. package/src/Processor/BVHLeafFold.js +4 -6
  58. package/src/Processor/BVHRefitter.js +67 -46
  59. package/src/Processor/BufferLayout.js +291 -0
  60. package/src/Processor/EmissiveTriangleBuilder.js +1 -1
  61. package/src/Processor/EnvironmentExactTable.js +187 -0
  62. package/src/Processor/EquirectHDRInfo.js +30 -223
  63. package/src/Processor/FileFormats.js +116 -0
  64. package/src/Processor/GLTFDecoders.js +85 -0
  65. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  66. package/src/Processor/GeometryExtractor.js +15 -5
  67. package/src/Processor/HostMemory.js +7 -1
  68. package/src/Processor/IESParser.js +47 -35
  69. package/src/Processor/InstanceTable.js +1 -1
  70. package/src/Processor/KernelManager.js +35 -2
  71. package/src/Processor/MaterialPacking.js +10 -8
  72. package/src/Processor/PBRT/PBRTMaterials.js +254 -42
  73. package/src/Processor/PBRT/PBRTParser.js +49 -19
  74. package/src/Processor/PBRT/PBRTScattering.js +93 -0
  75. package/src/Processor/PBRT/PBRTSceneBuilder.js +1050 -75
  76. package/src/Processor/PBRT/PBRTTextureBake.js +179 -0
  77. package/src/Processor/PBRT/PFM.js +59 -0
  78. package/src/Processor/PackedRayBuffer.js +116 -9
  79. package/src/Processor/ParallelBVHBuilder.js +3 -6
  80. package/src/Processor/PhysicalSky.js +5 -4
  81. package/src/Processor/PlatformImageLoader.js +50 -6
  82. package/src/Processor/QueueManager.js +39 -5
  83. package/src/Processor/ReinsertionOptimizer.js +2 -2
  84. package/src/Processor/SceneMetadata.js +30 -5
  85. package/src/Processor/SceneProcessor.js +53 -62
  86. package/src/Processor/ShaderBuilder.js +6 -16
  87. package/src/Processor/SimpleSky.js +1 -62
  88. package/src/Processor/SolarLimb.js +3 -0
  89. package/src/Processor/SplitBLAS.js +1 -1
  90. package/src/Processor/StorageTexturePool.js +22 -1
  91. package/src/Processor/TLASBuilder.js +1 -3
  92. package/src/Processor/TextureBuckets.js +237 -0
  93. package/src/Processor/TextureCreator.js +181 -8
  94. package/src/Processor/ToneMapGPU.js +64 -15
  95. package/src/Processor/Workers/BVHRefitWorker.js +5 -2
  96. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  97. package/src/Processor/Workers/BVHWorker.js +5 -18
  98. package/src/Processor/Workers/CDFWorker.js +6 -183
  99. package/src/Processor/Workers/TexturesWorker.js +103 -18
  100. package/src/Processor/archiveFormats.js +7 -0
  101. package/src/Processor/utils.js +2 -1
  102. package/src/RayzeeRenderer.js +3900 -0
  103. package/src/RenderSettings.js +68 -62
  104. package/src/Stages/ASVGF.js +13 -14
  105. package/src/Stages/AutoExposure.js +17 -17
  106. package/src/Stages/BilateralFilter.js +2 -1
  107. package/src/Stages/Compositor.js +8 -23
  108. package/src/Stages/DenoiserSettings.js +182 -0
  109. package/src/Stages/EdgeFilter.js +2 -1
  110. package/src/Stages/MotionVector.js +1 -1
  111. package/src/Stages/NRD.js +11 -5
  112. package/src/Stages/NormalDepth.js +12 -8
  113. package/src/Stages/PathTracer.js +336 -43
  114. package/src/Stages/PathTracerStage.js +69 -66
  115. package/src/Stages/Variance.js +1 -1
  116. package/src/Storage/BLASCache.js +1 -1
  117. package/src/Storage/CDFCache.js +18 -17
  118. package/src/Storage/DownloadCache.js +1 -1
  119. package/src/Storage/SceneGraphCodec.js +46 -3
  120. package/src/Storage/SpillStore.js +1 -1
  121. package/src/Storage/StorageManager.js +45 -9
  122. package/src/Storage/areas.js +4 -0
  123. package/src/TSL/BVHTraversal.js +50 -25
  124. package/src/TSL/Bidirectional.js +208 -0
  125. package/src/TSL/BidirectionalLamps.js +132 -0
  126. package/src/TSL/Clearcoat.js +15 -6
  127. package/src/TSL/Common.js +20 -15
  128. package/src/TSL/ConnectKernel.js +167 -0
  129. package/src/TSL/Debugger.js +1 -1
  130. package/src/TSL/Displacement.js +4 -3
  131. package/src/TSL/EmissiveSampling.js +65 -18
  132. package/src/TSL/Environment.js +77 -106
  133. package/src/TSL/EnvironmentCDF.js +85 -39
  134. package/src/TSL/ExtendKernel.js +8 -3
  135. package/src/TSL/FinalWriteKernel.js +4 -4
  136. package/src/TSL/GenerateKernel.js +18 -4
  137. package/src/TSL/HitFacet.js +35 -5
  138. package/src/TSL/LightBVHSampling.js +40 -9
  139. package/src/TSL/LightGenerateKernel.js +293 -0
  140. package/src/TSL/LightGuide.js +198 -0
  141. package/src/TSL/LightSplatKernel.js +180 -0
  142. package/src/TSL/LightVisibility.js +111 -0
  143. package/src/TSL/LightsCore.js +13 -38
  144. package/src/TSL/LightsDirect.js +28 -29
  145. package/src/TSL/LightsIndirect.js +3 -1
  146. package/src/TSL/LightsSampling.js +239 -51
  147. package/src/TSL/MaterialEvaluation.js +113 -31
  148. package/src/TSL/MaterialProperties.js +44 -17
  149. package/src/TSL/MaterialTransmission.js +52 -47
  150. package/src/TSL/MergeKernel.js +224 -0
  151. package/src/TSL/PathTracerCore.js +65 -29
  152. package/src/TSL/Random.js +5 -1
  153. package/src/TSL/SceneResources.js +51 -0
  154. package/src/TSL/ShadeKernel.js +985 -101
  155. package/src/TSL/Struct.js +10 -0
  156. package/src/TSL/Sun.js +1 -1
  157. package/src/TSL/TextureSampling.js +66 -61
  158. package/src/addons/archives.js +14 -0
  159. package/src/addons/bidirectional.js +13 -0
  160. package/src/addons/color.js +32 -0
  161. package/src/addons/formats.js +16 -0
  162. package/src/addons/physicalSky.js +14 -0
  163. package/src/addons/storage.js +19 -0
  164. package/src/core.js +31 -0
  165. package/src/index.js +13 -23
  166. package/src/integrators/BidirectionalIntegrator.js +838 -0
  167. package/src/managers/AnimationManager.js +88 -6
  168. package/src/managers/CameraManager.js +103 -46
  169. package/src/managers/DenoisingManager.js +14 -10
  170. package/src/managers/EnvironmentManager.js +125 -103
  171. package/src/managers/GoboManager.js +9 -0
  172. package/src/managers/MaterialDataManager.js +48 -37
  173. package/src/managers/UniformManager.js +32 -10
  174. package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
  175. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  176. package/src/Processor/TreeletOptimizer.js +0 -574
@@ -10,7 +10,7 @@ import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
10
10
  import { CameraOptimizer } from '../Processor/CameraOptimizer.js';
11
11
  import { createPerformanceMonitor, calculateAccumulationAlpha, updateCompletionThreshold } from '../Processor/utils.js';
12
12
  import { StorageTexturePool } from '../Processor/StorageTexturePool.js';
13
- import { UniformManager } from '../managers/UniformManager.js';
13
+ import { UniformManager, LIGHT_FLOATS, lightListCapacity } from '../managers/UniformManager.js';
14
14
  import { MaterialDataManager } from '../managers/MaterialDataManager.js';
15
15
  import { EnvironmentManager } from '../managers/EnvironmentManager.js';
16
16
  import { ShaderBuilder } from '../Processor/ShaderBuilder.js';
@@ -20,7 +20,8 @@ import { SceneProcessor } from '../Processor/SceneProcessor.js';
20
20
  import { LightSerializer } from '../Processor/LightSerializer';
21
21
 
22
22
  // Constants
23
- import { ENGINE_DEFAULTS as DEFAULT_STATE, TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
23
+ import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
24
+ import { TRIANGLE_DATA_LAYOUT } from '../Processor/BufferLayout.js';
24
25
  import { TRI_GEO_ROWS, TRI_SHADE_ROWS } from '../TSL/Common.js';
25
26
  import { createLogger, fmt } from '../utils/Logger.js';
26
27
 
@@ -53,8 +54,7 @@ const BVH_VEC4_PER_NODE = 4;
53
54
  * Events emitted:
54
55
  * - pathtracer:frameComplete - When a frame finishes rendering
55
56
  * - camera:moved - When camera position/orientation changes
56
- * - asvgf:reset - Request ASVGF to reset temporal data
57
- * - asvgf:updateParameters - Update ASVGF parameters
57
+ * - pipeline:historyReset - temporal history from before is not comparable (render mode change)
58
58
  *
59
59
  * Textures published to context:
60
60
  * - pathtracer:color - Main color output
@@ -113,6 +113,13 @@ export class PathTracerStage extends RenderStage {
113
113
  // Initialize material data manager
114
114
  this.materialData = new MaterialDataManager( this.sdfs );
115
115
  this.materialData.callbacks.onReset = () => this.reset();
116
+ // A material edit can switch the diffuse transmission lobe on or off, which the shade kernel compiles in.
117
+ this.materialData.callbacks.onMaterialFeaturesChanged = () => {
118
+
119
+ this._materialFeaturesChanged = true;
120
+
121
+ };
122
+
116
123
  // Material edits patch the per-triangle flags lane in the CPU records (`_triangleRecords`
117
124
  // when chunked, `_triangleFlat` otherwise — not sdfs.triangleData, which the PathTracerApp
118
125
  // build path never fills). The flags live in the shade rows, so only those go up again.
@@ -218,8 +225,8 @@ export class PathTracerStage extends RenderStage {
218
225
  // Initialized with dummy data so TSL compilation never sees null.
219
226
  this.lightStorageAttr = new StorageInstancedBufferAttribute( new Float32Array( 16 ), 4 );
220
227
  this.lightStorageNode = storage( this.lightStorageAttr, 'vec4', 1 ).toReadOnly();
221
- // Set when _rebuildLightBuffer had to grow-reallocate the attribute: compiled
222
- // kernels still bind the old one and must be rebuilt to see the new data.
228
+ // Set when _rebuildLightBuffer had to grow-reallocate the attribute, or a light list grew
229
+ // (_writeLightList): compiled kernels still bind the old one and must be rebuilt to see the new data.
223
230
  this._lightBufferRealloc = false;
224
231
 
225
232
  // Cached CPU-side data — rebuilt into the packed buffer whenever any source changes.
@@ -375,16 +382,6 @@ export class PathTracerStage extends RenderStage {
375
382
 
376
383
  } );
377
384
 
378
- this.on( 'pathtracer:setCompletionThreshold', ( data ) => {
379
-
380
- if ( data && data.threshold !== undefined ) {
381
-
382
- this.completionThreshold = data.threshold;
383
-
384
- }
385
-
386
- } );
387
-
388
385
  }
389
386
 
390
387
  // ===== PUBLIC API METHODS =====
@@ -492,53 +489,36 @@ export class PathTracerStage extends RenderStage {
492
489
  */
493
490
  _updateLightBufferNodes() {
494
491
 
495
- // Directional lights (12 floats per light — 8 light fields + gobo {index, signed intensity, scale, pad})
496
- if ( this.directionalLightsData && this.directionalLightsData.length > 0 ) {
497
-
498
- this.directionalLightsBufferNode.array = Array.from( this.directionalLightsData );
499
- this.numDirectionalLights.value = Math.floor( this.directionalLightsData.length / 12 );
500
-
501
- } else {
502
-
503
- this.numDirectionalLights.value = 0;
504
-
505
- }
506
-
507
- // Area lights (16 floats per light — 13 base + normalize/spread/shape)
508
- if ( this.areaLightsData && this.areaLightsData.length > 0 ) {
509
-
510
- this.areaLightsBufferNode.array = Array.from( this.areaLightsData );
511
- this.numAreaLights.value = Math.floor( this.areaLightsData.length / 16 );
512
-
513
- } else {
514
-
515
- this.numAreaLights.value = 0;
492
+ this._writeLightList( 'directional', this.directionalLightsData, this.numDirectionalLights );
493
+ this._writeLightList( 'area', this.areaLightsData, this.numAreaLights );
494
+ this._writeLightList( 'point', this.pointLightsData, this.numPointLights );
495
+ this._writeLightList( 'spot', this.spotLightsData, this.numSpotLights );
516
496
 
517
- }
497
+ }
518
498
 
519
- // Point lights (9 floats per light)
520
- if ( this.pointLightsData && this.pointLightsData.length > 0 ) {
499
+ /**
500
+ * In place, because the shader bakes the list's length: sized per scene it compiled a program per
501
+ * light count, and three.js uploads only what fits the length it was built with, so a light added
502
+ * after a build was dropped. A list grows only past its capacity, and that rebuilds the kernels.
503
+ * @private
504
+ */
505
+ _writeLightList( type, data, count ) {
521
506
 
522
- this.pointLightsBufferNode.array = Array.from( this.pointLightsData );
523
- this.numPointLights.value = Math.floor( this.pointLightsData.length / 9 );
507
+ const floats = LIGHT_FLOATS[ type ];
508
+ const node = this[ `${type}LightsBufferNode` ];
509
+ const lights = data ? Math.floor( data.length / floats ) : 0;
510
+ const used = lights * floats;
524
511
 
525
- } else {
512
+ if ( used > node.array.length ) {
526
513
 
527
- this.numPointLights.value = 0;
514
+ node.array = new Float32Array( lightListCapacity( type, lights ) * floats );
515
+ this._lightBufferRealloc = true;
528
516
 
529
517
  }
530
518
 
531
- // Spot lights (20 floats per light — 14 light fields + gobo {idx, signed intensity} + IES {idx, intensity} + 2 reserved)
532
- if ( this.spotLightsData && this.spotLightsData.length > 0 ) {
533
-
534
- this.spotLightsBufferNode.array = Array.from( this.spotLightsData );
535
- this.numSpotLights.value = Math.floor( this.spotLightsData.length / 20 );
536
-
537
- } else {
538
-
539
- this.numSpotLights.value = 0;
540
-
541
- }
519
+ if ( used > 0 ) node.array.set( data.subarray( 0, used ) );
520
+ node.array.fill( 0, used );
521
+ count.value = lights;
542
522
 
543
523
  }
544
524
 
@@ -559,12 +539,13 @@ export class PathTracerStage extends RenderStage {
559
539
  this.performanceMonitor?.reset();
560
540
 
561
541
  // lastRenderMode is deliberately NOT invalidated here — clearing it made
562
- // manageASVGFForRenderMode see a phantom mode change 50 ms after every reset, wiping
542
+ // resetHistoryOnRenderModeChange see a phantom mode change 50 ms after every reset, wiping
563
543
  // ASVGF's history. A real mode change is still caught by the renderMode comparison.
564
544
  this.lastInteractionModeState = false;
565
545
 
566
546
  // Only deterministic mode and lockstep readbacks rewind the seed axis; see _pinSeedToFrame.
567
547
  if ( this._pinSeedToFrame || this._lockstep ) this._seedTick = 0;
548
+ this._accumulationLockstep = !! this._lockstep;
568
549
 
569
550
  }
570
551
 
@@ -1088,26 +1069,48 @@ export class PathTracerStage extends RenderStage {
1088
1069
 
1089
1070
  }
1090
1071
 
1091
- /** Re-uploads every triangle from the CPU records, adopting `triangleData` as them when given. */
1092
- updateTriangleData( triangleData ) {
1072
+ /**
1073
+ * Re-uploads triangles from the CPU records, adopting `triangleData` as them when given.
1074
+ * @param {Object} [triangleData]
1075
+ * @param {Array<{offset:number,count:number}>} [ranges] - only these, in record lanes; every triangle when omitted
1076
+ */
1077
+ updateTriangleData( triangleData, ranges ) {
1093
1078
 
1094
1079
  if ( triangleData?.chunks?.length > 1 ) this._triangleRecords = triangleData;
1095
1080
  else if ( triangleData ) this._triangleFlat = triangleData.chunks ? triangleData.chunks[ 0 ] : triangleData;
1096
- this._uploadTriangles( 0, this._triangleRecordCount );
1081
+ if ( ranges ) this.updateBufferRanges( ranges, [] );
1082
+ else this._uploadTriangles( 0, this._triangleRecordCount );
1097
1083
 
1098
1084
  }
1099
1085
 
1100
- /** Update BVH node data in the existing GPU buffer (full). */
1101
- updateBVHData( bvhData ) {
1086
+ /**
1087
+ * Update BVH node data in the existing GPU buffer.
1088
+ * @param {Object} [bvhData]
1089
+ * @param {Array<{offset:number,count:number}>} [ranges] - only these, in floats; every node when omitted
1090
+ */
1091
+ updateBVHData( bvhData, ranges ) {
1102
1092
 
1103
1093
  if ( this._bvhRecords ) {
1104
1094
 
1105
- uploadStorageChunks( this.renderer, this.bvhStorageAttr, this._bvhRecords.chunks );
1095
+ if ( bvhData?.chunks?.length > 1 ) this._bvhRecords = bvhData;
1096
+ if ( ranges ) this.updateBufferRanges( [], ranges );
1097
+ else uploadStorageChunks( this.renderer, this.bvhStorageAttr, this._bvhRecords.chunks );
1098
+ return;
1099
+
1100
+ }
1101
+
1102
+ const flat = bvhData?.chunks ? bvhData.chunks[ 0 ] : bvhData;
1103
+ if ( ! ranges ) {
1104
+
1105
+ this._updateStorageBuffer( this.bvhStorageAttr, flat );
1106
1106
  return;
1107
1107
 
1108
1108
  }
1109
1109
 
1110
- this._updateStorageBuffer( this.bvhStorageAttr, bvhData?.chunks ? bvhData.chunks[ 0 ] : bvhData );
1110
+ // The attribute keeps its own array once the refit has moved the nodes into shared memory.
1111
+ const own = this.bvhStorageAttr?.array;
1112
+ if ( flat && own && flat !== own ) for ( const r of ranges ) own.set( flat.subarray( r.offset, r.offset + r.count ), r.offset );
1113
+ this.updateBufferRanges( [], ranges );
1111
1114
 
1112
1115
  }
1113
1116
 
@@ -1266,6 +1269,7 @@ export class PathTracerStage extends RenderStage {
1266
1269
 
1267
1270
  this.createStorageTextures( width, height );
1268
1271
  this.frameCount = 0;
1272
+ this._accumulationLockstep = false;
1269
1273
 
1270
1274
  }
1271
1275
 
@@ -1413,7 +1417,7 @@ export class PathTracerStage extends RenderStage {
1413
1417
 
1414
1418
  // ===== ASVGF DENOISING MANAGEMENT =====
1415
1419
 
1416
- manageASVGFForRenderMode( renderMode ) {
1420
+ resetHistoryOnRenderModeChange( renderMode ) {
1417
1421
 
1418
1422
  if ( renderMode !== this.lastRenderMode ) {
1419
1423
 
@@ -1433,8 +1437,7 @@ export class PathTracerStage extends RenderStage {
1433
1437
  // History from the previous mode is not comparable. temporalAlpha is NOT
1434
1438
  // touched here — it is owned by ASVGF_QUALITY_PRESETS, and overwriting it
1435
1439
  // with a hardcoded value meant `medium` and `high` never took effect.
1436
- this.emit( 'asvgf:reset' );
1437
- this.emit( 'denoiser:reset' );
1440
+ this.emit( 'pipeline:historyReset' );
1438
1441
 
1439
1442
  }
1440
1443
 
@@ -4,7 +4,7 @@ import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
4
4
  import { FloatType, RGBAFormat, LinearFilter, Box2, Vector2 } from 'three';
5
5
  import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
6
6
  import { luminance } from '../TSL/Common.js';
7
- import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
7
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
8
8
 
9
9
  // NaN/±Inf guard: a poisoned luminance would otherwise corrupt the moment EMA forever
10
10
  // (mean/meanSq → variance → BilateralFilter sigmaL). NaN (x!=x) → 0, ±Inf → [0,1e7].
@@ -4,7 +4,7 @@
4
4
  * stored BLAS is only ever used for exactly the triangles it was built over.
5
5
  */
6
6
 
7
- import { ENGINE_AREAS } from './StorageManager.js';
7
+ import { ENGINE_AREAS } from './areas.js';
8
8
 
9
9
  export const BLAS_CACHE_FORMAT = 2;
10
10
 
@@ -1,8 +1,8 @@
1
- import { ENGINE_AREAS } from './StorageManager.js';
1
+ import { ENGINE_AREAS } from './areas.js';
2
2
  import { sharedStorage } from './shared.js';
3
3
  import { getAssetConfig } from '../AssetConfig.js';
4
4
 
5
- const FORMAT = 1;
5
+ const FORMAT = 5;
6
6
 
7
7
  /**
8
8
  * Where an environment texture came from, recorded by the loader so its sampling tables can be
@@ -25,7 +25,10 @@ function keyFor( texture ) {
25
25
 
26
26
  const area = () => sharedStorage( getAssetConfig().cacheNamespace )?.area( ENGINE_AREAS.CDF ) ?? null;
27
27
 
28
- /** @returns {Promise<?{marginalData: Float32Array, conditionalData: Float32Array, totalSum: number, compensationDelta: number, width: number, height: number}>} */
28
+ const ARRAYS = [ 'exactConditional', 'exactMarginal', 'exactRowGuide', 'exactMarginalGuide' ];
29
+ const FILES = ARRAYS.map( name => `${name}.f32` );
30
+
31
+ /** @returns {Promise<?Object>} the fields of an EquirectHDRInfo build */
29
32
  export async function loadCDF( texture ) {
30
33
 
31
34
  const key = keyFor( texture );
@@ -34,14 +37,11 @@ export async function loadCDF( texture ) {
34
37
 
35
38
  try {
36
39
 
37
- const [ marginal, conditional ] = await Promise.all( [ entry.file( 'marginal.f32' ), entry.file( 'conditional.f32' ) ] );
38
- if ( ! marginal || ! conditional ) return null;
39
- const { totalSum, compensationDelta, width, height } = entry.extra;
40
- return {
41
- marginalData: new Float32Array( await marginal.arrayBuffer() ),
42
- conditionalData: new Float32Array( await conditional.arrayBuffer() ),
43
- totalSum, compensationDelta, width, height,
44
- };
40
+ const files = await Promise.all( FILES.map( name => entry.file( name ) ) );
41
+ if ( files.some( f => ! f ) ) return null;
42
+ const arrays = await Promise.all( files.map( async f => new Float32Array( await f.arrayBuffer() ) ) );
43
+ const { totalSum, width, height, exactWidth, exactHeight, radianceIntegral } = entry.extra;
44
+ return { ...Object.fromEntries( ARRAYS.map( ( name, i ) => [ name, arrays[ i ] ] ) ), totalSum, width, height, exactWidth, exactHeight, radianceIntegral };
45
45
 
46
46
  } finally {
47
47
 
@@ -51,20 +51,21 @@ export async function loadCDF( texture ) {
51
51
 
52
52
  }
53
53
 
54
- export async function saveCDF( texture, { marginalData, conditionalData, totalSum, compensationDelta, width, height } ) {
54
+ export async function saveCDF( texture, info ) {
55
55
 
56
+ const { totalSum, width, height, exactWidth, exactHeight, radianceIntegral } = info;
57
+ const arrays = ARRAYS.map( name => info[ name ] );
56
58
  const key = keyFor( texture );
57
59
  const target = key ? area() : null;
58
- if ( ! target || ! marginalData || ! conditionalData ) return false;
60
+ if ( ! target || arrays.some( a => ! a ) ) return false;
59
61
 
60
- const writer = await target.create( key, { label: texture.name || 'environment', expectedBytes: marginalData.byteLength + conditionalData.byteLength } );
62
+ const writer = await target.create( key, { label: texture.name || 'environment', expectedBytes: arrays.reduce( ( n, a ) => n + a.byteLength, 0 ) } );
61
63
  if ( ! writer ) return false;
62
64
 
63
65
  try {
64
66
 
65
- await writer.write( 'marginal.f32', marginalData );
66
- await writer.write( 'conditional.f32', conditionalData );
67
- await writer.commit( { totalSum, compensationDelta, width, height } );
67
+ for ( let i = 0; i < FILES.length; i ++ ) await writer.write( FILES[ i ], arrays[ i ] );
68
+ await writer.commit( { totalSum, width, height, exactWidth, exactHeight, radianceIntegral } );
68
69
  return true;
69
70
 
70
71
  } catch {
@@ -1,4 +1,4 @@
1
- import { ENGINE_AREAS } from './StorageManager.js';
1
+ import { ENGINE_AREAS } from './areas.js';
2
2
  import { sharedStorage } from './shared.js';
3
3
  import { getAssetConfig } from '../AssetConfig.js';
4
4
 
@@ -9,11 +9,13 @@
9
9
  */
10
10
 
11
11
  import {
12
- AnimationClip, BufferAttribute, BufferGeometry, DataTexture, Group, InstancedBufferAttribute, InstancedMesh,
13
- MaterialLoader, Mesh, Object3D, OrthographicCamera, PerspectiveCamera,
12
+ AnimationClip, BufferAttribute, BufferGeometry, Color, DataTexture, Group, InstancedBufferAttribute, InstancedMesh,
13
+ MaterialLoader, Mesh, Object3D, OrthographicCamera, PerspectiveCamera, DirectionalLight, PointLight, SpotLight,
14
14
  } from 'three';
15
15
 
16
16
  export const SCENE_GRAPH_FORMAT = 2;
17
+ // Texture slots the engine reads off a material that three.js does not serialise.
18
+ const ENGINE_TEXTURES = [ 'diffuseTransmissionMap', 'diffuseTransmissionColorMap' ];
17
19
  export const ARCHIVE_PATH = '__rayzeeArchivePath';
18
20
  export const ARCHIVE_LOADER = '__rayzeeArchiveLoader';
19
21
 
@@ -28,6 +30,7 @@ const TEXTURE_PROPS = [
28
30
  'flipY', 'generateMipmaps', 'premultiplyAlpha', 'unpackAlignment', 'colorSpace',
29
31
  ];
30
32
 
33
+ const LIGHT_PROPS = [ 'intensity', 'distance', 'decay', 'angle', 'penumbra' ];
31
34
  const OBJECT_PROPS = [ 'name', 'visible', 'castShadow', 'receiveShadow', 'frustumCulled', 'renderOrder', 'matrixAutoUpdate' ];
32
35
 
33
36
  export class SceneGraphUnsupported extends Error {
@@ -48,6 +51,9 @@ function kindOf( object ) {
48
51
  if ( object.isMesh ) return 'Mesh';
49
52
  if ( object.isPerspectiveCamera ) return 'PerspectiveCamera';
50
53
  if ( object.isOrthographicCamera ) return 'OrthographicCamera';
54
+ if ( object.isDirectionalLight ) return 'DirectionalLight';
55
+ if ( object.isPointLight ) return 'PointLight';
56
+ if ( object.isSpotLight ) return 'SpotLight';
51
57
  if ( object.isLight || object.isPoints || object.isLine || object.isSprite || object.isLOD ) return null;
52
58
  if ( object.isGroup ) return 'Group';
53
59
  return object.constructor === Object3D ? 'Object3D' : null;
@@ -163,6 +169,9 @@ function encodeMaterial( material, meta ) {
163
169
  for ( const [ key, value ] of Object.entries( material ) ) if ( value?.isColor ) json.exactColors[ key ] = [ value.r, value.g, value.b ];
164
170
  // MaterialLoader sets `reflectivity` after `ior`, and that setter rewrites ior with rounding.
165
171
  if ( material.ior !== undefined ) json.exactIor = material.ior;
172
+ // Engine properties three.js does not know, so toJSON drops them.
173
+ if ( material.diffuseTransmission !== undefined ) json.diffuseTransmission = material.diffuseTransmission;
174
+ for ( const key of ENGINE_TEXTURES ) if ( material[ key ]?.isTexture ) ( json.engineTextures ??= {} )[ key ] = material[ key ].uuid;
166
175
  return json;
167
176
 
168
177
  }
@@ -234,6 +243,14 @@ export function encodeSceneGraph( root, { environment = null, animations = [], s
234
243
 
235
244
  }
236
245
 
246
+ if ( object.isLight ) {
247
+
248
+ node.color = object.color.toArray();
249
+ for ( const prop of LIGHT_PROPS ) if ( object[ prop ] !== undefined ) node[ prop ] = object[ prop ];
250
+ if ( object.target ) node.target = object.target.uuid;
251
+
252
+ }
253
+
237
254
  const index = nodes.push( node ) - 1;
238
255
  for ( const child of object.children ) visit( child, index );
239
256
 
@@ -419,8 +436,16 @@ export async function decodeSceneGraph( manifest, data, { loadTexture } ) {
419
436
  for ( const json of manifest.materials ) {
420
437
 
421
438
  const material = materialLoader.parse( json );
422
- for ( const [ key, rgb ] of Object.entries( json.exactColors ?? {} ) ) material[ key ]?.setRGB( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] );
439
+ for ( const [ key, rgb ] of Object.entries( json.exactColors ?? {} ) ) {
440
+
441
+ if ( key === 'diffuseTransmissionColor' ) material[ key ] = new Color();
442
+ material[ key ]?.setRGB( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] );
443
+
444
+ }
445
+
423
446
  if ( json.exactIor !== undefined ) material.ior = json.exactIor;
447
+ if ( json.diffuseTransmission !== undefined ) material.diffuseTransmission = json.diffuseTransmission;
448
+ for ( const [ key, uuid ] of Object.entries( json.engineTextures ?? {} ) ) if ( textures[ uuid ] ) material[ key ] = textures[ uuid ];
424
449
  materials[ json.uuid ] = material;
425
450
 
426
451
  }
@@ -462,6 +487,9 @@ export async function decodeSceneGraph( manifest, data, { loadTexture } ) {
462
487
  case 'PerspectiveCamera': object = new PerspectiveCamera(); break;
463
488
  case 'OrthographicCamera': object = new OrthographicCamera(); break;
464
489
  case 'Group': object = new Group(); break;
490
+ case 'DirectionalLight': object = new DirectionalLight(); break;
491
+ case 'PointLight': object = new PointLight(); break;
492
+ case 'SpotLight': object = new SpotLight(); break;
465
493
  default: object = new Object3D();
466
494
 
467
495
  }
@@ -487,11 +515,26 @@ export async function decodeSceneGraph( manifest, data, { loadTexture } ) {
487
515
 
488
516
  }
489
517
 
518
+ if ( object.isLight ) {
519
+
520
+ object.color.fromArray( node.color );
521
+ for ( const prop of LIGHT_PROPS ) if ( node[ prop ] !== undefined ) object[ prop ] = node[ prop ];
522
+
523
+ }
524
+
490
525
  built.push( object );
491
526
  if ( node.parent >= 0 ) built[ node.parent ].add( object );
492
527
 
493
528
  }
494
529
 
530
+ // A light aims at its target, which is stored as an object of its own.
531
+ const byUuid = new Map( built.map( ( object ) => [ object.uuid, object ] ) );
532
+ manifest.nodes.forEach( ( node, i ) => {
533
+
534
+ if ( node.target && byUuid.has( node.target ) ) built[ i ].target = byUuid.get( node.target );
535
+
536
+ } );
537
+
495
538
  return {
496
539
  root: built[ 0 ],
497
540
  environment: manifest.environment ? textures[ manifest.environment ] : null,
@@ -1,4 +1,4 @@
1
- import { ENGINE_AREAS } from './StorageManager.js';
1
+ import { ENGINE_AREAS } from './areas.js';
2
2
 
3
3
  const WRITE_PIECE_BYTES = 32 * 1024 * 1024;
4
4
  const FILE = 'chunks.bin';
@@ -3,11 +3,9 @@ import { acquireLock, heldLockNames } from './locks.js';
3
3
  import { entryIdFor } from './identity.js';
4
4
  import { Emitter } from './events.js';
5
5
  import { InlineTransport } from './inlineTransport.js';
6
+ import { STORAGE_KIND, ENGINE_AREAS } from './areas.js';
6
7
 
7
- export const STORAGE_KIND = Object.freeze( { CACHE: 'cache', USER: 'user', SCRATCH: 'scratch' } );
8
-
9
- /** Areas the engine itself writes; hosts define their own with `defineArea`. */
10
- export const ENGINE_AREAS = Object.freeze( { DOWNLOADS: 'downloads', ARCHIVES: 'archives', SCENES: 'scenes', CDF: 'cdf', SPILL: 'spill' } );
8
+ export { STORAGE_KIND, ENGINE_AREAS };
11
9
 
12
10
  const META = 'meta.json';
13
11
  const META_VERSION = 1;
@@ -17,6 +15,8 @@ const MAX_DEFAULT_BUDGET = 100 * 1024 ** 3;
17
15
  const FREE_MARGIN = 64 * 1024 ** 2;
18
16
  const STREAM_BATCH = 8 * 1024 ** 2;
19
17
  const STREAM_WINDOW = 4;
18
+ // Other tabs write too, so the cache total kept in memory is listed again at most this often.
19
+ const CACHE_TOTAL_TTL_MS = 30_000;
20
20
 
21
21
  const encoder = new TextEncoder();
22
22
 
@@ -152,6 +152,7 @@ export class EntryWriter {
152
152
  this._sizes = { ...sizes };
153
153
  this._append = append;
154
154
  this._done = false;
155
+ this._committedBytes = append ? Object.values( sizes ).reduce( ( n, b ) => n + b, 0 ) : 0;
155
156
 
156
157
  }
157
158
 
@@ -313,6 +314,7 @@ export class EntryWriter {
313
314
 
314
315
  const { args, list } = transferable( JSON.stringify( meta ), { copy: false } );
315
316
  await this._transport.call( 'writeFile', { path: this._path, name: META, ...args }, list );
317
+ if ( this.area.kind === STORAGE_KIND.CACHE ) this.area.manager._countCacheBytes( meta.bytes - this._committedBytes );
316
318
  this.area.manager._changed( this.area.name );
317
319
  return meta;
318
320
 
@@ -700,6 +702,7 @@ export class StorageManager extends Emitter {
700
702
  this._transport = transport;
701
703
  this._areas = new Map();
702
704
  this._budget = null;
705
+ this._cacheTotal = null;
703
706
 
704
707
  for ( const name of Object.values( ENGINE_AREAS ) ) this.defineArea( name, { kind: name === ENGINE_AREAS.SPILL ? STORAGE_KIND.SCRATCH : STORAGE_KIND.CACHE } );
705
708
 
@@ -775,6 +778,40 @@ export class StorageManager extends Emitter {
775
778
 
776
779
  }
777
780
 
781
+ /**
782
+ * What cache areas hold together. Every new download asks, and a listing reads every entry's
783
+ * metadata, so it is listed once and kept current by this manager's own commits.
784
+ */
785
+ async _cacheBytes() {
786
+
787
+ let total = this._cacheTotal;
788
+ if ( ! total || Date.now() - total.at > CACHE_TOTAL_TTL_MS ) {
789
+
790
+ total = this._cacheTotal = { at: Date.now(), bytes: 0, ready: null };
791
+ total.ready = this._cacheEntries().then( ( entries ) => {
792
+
793
+ total.bytes += entries.reduce( ( n, e ) => n + ( e.meta.bytes ?? 0 ), 0 );
794
+
795
+ } );
796
+ total.ready.catch( () => {
797
+
798
+ if ( this._cacheTotal === total ) this._cacheTotal = null;
799
+
800
+ } );
801
+
802
+ }
803
+
804
+ await total.ready;
805
+ return total.bytes;
806
+
807
+ }
808
+
809
+ _countCacheBytes( bytes ) {
810
+
811
+ if ( this._cacheTotal ) this._cacheTotal.bytes += bytes;
812
+
813
+ }
814
+
778
815
  async _cacheEntries() {
779
816
 
780
817
  const out = [];
@@ -816,8 +853,8 @@ export class StorageManager extends Emitter {
816
853
 
817
854
  const entries = await this._cacheEntries();
818
855
  const budget = await this.budget();
819
- let cacheBytes = entries.reduce( ( n, e ) => n + ( e.meta.bytes ?? 0 ), 0 );
820
- const target = freeBytes ?? Math.max( 0, cacheBytes - budget );
856
+ const total = this._cacheTotal = { at: Date.now(), bytes: entries.reduce( ( n, e ) => n + ( e.meta.bytes ?? 0 ), 0 ), ready: null };
857
+ const target = freeBytes ?? Math.max( 0, total.bytes - budget );
821
858
  if ( target <= 0 ) return 0;
822
859
 
823
860
  const held = await heldLockNames();
@@ -832,7 +869,7 @@ export class StorageManager extends Emitter {
832
869
  if ( await area.removeById( meta.id ) ) {
833
870
 
834
871
  freed += meta.bytes ?? 0;
835
- cacheBytes -= meta.bytes ?? 0;
872
+ total.bytes -= meta.bytes ?? 0;
836
873
 
837
874
  }
838
875
 
@@ -855,8 +892,7 @@ export class StorageManager extends Emitter {
855
892
  const free = bytes + FREE_MARGIN - ( quota - usage );
856
893
  if ( kind !== STORAGE_KIND.CACHE ) return { free, budget: 0 };
857
894
 
858
- const cacheBytes = ( await this._cacheEntries() ).reduce( ( n, e ) => n + ( e.meta.bytes ?? 0 ), 0 );
859
- return { free, budget: cacheBytes + bytes - await this.budget() };
895
+ return { free, budget: await this._cacheBytes() + bytes - await this.budget() };
860
896
 
861
897
  };
862
898
 
@@ -0,0 +1,4 @@
1
+ export const STORAGE_KIND = Object.freeze( { CACHE: 'cache', USER: 'user', SCRATCH: 'scratch' } );
2
+
3
+ /** Areas the engine itself writes; hosts define their own with `defineArea`. */
4
+ export const ENGINE_AREAS = Object.freeze( { DOWNLOADS: 'downloads', ARCHIVES: 'archives', SCENES: 'scenes', CDF: 'cdf', SPILL: 'spill' } );