rayzee 9.0.0 → 9.1.1

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 (95) 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 +10353 -5337
  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/KernelManager.js +4 -1
  28. package/src/Processor/PBRT/PBRTCurves.js +104 -27
  29. package/src/Processor/PBRT/PBRTParser.js +132 -22
  30. package/src/Processor/PBRT/PBRTSceneBuilder.js +192 -49
  31. package/src/Processor/PBRT/buffers.js +27 -0
  32. package/src/Processor/PBRT/index.js +55 -15
  33. package/src/Processor/PackedRayBuffer.js +10 -13
  34. package/src/Processor/ParallelBVHBuilder.js +36 -15
  35. package/src/Processor/PhysicalSky.js +308 -0
  36. package/src/Processor/QueueManager.js +1 -0
  37. package/src/Processor/SceneProcessor.js +1132 -181
  38. package/src/Processor/SplitBLAS.js +249 -0
  39. package/src/Processor/SunPosition.js +41 -0
  40. package/src/Processor/TextureCreator.js +21 -0
  41. package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
  42. package/src/Processor/Workers/BVHWorker.js +17 -13
  43. package/src/Processor/ZipReader.js +207 -0
  44. package/src/RenderSettings.js +38 -0
  45. package/src/SceneState/SceneState.js +367 -0
  46. package/src/SceneState/portable.js +52 -0
  47. package/src/Stages/NormalDepth.js +7 -6
  48. package/src/Stages/PathTracer.js +108 -4
  49. package/src/Stages/PathTracerStage.js +251 -104
  50. package/src/Storage/BLASCache.js +190 -0
  51. package/src/Storage/CDFCache.js +77 -0
  52. package/src/Storage/DownloadCache.js +266 -0
  53. package/src/Storage/GeometrySpill.js +217 -0
  54. package/src/Storage/SceneGraphCodec.js +503 -0
  55. package/src/Storage/SpillStore.js +112 -0
  56. package/src/Storage/StorageManager.js +934 -0
  57. package/src/Storage/StorageOps.js +137 -0
  58. package/src/Storage/StorageWorker.js +37 -0
  59. package/src/Storage/events.js +29 -0
  60. package/src/Storage/identity.js +77 -0
  61. package/src/Storage/index.js +9 -0
  62. package/src/Storage/inlineTransport.js +24 -0
  63. package/src/Storage/locks.js +99 -0
  64. package/src/Storage/openStorage.js +87 -0
  65. package/src/Storage/sceneCachePolicy.js +12 -0
  66. package/src/Storage/shared.js +9 -0
  67. package/src/Storage/transport.js +62 -0
  68. package/src/TSL/Atmosphere.js +684 -0
  69. package/src/TSL/BVHTraversal.js +288 -239
  70. package/src/TSL/Common.js +11 -2
  71. package/src/TSL/Displacement.js +6 -6
  72. package/src/TSL/EmissiveSampling.js +5 -5
  73. package/src/TSL/EnvironmentCDF.js +176 -0
  74. package/src/TSL/GenerateKernel.js +3 -3
  75. package/src/TSL/HitFacet.js +4 -4
  76. package/src/TSL/LightsDirect.js +8 -8
  77. package/src/TSL/LightsSampling.js +55 -37
  78. package/src/TSL/Random.js +1 -0
  79. package/src/TSL/ShadeKernel.js +75 -19
  80. package/src/TSL/Sun.js +40 -0
  81. package/src/TSL/TextureSampling.js +6 -7
  82. package/src/index.js +18 -0
  83. package/src/managers/CameraManager.js +169 -1
  84. package/src/managers/EnvironmentManager.js +263 -33
  85. package/src/managers/GoboManager.js +19 -8
  86. package/src/managers/IESManager.js +3 -3
  87. package/src/managers/LightManager.js +98 -5
  88. package/src/managers/MaterialDataManager.js +22 -2
  89. package/src/managers/TransformManager.js +26 -0
  90. package/src/managers/UniformManager.js +5 -3
  91. package/src/managers/VideoRenderManager.js +3 -1
  92. package/src/managers/timeline/CameraTrack.js +22 -0
  93. package/src/managers/timeline/TimelineManager.js +14 -0
  94. package/src/version.js +3 -0
  95. package/src/Processor/ProceduralSky.js +0 -291
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * ShadeKernel.js — wavefront material eval + bounce generation. 256×1 workgroup, 1D dispatch.
3
- * 10 storage-buffer bindings: bvh, tri, mat, light, ray, rng, hit, gBuffer, counters, activeIndices
4
- * (at the device per-stage limit of 10; envCDF is a texture, not a storage buffer).
3
+ * 10 storage-buffer bindings: bvh, triGeo, triShade, mat, light, ray, hit (+ RNG state), gBuffer,
4
+ * counters, activeIndices (at the device per-stage limit of 10; envCDF is a texture, not a storage buffer).
5
5
  */
