rayzee 9.0.0 → 9.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 (94) hide show
  1. package/README.md +139 -8
  2. package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
  3. package/dist/{NeuralSuperRes-CQLCLpcC.js → NeuralSuperRes-CbNvQjCw.js} +3 -2
  4. package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
  5. package/dist/rayzee.es.js +10344 -5334
  6. package/dist/rayzee.umd.js +265 -265
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +4 -0
  9. package/src/EngineDefaults.js +38 -56
  10. package/src/EngineEvents.js +3 -0
  11. package/src/EngineIssues.js +6 -0
  12. package/src/Headless.js +3 -1
  13. package/src/Passes/AIUpscaler.js +2 -1
  14. package/src/Passes/OIDNDenoiser.js +3 -6
  15. package/src/PathTracerApp.js +290 -19
  16. package/src/Processor/ArchiveCache.js +120 -0
  17. package/src/Processor/ArchiveReader.js +179 -32
  18. package/src/Processor/AssetLoader.js +514 -76
  19. package/src/Processor/AtmosphereModel.js +465 -0
  20. package/src/Processor/BVHBuilder.js +4 -1
  21. package/src/Processor/BVHLeafFold.js +100 -0
  22. package/src/Processor/BVHRefitter.js +98 -214
  23. package/src/Processor/ChunkedRecords.js +189 -4
  24. package/src/Processor/EmissiveTriangleBuilder.js +8 -0
  25. package/src/Processor/GeometryExtractor.js +238 -40
  26. package/src/Processor/InstanceTable.js +55 -37
  27. package/src/Processor/PBRT/PBRTCurves.js +104 -27
  28. package/src/Processor/PBRT/PBRTParser.js +132 -22
  29. package/src/Processor/PBRT/PBRTSceneBuilder.js +192 -49
  30. package/src/Processor/PBRT/buffers.js +27 -0
  31. package/src/Processor/PBRT/index.js +55 -15
  32. package/src/Processor/PackedRayBuffer.js +10 -13
  33. package/src/Processor/ParallelBVHBuilder.js +36 -15
  34. package/src/Processor/PhysicalSky.js +308 -0
  35. package/src/Processor/QueueManager.js +1 -0
  36. package/src/Processor/SceneProcessor.js +1132 -181
  37. package/src/Processor/SplitBLAS.js +249 -0
  38. package/src/Processor/SunPosition.js +41 -0
  39. package/src/Processor/TextureCreator.js +21 -0
  40. package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
  41. package/src/Processor/Workers/BVHWorker.js +17 -13
  42. package/src/Processor/ZipReader.js +207 -0
  43. package/src/RenderSettings.js +38 -0
  44. package/src/SceneState/SceneState.js +367 -0
  45. package/src/SceneState/portable.js +52 -0
  46. package/src/Stages/NormalDepth.js +7 -6
  47. package/src/Stages/PathTracer.js +108 -4
  48. package/src/Stages/PathTracerStage.js +251 -104
  49. package/src/Storage/BLASCache.js +190 -0
  50. package/src/Storage/CDFCache.js +77 -0
  51. package/src/Storage/DownloadCache.js +266 -0
  52. package/src/Storage/GeometrySpill.js +217 -0
  53. package/src/Storage/SceneGraphCodec.js +503 -0
  54. package/src/Storage/SpillStore.js +112 -0
  55. package/src/Storage/StorageManager.js +934 -0
  56. package/src/Storage/StorageOps.js +137 -0
  57. package/src/Storage/StorageWorker.js +37 -0
  58. package/src/Storage/events.js +29 -0
  59. package/src/Storage/identity.js +77 -0
  60. package/src/Storage/index.js +9 -0
  61. package/src/Storage/inlineTransport.js +24 -0
  62. package/src/Storage/locks.js +99 -0
  63. package/src/Storage/openStorage.js +87 -0
  64. package/src/Storage/sceneCachePolicy.js +12 -0
  65. package/src/Storage/shared.js +9 -0
  66. package/src/Storage/transport.js +62 -0
  67. package/src/TSL/Atmosphere.js +684 -0
  68. package/src/TSL/BVHTraversal.js +288 -239
  69. package/src/TSL/Common.js +11 -2
  70. package/src/TSL/Displacement.js +6 -6
  71. package/src/TSL/EmissiveSampling.js +5 -5
  72. package/src/TSL/EnvironmentCDF.js +176 -0
  73. package/src/TSL/GenerateKernel.js +3 -3
  74. package/src/TSL/HitFacet.js +4 -4
  75. package/src/TSL/LightsDirect.js +8 -8
  76. package/src/TSL/LightsSampling.js +55 -37
  77. package/src/TSL/Random.js +1 -0
  78. package/src/TSL/ShadeKernel.js +75 -19
  79. package/src/TSL/Sun.js +40 -0
  80. package/src/TSL/TextureSampling.js +6 -7
  81. package/src/index.js +18 -0
  82. package/src/managers/CameraManager.js +169 -1
  83. package/src/managers/EnvironmentManager.js +263 -33
  84. package/src/managers/GoboManager.js +19 -8
  85. package/src/managers/IESManager.js +3 -3
  86. package/src/managers/LightManager.js +98 -5
  87. package/src/managers/MaterialDataManager.js +22 -2
  88. package/src/managers/TransformManager.js +26 -0
  89. package/src/managers/UniformManager.js +5 -3
  90. package/src/managers/VideoRenderManager.js +3 -1
  91. package/src/managers/timeline/CameraTrack.js +22 -0
  92. package/src/managers/timeline/TimelineManager.js +14 -0
  93. package/src/version.js +3 -0
  94. package/src/Processor/ProceduralSky.js +0 -291