6
6
 
7
7
  import {
8
8
  Fn, float, vec2, vec3, vec4, int, uint,
9
9
  bool as tslBool,
10
- If, Loop, normalize, max, min, exp, log, clamp, dot, length, select, smoothstep,
10
+ If, Loop, normalize, max, min, exp, log, clamp, dot, length, select, smoothstep, acos,
11
11
  instanceIndex,
12
12
  sampler,
13
13
  atomicAdd, atomicLoad, atomicStore, uintBitsToFloat,
@@ -28,6 +28,7 @@ import { handleMaterialTransparency, MaterialInteractionResult } from './Materia
28
28
  import { sampleChromaticCollision, sampleHenyeyGreenstein, subsurfaceCoefficients, CollisionSample, MediumCoeffs } from './Subsurface.js';
29
29
  import { calculateIndirectLighting } from './LightsIndirect.js';
30
30
  import { IndirectLightingResult, sampleCone } from './LightsCore.js';
31
+ import { sunRadianceToward } from './Sun.js';
31
32
  import { regularizePathContribution, generateSampledDirection, computeNDCDepth, handleRussianRoulette } from './PathTracerCore.js';
32
33
  import { evaluateSpecularDFG, baseFresnelParams } from './MaterialProperties.js';
33
34
  import { NRD_HIT_DIST_A, NRD_HIT_DIST_B } from '../EngineDefaults.js';
@@ -63,6 +64,7 @@ import {
63
64
  writeGBuffer, writeGBufferHitDist, readGBuffer, gbDecodeNormalDepth,
64
65
  readRayRadiance,
65
66
  readFeatureThroughput, writeFeatureThroughput,
67
+ readRngState, writeRngState,
66
68
  } from '../Processor/PackedRayBuffer.js';
67
69
 
68
70
  const WG_SIZE = 256;
@@ -79,7 +81,7 @@ export function buildShadeKernel( params ) {
79
81
  bvhBuffer, triangleBuffer, materialBuffer,
80
82
  envCDFTexture,
81
83
  lightBuffer,
82
- rayBufferRW, rngBufferRW, hitBufferRO, gBufferRW,
84
+ rayBufferRW, hitBufferRW, gBufferRW,
83
85
  counters,
84
86
  activeIndicesRO,
85
87
  envTexture, environmentIntensity, envMatrix,
@@ -111,6 +113,7 @@ export function buildShadeKernel( params ) {
111
113
  // Aux G-buffer (normal/depth/albedo + surface ID) feeds only the denoiser/OIDN MRT. Gated by a
112
114
  // live uniform (1 = denoiser on) so the wavefront skips these writes when nothing consumes them.
113
115
  auxGBufferEnabled,
116
+ hasSun, sunDirection, sunRadiance, sunParams,
114
117
  } = params;
115
118
 
116
119
  const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
@@ -196,7 +199,7 @@ export function buildShadeKernel( params ) {
196
199
  const currentRadiance = readRayRadiance( rayBufferRW, rayID ).toVar();
197
200
  // One ray per pixel: rayID is the pixel index.
198
201
  const pixelIndex = rayID;
199
- const rngState = rngBufferRW.element( rayID ).toVar();
202
+ const rngState = readRngState( hitBufferRW, rayID ).toVar();
200
203
 
201
204
  // STBN keyed on (GLOBAL pixel, dimension, frame). pixelIndex is the LOCAL path slot; the global pixel
202
205
  // = chunkRowBase·W + localSlot, so the blue-noise pattern stays spatially aligned across row-band chunks
@@ -216,13 +219,13 @@ export function buildShadeKernel( params ) {
216
219
  // deferring continue (a missed persist = stale tint). 1 = untinted (direct hit → today's albedo).
217
220
  const featCarry = readFeatureThroughput( rayBufferRW, rayID ).toVar();
218
221
 
219
- const hitDist = readHitDistance( hitBufferRO, rayID ).toVar();
220
- const hitNormal = readHitNormal( hitBufferRO, rayID ).toVar();
222
+ const hitDist = readHitDistance( hitBufferRW, rayID ).toVar();
223
+ const hitNormal = readHitNormal( hitBufferRW, rayID ).toVar();
221
224
  // hitInfo.uv is the interpolated texture UV (not barycentrics)
222
- const hitUV = readHitBarycentrics( hitBufferRO, rayID ).toVar();
223
- const hitMatIdx = readHitMaterialIndex( hitBufferRO, rayID ).toVar();
224
- const hitTriIdx = readHitTriangleIndex( hitBufferRO, rayID ).toVar();
225
- const hitInstance = readHitInstanceLeaf( hitBufferRO, rayID ).toVar();
225
+ const hitUV = readHitBarycentrics( hitBufferRW, rayID ).toVar();
226
+ const hitMatIdx = readHitMaterialIndex( hitBufferRW, rayID ).toVar();
227
+ const hitTriIdx = readHitTriangleIndex( hitBufferRW, rayID ).toVar();
228
+ const hitInstance = readHitInstanceLeaf( hitBufferRW, rayID ).toVar();
226
229
 
227
230
  // per-ray camera-bounce depth — advances ONLY on opaque scatter (free bounces don't); drives termination (maxBounces). Megakernel: effectiveBounces.
228
231
  const cameraDepth = readPathBounces( rayBufferRW, rayID ).toVar();
@@ -287,6 +290,7 @@ export function buildShadeKernel( params ) {
287
290
  enableEnvironmentLight,
288
291
  tslBool( true ), // wantUnoccluded
289
292
  vec3( 0.0 ), planeN, float( 0.0 ),
293
+ hasSun, sunDirection, sunRadiance, sunParams,
290
294
  ) ).toVar();
291
295
 
292
296
  const lumShad = max( dot( dual.shadowed, REC709_LUMINANCE_COEFFICIENTS ), float( 0.0 ) );
@@ -357,7 +361,7 @@ export function buildShadeKernel( params ) {
357
361
  writeRayRadiance( rayBufferRW, rayID, vec4( outRgb, outAlpha ) );
358
362
  // DDFA: the plane's aux (written above) is a committed surface — lock it (cosmetic; ray dies here).
359
363
  writeRayDirFlags( rayBufferRW, rayID, direction, flags.bitOr( uint( RAY_FLAG.AUX_LOCKED ) ).bitAnd( uint( ~ RAY_FLAG.ACTIVE ) ) );
360
- rngBufferRW.element( rayID ).assign( rngState );
364
+ writeRngState( hitBufferRW, rayID, rngState );
361
365
  Return();
362
366
 
363
367
  } );
@@ -469,6 +473,53 @@ export function buildShadeKernel( params ) {
469
473
  currentRadiance.w
470
474
  ) );
471
475
 
476
+ // The sun disc; on a redirected ray, the partner of the sending vertex's sun NEE. A blurred
477
+ // backdrop gets a smooth falloff: a hard-edged cone reads as a white coin.
478
+ If( hasSun, () => {
479
+
480
+ const sunL = vec3( 0.0 ).toVar();
481
+ If( isBackdropView.and( backgroundBlurriness.greaterThan( 0.0 ) ), () => {
482
+
483
+ const blurAngle = backgroundBlurriness.mul( 1.3 ).toVar();
484
+ const angle = acos( clamp( dot( normalize( envDir ), sunDirection ), - 1.0, 1.0 ) );
485
+ If( angle.lessThan( blurAngle ), () => {
486
+
487
+ const falloff = float( 1.0 ).sub( angle.div( blurAngle ).pow( 2 ) ).pow( 2 );
488
+ // ∫ falloff dΩ = π θb² / 3 for a small cone.
489
+ sunL.assign( sunRadiance.mul( environmentIntensity ).mul( sunParams.y ).mul( falloff ).mul( 3 / Math.PI ).div( blurAngle.mul( blurAngle ) ) );
490
+
491
+ } );
492
+
493
+ } ).Else( () => {
494
+
495
+ sunL.assign( sunRadianceToward( normalize( envDir ), sunDirection, sunRadiance, sunParams ).mul( environmentIntensity ) );
496
+
497
+ } );
498
+ If( sunL.x.add( sunL.y ).add( sunL.z ).greaterThan( 0.0 ), () => {
499
+
500
+ const sunW = float( 1.0 ).toVar();
501
+ If( isBackdropView.not().and( flags.bitAnd( uint( RAY_FLAG.SUN_NEE ) ).notEqual( uint( 0 ) ) ), () => {
502
+
503
+ sunW.assign( powerHeuristic( { pdf1: readRayPdf( rayBufferRW, rayID ), pdf2: float( 1.0 ).div( sunParams.y ) } ) );
504
+
505
+ } );
506
+ const vertex = max( bounceIndex.sub( 1 ), int( 0 ) );
507
+ const sunScale = select( isBackdropView, backgroundIntensity,
508
+ sunW.mul( select( vertex.greaterThan( int( 0 ) ), globalIlluminationIntensity, float( 1.0 ) ) ) );
509
+ currentRadiance.assign( vec4(
510
+ currentRadiance.xyz.add(
511
+ regularizePathContribution(
512
+ throughput.mul( sunL ).mul( sunScale ),
513
+ select( isBackdropView, float( 0.0 ), float( vertex ) ), fireflyThreshold, int( accumFrame ),
514
+ ),
515
+ ),
516
+ currentRadiance.w
517
+ ) );
518
+
519
+ } );
520
+
521
+ } );
522
+
472
523
  } );
473
524
 
474
525
  // Solid-color backdrop ('color' mode): a primary ray that doesn't show the env image and isn't
@@ -577,7 +628,7 @@ export function buildShadeKernel( params ) {
577
628
 
578
629
  writeRayRadiance( rayBufferRW, rayID, currentRadiance );
579
630
  writeRayDirFlags( rayBufferRW, rayID, direction, flags.bitAnd( uint( ~ RAY_FLAG.ACTIVE ) ) );
580
- rngBufferRW.element( rayID ).assign( rngState );
631
+ writeRngState( hitBufferRW, rayID, rngState );
581
632
  Return();
582
633
 
583
634
  } );
@@ -594,7 +645,7 @@ export function buildShadeKernel( params ) {
594
645
  // fires the next hit's env/emissive MIS, down-weighting SSS-then-env/emitter views.
595
646
  writeRayThroughputPdf( rayBufferRW, rayID, throughput, readRayPdf( rayBufferRW, rayID ) );
596
647
  writeRayRadiance( rayBufferRW, rayID, currentRadiance );
597
- rngBufferRW.element( rayID ).assign( rngState );
648
+ writeRngState( hitBufferRW, rayID, rngState );
598
649
  Return();
599
650
 
600
651
  } );
@@ -735,7 +786,7 @@ export function buildShadeKernel( params ) {
735
786
 
736
787
  writeRayRadiance( rayBufferRW, rayID, currentRadiance );
737
788
  writeRayDirFlags( rayBufferRW, rayID, direction, flags.bitAnd( uint( ~ RAY_FLAG.ACTIVE ) ) );
738
- rngBufferRW.element( rayID ).assign( rngState );
789
+ writeRngState( hitBufferRW, rayID, rngState );
739
790
 
740
791
  };