@@ -17,6 +17,7 @@ import { packMaterial, UNIT_RANGE_PROPERTIES, clampUnit } from '../Processor/Mat
17
17
  import { resolveMaterialTextures, MATERIAL_VALUE_SOURCE } from '../Processor/GeometryExtractor.js';
18
18
  import { convertLinearTriple, convertLinearTriples, getWorkingMatrixSpace } from '../Color/WorkingMatrix.js';
19
19
  import { createLogger, fmt } from '../utils/Logger.js';
20
+ import { toPortable } from '../SceneState/portable.js';
20
21
 
21
22
  /** Material buffers already moved into the working space, so a re-init cannot convert twice. */
22
23
  const convertedBuffers = new WeakSet();
@@ -30,6 +31,9 @@ const TRI_FLAGS_OFFSET = T.MATERIAL_FLAGS_OFFSET;
30
31
  // Material properties that affect the shadow-ray opaque-blocker flag.
31
32
  const BLOCKER_PROPS = new Set( [ 'transmission', 'transparent', 'opacity', 'alphaMode' ] );
32
33
 
34
+ /** Material properties whose edit rewrites triangle records (side, shadow blocker, emitters). */
35
+ export const TRIANGLE_PATCH_PROPERTIES = new Set( [ 'side', ...BLOCKER_PROPS, 'emissive', 'emissiveIntensity' ] );
36
+
33
37
  // Map slot → sRGB pool (true) or linear pool.