741
792
 
@@ -935,7 +986,7 @@ export function buildShadeKernel( params ) {
935
986
  throughput.mulAssign( interaction.throughput );
936
987
 
937
988
  // Off the side of the facet the new ray leaves on: reflection stays, transmission and alpha skip cross.
938
- const Ng = unpackHitFacet( readHitFacet( hitBufferRO, rayID ) ).faceN;
989
+ const Ng = unpackHitFacet( readHitFacet( hitBufferRW, rayID ) ).faceN;
939
990
  const newOrigin = offsetRayOrigin( hitPoint, select( dot( Ng, interaction.direction ).lessThan( 0.0 ), Ng.negate(), Ng ) );
940
991
 
941
992
  // SSS = free bounce (depth unchanged); transmission advances camera-bounce depth.
@@ -979,7 +1030,7 @@ export function buildShadeKernel( params ) {
979
1030
  writeFeatureThroughput( rayBufferRW, rayID, featCarry );
980
1031
 
981
1032
  } );
982
- rngBufferRW.element( rayID ).assign( rngState );
1033
+ writeRngState( hitBufferRW, rayID, rngState );
983
1034
  Return();
984
1035
 
985
1036
  } );
@@ -1066,7 +1117,7 @@ export function buildShadeKernel( params ) {
1066
1117
  const Ngeo = normalize( hitNormal );
1067
1118
  const NgeoFF = select( dot( Ngeo, V ).lessThan( 0.0 ), Ngeo.negate(), Ngeo ).toVar();
1068
1119
  // The facet on the viewer's side: every ray spawned from here is offset off it (HitFacet.js).
1069
- const facet = unpackHitFacet( readHitFacet( hitBufferRO, rayID ) );
1120
+ const facet = unpackHitFacet( readHitFacet( hitBufferRW, rayID ) );
1070
1121
  const facetN = facet.faceN.toVar();
1071
1122
  const terminatorLift = Ngeo.mul( facet.liftScale );
1072
1123
  const lightShadowOrigin = L => shadowTerminatorOrigin( {
@@ -1188,6 +1239,7 @@ export function buildShadeKernel( params ) {
1188
1239
  enableEnvironmentLight,
1189
1240
  tslBool( false ), // wantUnoccluded: false on real surfaces — dead-codes the unoccluded sum
1190
1241
  terminatorLift, facetN, shadowTerminatorOffset,
1242
+ hasSun, sunDirection, sunRadiance, sunParams,
1191
1243
  ) ).shadowed.toVar();
1192
1244
 
1193
1245
  const giScale = select( bounceIndex.greaterThan( 0 ), globalIlluminationIntensity, float( 1.0 ) );
@@ -1367,6 +1419,10 @@ export function buildShadeKernel( params ) {
1367
1419
 
1368
1420
  const newOrigin = offsetRayOrigin( hitPoint, select( dot( facetN, bounceDir ).lessThan( 0.0 ), facetN.negate(), facetN ) );
1369
1421
 
1422
+ // Whether this vertex's sun NEE could have drawn bounceDir, so a sun hit at the miss knows its MIS partner.
1423
+ const sunNEE = brdfIsTransmission.not().and( dot( N, bounceDir ).greaterThan( 0.0 ) );
1424
+ flags.assign( flags.sub( flags.bitAnd( uint( RAY_FLAG.SUN_NEE ) ) ).bitOr( select( sunNEE, uint( RAY_FLAG.SUN_NEE ), uint( 0 ) ) ) );
1425
+
1370
1426
  // Opaque scatter: the only bounce that advances camera depth.
1371
1427
  writeRayOriginMeta( rayBufferRW, rayID, newOrigin, cameraDepth.add( 1 ), sssSteps, transparentCount );
1372
1428
  writeRayDirFlags( rayBufferRW, rayID, bounceDir, flags );
@@ -1379,7 +1435,7 @@ export function buildShadeKernel( params ) {
1379
1435
  writeFeatureThroughput( rayBufferRW, rayID, featCarry );
1380
1436
 
1381
1437
  } );
1382
- rngBufferRW.element( rayID ).assign( rngState );
1438
+ writeRngState( hitBufferRW, rayID, rngState );
1383
1439
 
1384
1440
  } );
1385
1441
 
package/src/TSL/Sun.js ADDED
@@ -0,0 +1,40 @@
1
+ import { Fn, float, vec3, sqrt, max, dot, cross, pow, cos, sin, abs, normalize, select, If } from 'three/tsl';
2
+
3
+ import { LIMB_DARKENING_EXPONENT } from '../Processor/AtmosphereModel.js';
4
+
5
+ const LIMB_EXPONENT = vec3( ...LIMB_DARKENING_EXPONENT );
6
+ // µ^α averages to 2 / (α + 2) over the disc, so this keeps the mean at `sunRadiance`.
7
+ const LIMB_NORM = vec3( ...LIMB_DARKENING_EXPONENT.map( a => ( a + 2 ) / 2 ) );
8
+
9
+ /**
10
+ * Radiance the physical sky's sun sends along `dir`: limb darkened, and nothing below the sky's
11
+ * horizon. `sunParams` = ( cos half-angle, solid angle, 1 / sin² half-angle, horizon dip sin ).
12
+ */
13
+ export const sunRadianceToward = /*@__PURE__*/ Fn( ( [ dir, sunDirection, sunRadiance, sunParams ] ) => {
14
+
15
+ const out = vec3( 0 ).toVar();
16
+ If( dot( dir, sunDirection ).greaterThanEqual( sunParams.x ).and( dir.y.greaterThanEqual( sunParams.w.negate() ) ), () => {
17
+
18
+ const c = cross( dir, sunDirection );
19
+ const mu = sqrt( max( float( 1 ).sub( dot( c, c ).mul( sunParams.z ) ), 1e-8 ) );
20
+ out.assign( sunRadiance.mul( pow( vec3( mu ), LIMB_EXPONENT ) ).mul( LIMB_NORM ) );
21
+
22
+ } );
23
+ return out;
24
+
25
+ } );
26
+
27
+ /** A direction uniform in solid angle over the sun disc. */
28
+ export const sampleSunDisc = /*@__PURE__*/ Fn( ( [ sunDirection, sunParams, xi ] ) => {
29
+
30
+ // 1 − cosθ straight from the solid angle: 1 − cos(half-angle) cancels to nothing in f32.
31
+ const oneMinusCos = xi.x.mul( sunParams.y.mul( 1 / ( 2 * Math.PI ) ) );
32
+ const cosT = float( 1 ).sub( oneMinusCos );
33
+ const sinT = sqrt( max( oneMinusCos.mul( float( 2 ).sub( oneMinusCos ) ), 0 ) );
34
+ const phi = xi.y.mul( 2 * Math.PI );
35
+ const w = sunDirection;
36
+ const u = normalize( cross( select( abs( w.x ).greaterThan( 0.9 ), vec3( 0, 1, 0 ), vec3( 1, 0, 0 ) ), w ) );
37
+ const v = cross( w, u );
38
+ return normalize( w.mul( cosT ).add( u.mul( cos( phi ) ).add( v.mul( sin( phi ) ) ).mul( sinT ) ) );
39
+
40
+ } );
@@ -6,7 +6,7 @@ import {
6
6
  MaterialSamples,
7
7
  ExtMapResult,
8
8
  } from './Struct.js';