34
38
  const TEXTURE_POOLS = [
35
39
  [ 'map', true ], [ 'emissiveMap', true ], [ 'sheenColorMap', true ], [ 'specularColorMap', true ],
@@ -98,7 +102,7 @@ export class MaterialDataManager {
98
102
  */
99
103
  this.callbacks = {};
100
104
 
101
- // Per material index: createMaterialObject's sources, and properties a host set since.
105
+ // Per material index: createMaterialObject's sources, and properties a host set since (name → portable value).
102
106
  this._sources = [];
103
107
  this._hostSet = [];
104
108
 
@@ -310,6 +314,22 @@ export class MaterialDataManager {
310
314
 
311
315
  }
312
316
 
317
+ /**
318
+ * Every property a host set, per material, as portable values (see SceneState/portable.js).
319
+ * @returns {Array<{index: number, props: Object<string, *>}>}
320
+ */
321
+ serializeHostEdits() {
322
+
323
+ const out = [];
324
+ this._hostSet.forEach( ( props, index ) => {
325
+
326
+ if ( props?.size ) out.push( { index, props: Object.fromEntries( props ) } );
327
+
328
+ } );
329
+ return out;
330
+
331
+ }
332
+
313
333
  /**
314
334
  * Update a single material property in the storage buffer.
315
335
  * @param {number} materialIndex
@@ -481,7 +501,7 @@ export class MaterialDataManager {
481
501
  }
482
502
 
483
503
  this.materialStorageAttr.needsUpdate = true;
484
- ( this._hostSet[ materialIndex ] ??= new Set() ).add( property );
504
+ ( this._hostSet[ materialIndex ] ??= new Map() ).set( property, toPortable( value ) );
485
505
 
486
506
  // Recompute triangle-data opaque-blocker flag when any input to it changes.
487
507
  if ( BLOCKER_PROPS.has( property ) ) {
@@ -14,6 +14,13 @@ import { EngineEvents } from '../EngineEvents.js';
14
14
  // does not mutate its arguments, so this can be a shared constant.
15
15
  const FORWARD = new Vector3( 0, 0, - 1 );
16
16
 
17
+ const rootScene = object => {
18
+
19
+ while ( object.parent ) object = object.parent;
20
+ return object.isScene ? object : null;
21
+
22
+ };
23
+
17
24
  export class TransformManager {
18
25
 
19
26
  constructor( { camera, canvas, orbitControls, app } ) {
@@ -33,6 +40,8 @@ export class TransformManager {
33
40
  this._meshes = null;
34
41
  this._refitInFlight = false;
35
42
  this._baselineComputed = false;
43
+ // Objects the gizmo moved, so a saved session can put them back.
44
+ this._moved = new Set();
36
45
 
37
46
  // Light transform state — spot/directional lights aim via a separate
38
47
  // `.target` Object3D rather than their own rotation (see attach()).
@@ -56,6 +65,21 @@ export class TransformManager {
56
65
  setMeshData( meshes ) {
57
66
 
58
67
  this._meshes = meshes;
68
+ for ( const object of this._moved ) if ( ! rootScene( object ) ) this._moved.delete( object );
69
+
70
+ }
71
+
72
+ /** Objects the gizmo has moved that are still in the scene (lights are saved with the lights). */
73
+ get movedObjects() {
74
+
75
+ return [ ...this._moved ].filter( object => rootScene( object ) );
76
+
77
+ }
78
+
79
+ /** Counts an object as moved, as a gizmo drag would — for a restored session. */
80
+ noteMoved( object ) {
81
+
82
+ if ( object && ! object.isLight ) this._moved.add( object );
59
83
 
60
84
  }
61
85
 
@@ -335,6 +359,7 @@ export class TransformManager {
335
359
  // instead costs a pass over every vertex, and corrupts any other object sharing
336
360
  // this geometry.
337
361
  this._app.updateMeshTransforms( affectedIndices );
362
+ this._moved.add( this._attached );
338
363
 
339
364
  } catch ( err ) {
340
365
 
@@ -393,6 +418,7 @@ export class TransformManager {
393
418
  this._controls.dispose();
394
419
 
395
420
  this._meshes = null;
421
+ this._moved.clear();
396
422
  this._baselineComputed = false;
397
423
  this._tempForward = null;
398
424
  this._lightTargetDistance = null;
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { uniform, uniformArray } from 'three/tsl';
9
- import { Vector2, Matrix4, Vector3, Color, MathUtils } from 'three';
9
+ import { Vector2, Matrix4, Vector3, Vector4, Color, MathUtils } from 'three';
10
10
  import { samplingTechniqueUniform } from '../TSL/Random.js';
11
11
  import { ENGINE_DEFAULTS as DEFAULT_STATE, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
12
12
 
@@ -249,9 +249,11 @@ export class UniformManager {
249
249
  ub( 'enableGroundCatcher', DEFAULT_STATE.enableGroundCatcher );
250
250
  u( 'groundCatcherHeight', DEFAULT_STATE.groundCatcherHeight, 'float' );
251
251
 
252
- // Sun parameters
252
+ // The physical sky's sun (TSL/Sun.js): world direction, disc-average radiance, and
253
+ // ( cos half-angle, solid angle, 1 / sin² half-angle, horizon dip sin )
253
254
  u( 'sunDirection', new Vector3( 0, 1, 0 ), 'vec3' );
254
- u( 'sunAngularSize', 0.0087, 'float' );
255
+ u( 'sunRadiance', new Vector3(), 'vec3' );
256
+ u( 'sunParams', new Vector4( 1, 0, 0, 0 ), 'vec4' );
255
257
  ub( 'hasSun', false );
256
258
 
257
259
  // Lighting
@@ -30,6 +30,7 @@ export class VideoRenderManager {
30
30
  * @param {boolean} [options.enableOIDN=true] - Run OIDN denoiser per frame
31
31
  * @param {number} [options.speed=1] - Playback speed multiplier (maps video time to animation time)
32
32
  * @param {number} [options.totalFrames] - Override total frame count (for looped animations)
33
+ * @param {number} [options.startFrame=0] - First frame to render: a resumed export skips those it has
33
34
  * @param {import('./timeline/TimelineManager.js').TimelineManager} [options.timeline] - Keyframes to
34
35
  * play in video time; without a clip the video lasts as long as they do
35
36
  * @param {Function} [options.onFrame] - async (ImageBitmap, frameIndex, totalFrames) => void
@@ -45,6 +46,7 @@ export class VideoRenderManager {
45
46
  samplesPerFrame = PRODUCTION_RENDER_CONFIG.maxSamples,
46
47
  enableOIDN = true,
47
48
  timeline = null,
49
+ startFrame = 0,
48
50
  onFrame,
49
51
  onProgress,
50
52
  onComplete,
@@ -94,7 +96,7 @@ export class VideoRenderManager {
94
96
 
95
97
  try {
96
98
 
97
- for ( let i = 0; i < totalFrames; i ++ ) {
99
+ for ( let i = Math.max( 0, startFrame ); i < totalFrames; i ++ ) {
98
100
 
99
101
  if ( this._cancelled ) break;
100
102
 
@@ -133,6 +133,28 @@ export class CameraTrack {
133
133
 
134
134
  }
135
135
 
136
+ /** The keys as plain data, for a saved session. */
137
+ serialize() {
138
+
139
+ return this.keys.map( ( { time, position, target, fov, orthoHeight } ) => ( { time, position: position.toArray(), target: target.toArray(), fov, orthoHeight } ) );
140
+
141
+ }
142
+
143
+ /** Replaces every key with the ones {@link serialize} described. */
144
+ restore( keys ) {
145
+
146
+ this.keys = ( keys ?? [] ).map( key => ( {
147
+ id: this._nextId ++,
148
+ time: key.time,
149
+ position: new Vector3().fromArray( key.position ),
150
+ target: new Vector3().fromArray( key.target ),
151
+ fov: key.fov,
152
+ orthoHeight: key.orthoHeight,
153
+ } ) );
154
+ this._changed();
155
+
156
+ }
157
+
136
158
  dispose() {
137
159
 
138
160
  this._cameraManager = this._onChange = null;
@@ -105,6 +105,20 @@ export class TimelineManager extends EventDispatcher {
105
105
 
106
106
  }
107
107
 
108
+ /** Every track's keys as plain data, for a saved session. */
109
+ serialize() {
110
+
111
+ return { camera: this.camera.serialize() };
112
+
113
+ }
114
+
115
+ restore( state ) {
116
+
117
+ this.stop();
118
+ this.camera.restore( state?.camera );
119
+
120
+ }
121
+
108
122
  /** Drops every key: they belonged to the scene being replaced. */
109
123
  clear() {
110
124
 
package/src/version.js ADDED
@@ -0,0 +1,3 @@
1
+ import { version } from '../package.json';
2
+
3
+ export const VERSION = version;
@@ -1,291 +0,0 @@
1
- import {
2
- RGBAFormat, FloatType, LinearFilter, RepeatWrapping, ClampToEdgeWrapping,
3
- EquirectangularReflectionMapping, LinearSRGBColorSpace, DataTexture
4
- } from 'three';
5
-
6
- /**
7
- * ProceduralSky
8
- *
9
- * CPU-based Preetham atmospheric scattering sky generator.
10
- * Produces a DataTexture directly — no render targets, no GPU readback,
11
- * no resource lifecycle issues with the WebGPU backend.
12
- *
13
- * For a 512×256 texture this takes ~5-10ms on CPU, which is negligible
14
- * since it only runs on parameter change (not per-frame).
15
- *
16
- * Public API matches ProceduralSkyRenderer: render(params) → texture.
17
- */
18
-
19
- // ── Constants (from preetham_sky.glsl) ──
20
-
21
- const PI = Math.PI;
22
- const TWO_PI = Math.PI * 2;
23
- const E = Math.E;
24
-
25
- const TOTAL_RAYLEIGH = [ 5.804542996261093e-6, 1.3562911419845635e-5, 3.0265902468824876e-5 ];
26
- const MIE_CONST = [ 1.8399918514433978e14, 2.7798023919660528e14, 4.0790479543861094e14 ];
27
-
28
- const CUTOFF_ANGLE = 1.6110731556870734;
29
- const STEEPNESS = 1.5;
30
- const EE_CONST = 1000.0;
31
-
32
- const RAYLEIGH_ZENITH_LENGTH = 8400.0;
33
- const MIE_ZENITH_LENGTH = 1250.0;
34
- const SUN_ANGULAR_DIAMETER_COS = 0.9999566769464484;
35
-
36
- const THREE_OVER_SIXTEEN_PI = 0.05968310365946075;
37
- const ONE_OVER_FOUR_PI = 0.07957747154594767;
38
-
39
- // ── CPU Math ──
40
-
41
- function computeSunIntensity( zenithAngleCos ) {
42
-
43
- const clamped = Math.max( - 1.0, Math.min( 1.0, zenithAngleCos ) );
44
- return EE_CONST * Math.max( 0.0, 1.0 - Math.pow( E, - ( CUTOFF_ANGLE - Math.acos( clamped ) ) / STEEPNESS ) );
45
-
46
- }
47
-
48
- function totalMie( T ) {
49
-
50
- const c = ( 0.2 * T ) * 10e-18;
51
- return [ 0.434 * c * MIE_CONST[ 0 ], 0.434 * c * MIE_CONST[ 1 ], 0.434 * c * MIE_CONST[ 2 ] ];
52
-
53
- }
54
-
55
- function rayleighPhase( cosTheta ) {
56
-
57
- return THREE_OVER_SIXTEEN_PI * ( 1.0 + Math.pow( cosTheta, 2.0 ) );
58
-
59
- }
60
-
61
- function hgPhase( cosTheta, g ) {
62
-
63
- const g2 = g * g;
64
- return ONE_OVER_FOUR_PI * ( ( 1.0 - g2 ) / Math.pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ) );
65
-
66
- }
67
-
68
- function dot3( a, b ) {
69
-
70
- return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ];
71
-
72
- }
73
-
74
- function normalize3( v ) {
75
-
76
- const len = Math.sqrt( v[ 0 ] * v[ 0 ] + v[ 1 ] * v[ 1 ] + v[ 2 ] * v[ 2 ] );
77
- if ( len === 0 ) return [ 0, 0, 0 ];
78
- return [ v[ 0 ] / len, v[ 1 ] / len, v[ 2 ] / len ];
79
-
80
- }
81
-
82
- function computePreethamPixel( u, v, sunDir, sunIntensity, rayleighDensity, mieDensity, mieAnisotropy, turbidity ) {
83
-
84
- // Equirectangular → direction
85
- const theta = ( 1.0 - v ) * PI;
86
- const phi = ( u - 0.5 ) * TWO_PI;
87
- const sinTheta = Math.sin( theta );
88
- const direction = normalize3( [
89
- sinTheta * Math.sin( phi ),
90
- Math.cos( theta ),
91
- sinTheta * Math.cos( phi )
92
- ] );
93
-
94
- const vSunDirection = normalize3( sunDir );
95
-
96
- // Sun intensity from zenith angle
97
- const vSunE = computeSunIntensity( vSunDirection[ 1 ] ) * sunIntensity;
98
-
99
- // Sun fade near horizon
100
- const vSunfade = 1.0 - Math.max( 0.0, Math.min( 1.0, 1.0 - Math.exp( vSunDirection[ 1 ] / 450000.0 ) ) );
101
-
102
- // Rayleigh coefficient
103
- const rayleighCoeff = rayleighDensity - ( 1.0 * ( 1.0 - vSunfade ) );
104
- const vBetaR = [ TOTAL_RAYLEIGH[ 0 ] * rayleighCoeff, TOTAL_RAYLEIGH[ 1 ] * rayleighCoeff, TOTAL_RAYLEIGH[ 2 ] * rayleighCoeff ];
105
-
106
- // Mie coefficient
107
- const mieCoeff = totalMie( turbidity );
108
- const vBetaM = [ mieCoeff[ 0 ] * mieDensity, mieCoeff[ 1 ] * mieDensity, mieCoeff[ 2 ] * mieDensity ];
109
-
110
- // Zenith angle and optical path length
111
- const fragZenithAngle = Math.acos( Math.max( 0.0, direction[ 1 ] ) );
112
- const cosZenith = Math.cos( fragZenithAngle );
113
- const zenithDeg = fragZenithAngle * ( 180.0 / PI );
114
- const inverseFactor = cosZenith + 0.15 * Math.pow( 93.885 - zenithDeg, - 1.253 );
115
- const sR = RAYLEIGH_ZENITH_LENGTH / inverseFactor;
116
- const sM = MIE_ZENITH_LENGTH / inverseFactor;
117
-
118
- // Extinction (Beer's law)
119
- const Fex = [
120
- Math.exp( - ( vBetaR[ 0 ] * sR + vBetaM[ 0 ] * sM ) ),
121
- Math.exp( - ( vBetaR[ 1 ] * sR + vBetaM[ 1 ] * sM ) ),
122
- Math.exp( - ( vBetaR[ 2 ] * sR + vBetaM[ 2 ] * sM ) )
123
- ];
124
-
125
- // Scattering angle
126
- const cosViewSun = dot3( direction, vSunDirection );
127
-
128
- // Phase functions
129
- const rPhase = rayleighPhase( cosViewSun * 0.5 + 0.5 );
130
- const betaRTheta = [ vBetaR[ 0 ] * rPhase, vBetaR[ 1 ] * rPhase, vBetaR[ 2 ] * rPhase ];
131
-
132
- const mPhase = hgPhase( cosViewSun, mieAnisotropy );
133
- const betaMTheta = [ vBetaM[ 0 ] * mPhase, vBetaM[ 1 ] * mPhase, vBetaM[ 2 ] * mPhase ];
134
-
135
- // Inscattered light
136
- const betaSum = [ betaRTheta[ 0 ] + betaMTheta[ 0 ], betaRTheta[ 1 ] + betaMTheta[ 1 ], betaRTheta[ 2 ] + betaMTheta[ 2 ] ];
137
- const betaRplusM = [ vBetaR[ 0 ] + vBetaM[ 0 ], vBetaR[ 1 ] + vBetaM[ 1 ], vBetaR[ 2 ] + vBetaM[ 2 ] ];
138
-
139
- const Lin = [
140
- Math.pow( vSunE * ( betaSum[ 0 ] / betaRplusM[ 0 ] ) * ( 1.0 - Fex[ 0 ] ), 1.5 ),
141
- Math.pow( vSunE * ( betaSum[ 1 ] / betaRplusM[ 1 ] ) * ( 1.0 - Fex[ 1 ] ), 1.5 ),
142
- Math.pow( vSunE * ( betaSum[ 2 ] / betaRplusM[ 2 ] ) * ( 1.0 - Fex[ 2 ] ), 1.5 )
143
- ];
144
-
145
- // Sunset mix
146
- const sunsetFactor = Math.max( 0.0, Math.min( 1.0, 1.0 - Math.pow( 1.0 - vSunDirection[ 1 ], 5.0 ) ) );
147
- const sunsetColor = [
148
- Math.sqrt( vSunE * ( betaSum[ 0 ] / betaRplusM[ 0 ] ) * Fex[ 0 ] ),
149
- Math.sqrt( vSunE * ( betaSum[ 1 ] / betaRplusM[ 1 ] ) * Fex[ 1 ] ),
150
- Math.sqrt( vSunE * ( betaSum[ 2 ] / betaRplusM[ 2 ] ) * Fex[ 2 ] )
151
- ];
152
-
153
- const mixW = 1.0 - sunsetFactor;
154
- for ( let i = 0; i < 3; i ++ ) {
155
-
156
- const mixVal = 1.0 * ( 1.0 - mixW ) + sunsetColor[ i ] * mixW;
157
- Lin[ i ] *= mixVal;
158
-
159
- }
160
-
161
- // Base luminance + sun disk
162
- const L0 = [ 0.1 * Fex[ 0 ], 0.1 * Fex[ 1 ], 0.1 * Fex[ 2 ] ];
163
- const sundisk = smoothstep( SUN_ANGULAR_DIAMETER_COS, SUN_ANGULAR_DIAMETER_COS + 0.00002, cosViewSun );
164
- for ( let i = 0; i < 3; i ++ ) {
165
-
166
- L0[ i ] += vSunE * 19000.0 * Fex[ i ] * sundisk;
167
-
168
- }
169
-
170
- // Final color — clamp to half-float max (65504) to avoid overflow
171
- // in the CDF builder's half-float conversion
172
- const MAX_HDR = 65504.0;
173
- return [
174
- Math.min( ( Lin[ 0 ] + L0[ 0 ] ) * 0.04, MAX_HDR ),
175
- Math.min( ( Lin[ 1 ] + L0[ 1 ] ) * 0.04 + 0.0003, MAX_HDR ),
176
- Math.min( ( Lin[ 2 ] + L0[ 2 ] ) * 0.04 + 0.00075, MAX_HDR ),
177
- 1.0
178
- ];
179
-
180
- }
181
-
182
- function smoothstep( edge0, edge1, x ) {
183
-
184
- const t = Math.max( 0.0, Math.min( 1.0, ( x - edge0 ) / ( edge1 - edge0 ) ) );
185
- return t * t * ( 3.0 - 2.0 * t );
186
-
187
- }
188
-
189
- // ── Renderer Class ──
190
-
191
- export class ProceduralSky {
192
-
193
- constructor( width = 512, height = 256 ) {
194
-
195
- this.width = width;
196
- this.height = height;
197
- this.lastRenderTime = 0;
198
-
199
- // Pre-allocate pixel buffer and DataTexture (reused across renders)
200
- this._pixels = new Float32Array( width * height * 4 );
201
- this._texture = new DataTexture( this._pixels, width, height, RGBAFormat, FloatType );
202
- this._texture.mapping = EquirectangularReflectionMapping;
203
- this._texture.colorSpace = LinearSRGBColorSpace;
204
- this._texture.minFilter = LinearFilter;
205
- this._texture.magFilter = LinearFilter;
206
- this._texture.wrapS = RepeatWrapping;
207
- this._texture.wrapT = ClampToEdgeWrapping;
208
- this._texture.generateMipmaps = false;
209
-
210
- }
211
-
212
- /**
213
- * Generate Preetham sky into a reusable DataTexture.
214
- * @param {Object} params - Sky parameters
215
- * @returns {DataTexture} Equirectangular sky texture with CPU data
216
- */
217
- render( params ) {
218
-
219
- const startTime = performance.now();
220
-
221
- const sunDir = [ params.sunDirection.x, params.sunDirection.y, params.sunDirection.z ];
222
- const sunIntensity = params.sunIntensity || 1.0;
223
- const rayleighDensity = params.rayleighDensity || 2.0;
224
- const mieDensity = params.mieDensity || 0.005;
225
- const mieAnisotropy = params.mieAnisotropy || 0.8;
226
- const turbidity = params.turbidity || 2.0;
227
-
228
- const { width, height } = this;
229
- const pixels = this._pixels;
230
-
231
- for ( let y = 0; y < height; y ++ ) {
232
-
233
- const v = ( y + 0.5 ) / height;
234
-
235
- for ( let x = 0; x < width; x ++ ) {
236
-
237
- const u = ( x + 0.5 ) / width;
238
- const color = computePreethamPixel( u, v, sunDir, sunIntensity, rayleighDensity, mieDensity, mieAnisotropy, turbidity );
239
-
240
- const idx = ( y * width + x ) * 4;
241
- pixels[ idx ] = color[ 0 ];
242
- pixels[ idx + 1 ] = color[ 1 ];
243
- pixels[ idx + 2 ] = color[ 2 ];
244
- pixels[ idx + 3 ] = color[ 3 ];
245
-
246
- }
247
-
248
- }
249
-
250
- this._texture.needsUpdate = true;
251
- // Fresh linear Rec.709 pixels in a reused texture. Colour management records which space a
252
- // texture's pixels hold so it never converts them twice; left in place, that record would
253
- // claim these were already converted and they never would be.
254
- delete this._texture.userData?.__rayzeeColorSpace;
255
- this.lastRenderTime = performance.now() - startTime;
256
-
257
- return this._texture;
258
-
259
- }
260
-
261
- setResolution( width, height ) {
262
-
263
- if ( this.width === width && this.height === height ) return;
264
- this.width = width;
265
- this.height = height;
266
- this._pixels = new Float32Array( width * height * 4 );
267
- this._texture.dispose();
268
- this._texture = new DataTexture( this._pixels, width, height, RGBAFormat, FloatType );
269
- this._texture.mapping = EquirectangularReflectionMapping;
270
- this._texture.colorSpace = LinearSRGBColorSpace;
271
- this._texture.minFilter = LinearFilter;
272
- this._texture.magFilter = LinearFilter;
273
- this._texture.wrapS = RepeatWrapping;
274
- this._texture.wrapT = ClampToEdgeWrapping;
275
- this._texture.generateMipmaps = false;
276
-
277
- }
278
-
279
- getLastRenderTime() {
280
-
281
- return this.lastRenderTime;
282
-
283
- }
284
-
285
- dispose() {
286
-
287
- this._texture.dispose();
288
-
289
- }
290
-
291
- }