9
- import { getDatafromStorageBuffer, instanceRows, instanceDirToWorld, TRI_STRIDE } from './Common.js';
9
+ import { instanceRows, instanceDirToWorld, triangleRow } from './Common.js';
10
10
  import { TEXTURE_CONSTANTS } from '../EngineDefaults.js';
11
11
 
12
12
  // ================================================================================
@@ -414,13 +414,12 @@ const uvTransformJacobian = /*@__PURE__*/ wgslFn( `
414
414
  */
415
415
  export const triangleUVTangent = Fn( ( [ triangleBuffer, triIndex, geometryNormal, transform, bvhBuffer, instanceLeaf ] ) => {
416
416
 
417
- const S = int( TRI_STRIDE );
418
- const pA = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 0 ), S ).xyz );
419
- const e1 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 1 ), S ).xyz ).sub( pA );
420
- const e2 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 2 ), S ).xyz ).sub( pA );
417
+ const pA = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 0 ).xyz );
418
+ const e1 = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 1 ).xyz ).sub( pA );
419
+ const e2 = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 2 ).xyz ).sub( pA );
421
420
 
422
- const uvAB = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 3 ), S ) );
423
- const uvC = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 4 ), S ).xy );
421
+ const uvAB = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 3 ) );
422
+ const uvC = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 4 ).xy );
424
423
  const d1 = uvAB.zw.sub( uvAB.xy );
425
424
  const d2 = uvC.sub( uvAB.xy );
426
425
 
package/src/index.js CHANGED
@@ -26,6 +26,22 @@ export { SETTING_SOURCE } from './RenderSettings.js';
26
26
  // Asset URL / cache namespace overrides (call before constructing PathTracerApp)
27
27
  export { configureAssets, getAssetConfig } from './AssetConfig.js';
28
28
 
29
+ // On-disk storage (origin private file system): caches, and areas a host defines for its own data
30
+ export {
31
+ openStorage, StorageManager, STORAGE_KIND, ENGINE_AREAS, fileIdentity, identityKey, sameIdentity,
32
+ DownloadCache, DOWNLOAD_POLICY, fetchFile, nameFromUrl, cachedObjectURL, acquireLock, heldLockNames,
33
+ } from './Storage/index.js';
34
+
35
+ // Archives read in place: one entry at a time, never the whole file in memory
36
+ export { openZip, readZipDirectory } from './Processor/ZipReader.js';
37
+
38
+ // Engine package version, as built
39
+ export { VERSION } from './version.js';
40
+
41
+ // A loaded scene's edits as plain data (app.exportSceneState / importSceneState)
42
+ export { SCENE_STATE_VERSION } from './SceneState/SceneState.js';
43
+ export { toPortable, fromPortable } from './SceneState/portable.js';
44
+
29
45
  // glTF alphaMode derivation — the one definition, shared with hosts that edit materials
30
46
  export { deriveAlphaMode, MATERIAL_VALUE_SOURCE } from './Processor/GeometryExtractor.js';
31
47
 
@@ -47,6 +63,7 @@ export {
47
63
  CAMERA_PRESETS,
48
64
  CAMERA_RANGES,
49
65
  SKY_PRESETS,
66
+ DEFAULT_SUN_PATH,
50
67
  AUTO_FOCUS_MODES,
51
68
  AF_DEFAULTS,
52
69
  TRIANGLE_DATA_LAYOUT,
@@ -61,6 +78,7 @@ export {
61
78
  RENDER_PROFILES,
62
79
  getRenderProfile,
63
80
  } from './EngineDefaults.js';
81
+ export { sunPosition, timeForSunElevation, dayOfYearForMonth } from './Processor/SunPosition.js';
64
82
 
65
83
  // Settings & managers (for advanced consumers)
66
84
  export { RenderSettings } from './RenderSettings.js';
@@ -1,10 +1,11 @@
1
- import { EventDispatcher, MathUtils, OrthographicCamera, PerspectiveCamera, Vector3 } from 'three';
1
+ import { EventDispatcher, MathUtils, OrthographicCamera, PerspectiveCamera, Quaternion, Vector3 } from 'three';
2
2
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
3
3
  import { EngineEvents, } from '../EngineEvents.js';
4
4
  import { AF_DEFAULTS, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
5
5
  import { viewDepth } from './InteractionManager.js';
6
6
  import { ViewCamera } from './ViewCamera.js';
7
7
  import { WalkControls } from './WalkControls.js';
8
+ import { toPortable, fromPortable } from '../SceneState/portable.js';
8
9
 
9
10
  const DEFAULT_CAMERA_SCALE = Object.freeze( new Vector3( 1, 1, 1 ) );
10
11
 
@@ -557,6 +558,173 @@ export class CameraManager extends EventDispatcher {
557
558
 
558
559
  }
559
560
 
561
+ // ── Saved sessions ────────────────────────────────────────────
562
+
563
+ /**
564
+ * The live view, every camera's own effects, the cameras the user added and which one is
565
+ * active, as plain data. The active camera's effects are the render settings themselves.
566
+ * @returns {Object}
567
+ */
568
+ serialize() {
569
+
570
+ const camera = this.camera;
571
+ const saved = this._defaultCameraState;
572
+
573
+ return {
574
+ current: this.currentCameraIndex,
575
+ view: {
576
+ position: camera.position.toArray(),
577
+ quaternion: camera.quaternion.toArray(),
578
+ scale: camera.scale.toArray(),
579
+ fov: camera.fov,
580
+ near: camera.near,
581
+ far: camera.far,
582
+ zoom: camera.zoom,
583
+ orthographic: !! camera.orthographic,
584
+ orthoHalfHeight: camera.orthoHalfHeight,
585
+ target: this.controls.target.toArray(),
586
+ },
587
+ defaultView: saved ? {
588
+ position: saved.position.toArray(),
589
+ quaternion: saved.quaternion.toArray(),
590
+ scale: saved.scale.toArray(),
591
+ fov: saved.fov,
592
+ near: saved.near,
593
+ far: saved.far,
594
+ target: saved.target?.toArray() ?? null,
595
+ } : null,
596
+ navigationMode: this.navigationMode,
597
+ autoFocus: { mode: this.autoFocusMode, point: { ...this.afScreenPoint } },
598
+ cameras: this.cameras.map( ( cam, index ) => {
599
+
600
+ const entry = { index, name: cam.name ?? '', effects: toPortable( cam.userData?.__rayzeeEffects ) ?? null };
601
+ if ( index === 0 || ! cam.userData?.__rayzeeUserCamera ) return entry;
602
+
603
+ return {
604
+ ...entry,
605
+ user: true,
606
+ orthographic: !! cam.isOrthographicCamera,
607
+ frustum: cam.isOrthographicCamera ? [ cam.left, cam.right, cam.top, cam.bottom ] : null,
608
+ fov: cam.fov ?? null,
609
+ aspect: cam.aspect ?? null,
610
+ near: cam.near,
611
+ far: cam.far,
612
+ zoom: cam.zoom,
613
+ position: cam.position.toArray(),
614
+ quaternion: cam.quaternion.toArray(),
615
+ scale: cam.scale.toArray(),
616
+ orbitTarget: cam.userData.__rayzeeOrbitTarget?.toArray() ?? null,
617
+ };
618
+
619
+ } ),
620
+ };
621
+
622
+ }
623
+
624
+ /**
625
+ * Puts back what {@link serialize} recorded, against the cameras this load produced. A model
626
+ * camera is matched by index and name; one that no longer matches keeps its own effects.
627
+ * Restore render settings first: this sets the exact view after any projection change they made.
628
+ * @param {Object} state
629
+ * @returns {{mismatched: string[]}} model cameras whose saved effects were not applied
630
+ */
631
+ restore( state ) {
632
+
633
+ const mismatched = [];
634
+ if ( ! state ) return { mismatched };
635
+
636
+ this.cameras = this.cameras.filter( cam => ! cam.userData?.__rayzeeUserCamera );
637
+ const indexMap = new Map();
638
+
639
+ for ( const entry of state.cameras ?? [] ) {
640
+
641
+ if ( entry.user ) {
642
+
643
+ const cam = entry.orthographic
644
+ ? new OrthographicCamera( ...entry.frustum, entry.near, entry.far )
645
+ : new PerspectiveCamera( entry.fov, entry.aspect, entry.near, entry.far );
646
+ cam.name = entry.name;
647
+ cam.zoom = entry.zoom ?? 1;
648
+ cam.position.fromArray( entry.position );
649
+ cam.quaternion.fromArray( entry.quaternion );
650
+ cam.scale.fromArray( entry.scale );
651
+ cam.updateProjectionMatrix();
652
+ cam.updateMatrixWorld( true );
653
+ cam.userData.__rayzeeUserCamera = true;
654
+ cam.userData.__rayzeeEffects = fromPortable( entry.effects );
655
+ if ( entry.orbitTarget ) cam.userData.__rayzeeOrbitTarget = new Vector3().fromArray( entry.orbitTarget );
656
+ indexMap.set( entry.index, this.cameras.length );
657
+ this.cameras.push( cam );
658
+ continue;
659
+
660
+ }
661
+
662
+ const cam = this.cameras[ entry.index ];
663
+ if ( ! cam || ( entry.index > 0 && ( cam.name ?? '' ) !== entry.name ) ) {
664
+
665
+ mismatched.push( entry.name );
666
+ continue;
667
+
668
+ }
669
+
670
+ indexMap.set( entry.index, entry.index );
671
+ if ( entry.effects ) cam.userData.__rayzeeEffects = fromPortable( entry.effects );
672
+
673
+ }
674
+
675
+ this._userCameraCounter = this.cameras.filter( cam => cam.userData?.__rayzeeUserCamera ).length;
676
+ this.currentCameraIndex = indexMap.get( state.current ) ?? 0;
677
+
678
+ const d = state.defaultView;
679
+ this._defaultCameraState = d ? {
680
+ position: new Vector3().fromArray( d.position ),
681
+ quaternion: new Quaternion().fromArray( d.quaternion ),
682
+ scale: new Vector3().fromArray( d.scale ),
683
+ fov: d.fov,
684
+ near: d.near,
685
+ far: d.far,
686
+ target: d.target ? new Vector3().fromArray( d.target ) : null,
687
+ } : null;
688
+
689
+ if ( state.navigationMode ) this.setNavigationMode( state.navigationMode );
690
+
691
+ const v = state.view;
692
+ if ( v ) {
693
+
694
+ const camera = this.camera;
695
+ camera.position.fromArray( v.position );
696
+ camera.quaternion.fromArray( v.quaternion );
697
+ camera.scale.fromArray( v.scale );
698
+ camera.fov = v.fov;
699
+ camera.near = v.near;
700
+ camera.far = v.far;
701
+ this._setView( v.orthographic, v.orthoHalfHeight );
702
+ camera.zoom = v.zoom ?? 1;
703
+ camera.updateProjectionMatrix();
704
+ camera.updateMatrixWorld( true );
705
+ this.controls.target.fromArray( v.target );
706
+ this.controls.update();
707
+
708
+ }
709
+
710
+ if ( state.autoFocus ) {
711
+
712
+ this.setAutoFocusMode( state.autoFocus.mode );
713
+ this.setAFScreenPoint( state.autoFocus.point.x, state.autoFocus.point.y );
714
+
715
+ }
716
+
717
+ this.resetAutoFocus();
718
+ this._reportOrthoHeight();
719
+ this.dispatchEvent( {
720
+ type: 'CameraSwitched', cameraIndex: this.currentCameraIndex, effects: this._captureEffects(), fov: this.camera.fov,
721
+ cameraProjection: this._getSettings?.( 'cameraProjection' ),
722
+ } );
723
+
724
+ return { mismatched };
725
+
726
+ }
727
+
560
728
  // ── Aliases (match Sub-API surface) ───────────────────────────
561
729
 
562
730
  /** The active camera, a {@link ViewCamera}. */