rayzee 7.31.1 → 8.0.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 (51) hide show
  1. package/README.md +164 -0
  2. package/dist/rayzee.es.js +6366 -4361
  3. package/dist/rayzee.umd.js +122 -73
  4. package/package.json +1 -1
  5. package/src/EngineDefaults.js +168 -13
  6. package/src/EngineIssues.js +5 -0
  7. package/src/PathTracerApp.js +120 -19
  8. package/src/Processor/ArchiveReader.js +538 -0
  9. package/src/Processor/AssetLoader.js +265 -23
  10. package/src/Processor/BVHBuilder.js +48 -32
  11. package/src/Processor/BVHRefitter.js +244 -99
  12. package/src/Processor/ChunkedRecords.js +302 -0
  13. package/src/Processor/EmissiveTriangleBuilder.js +48 -21
  14. package/src/Processor/GeometryExtractor.js +577 -92
  15. package/src/Processor/HostMemory.js +222 -0
  16. package/src/Processor/InstanceTable.js +548 -97
  17. package/src/Processor/PBRT/EqualAreaOctahedral.js +32 -8
  18. package/src/Processor/PBRT/PBRTCurves.js +249 -0
  19. package/src/Processor/PBRT/PBRTMath.js +19 -0
  20. package/src/Processor/PBRT/PBRTParser.js +187 -41
  21. package/src/Processor/PBRT/PBRTSceneBuilder.js +586 -42
  22. package/src/Processor/PBRT/PBRTTokenizer.js +205 -65
  23. package/src/Processor/PBRT/index.js +257 -50
  24. package/src/Processor/PackedRayBuffer.js +29 -18
  25. package/src/Processor/ParallelBVHBuilder.js +9 -8
  26. package/src/Processor/QueueManager.js +5 -3
  27. package/src/Processor/SceneProcessor.js +1046 -365
  28. package/src/Processor/TLASBuilder.js +546 -201
  29. package/src/Processor/TextureCreator.js +11 -9
  30. package/src/Processor/VRAMTracker.js +12 -3
  31. package/src/Processor/Workers/BVHRefitWorker.js +16 -11
  32. package/src/Processor/Workers/BVHWorker.js +12 -12
  33. package/src/Processor/Workers/TLASWorker.js +37 -0
  34. package/src/Stages/NormalDepth.js +6 -2
  35. package/src/Stages/PathTracer.js +5 -5
  36. package/src/Stages/PathTracerStage.js +163 -41
  37. package/src/TSL/BVHTraversal.js +208 -90
  38. package/src/TSL/Common.js +135 -2
  39. package/src/TSL/Displacement.js +21 -11
  40. package/src/TSL/EmissiveSampling.js +49 -25
  41. package/src/TSL/ExtendKernel.js +3 -2
  42. package/src/TSL/LightBVHSampling.js +7 -4
  43. package/src/TSL/LightsDirect.js +30 -29
  44. package/src/TSL/ShadeKernel.js +8 -6
  45. package/src/TSL/Struct.js +3 -0
  46. package/src/TSL/TextureSampling.js +24 -9
  47. package/src/TSL/patches.js +128 -3
  48. package/src/index.js +3 -0
  49. package/src/managers/AnimationManager.js +86 -76
  50. package/src/managers/MaterialDataManager.js +29 -23
  51. package/src/managers/TransformManager.js +9 -192
@@ -52,8 +52,26 @@ export function equalAreaSphereToSquare( dx, dy, dz ) {
52
52
 
53
53
  }
54
54
 
55
- /** Bilinear fetch from a tightly packed float image, clamped at the edges. */
56
- function sampleBilinear( data, width, height, channels, u, v, out ) {
55
+ /** 8-bit sRGB → linear, tabulated so a per-tap decode costs a lookup rather than a pow(). */
56
+ const SRGB_TO_LINEAR = ( () => {
57
+
58
+ const t = new Float32Array( 256 );
59
+ for ( let i = 0; i < 256; i ++ ) {
60
+
61
+ const c = i / 255;
62
+ t[ i ] = c <= 0.04045 ? c / 12.92 : Math.pow( ( c + 0.055 ) / 1.055, 2.4 );
63
+
64
+ }
65
+
66
+ return t;
67
+
68
+ } )();
69
+
70
+ /**
71
+ * Bilinear fetch from a tightly packed image, clamped at the edges.
72
+ * `table` maps stored values to linear before filtering; null reads them as-is.
73
+ */
74
+ function sampleBilinear( data, width, height, channels, u, v, out, table ) {
57
75
 
58
76
  const fx = Math.min( Math.max( u * width - 0.5, 0 ), width - 1 );
59
77
  const fy = Math.min( Math.max( v * height - 0.5, 0 ), height - 1 );
@@ -68,8 +86,12 @@ function sampleBilinear( data, width, height, channels, u, v, out ) {
68
86
 
69
87
  for ( let c = 0; c < 3; c ++ ) {
70
88
 
71
- const top = data[ i00 + c ] * ( 1 - tx ) + data[ i10 + c ] * tx;
72
- const bottom = data[ i01 + c ] * ( 1 - tx ) + data[ i11 + c ] * tx;
89
+ const a = table ? table[ data[ i00 + c ] ] : data[ i00 + c ];
90
+ const b = table ? table[ data[ i10 + c ] ] : data[ i10 + c ];
91
+ const d = table ? table[ data[ i01 + c ] ] : data[ i01 + c ];
92
+ const e = table ? table[ data[ i11 + c ] ] : data[ i11 + c ];
93
+ const top = a * ( 1 - tx ) + b * tx;
94
+ const bottom = d * ( 1 - tx ) + e * tx;
73
95
  out[ c ] = top * ( 1 - ty ) + bottom * ty;
74
96
 
75
97
  }
@@ -83,9 +105,10 @@ function sampleBilinear( data, width, height, channels, u, v, out ) {
83
105
  * v = 1 - acos(y)/pi, with row 0 at v = 1 so "up" lands at the top like any other HDRI.
84
106
  *
85
107
  * @param {{data: ArrayLike<number>, width: number, height: number, channels: number,
86
- * bottomUp?: boolean}} src - `bottomUp` when row 0 holds the BOTTOM of the image, which is
87
- * what three's EXR/HDR loaders produce; pbrt indexes the square top-down, and reading it
88
- * the wrong way round turns out to rotate the sky 180° about the light's z axis.
108
+ * bottomUp?: boolean, srgb?: boolean}} src - `bottomUp` when row 0 holds the BOTTOM of the
109
+ * image, which is what three's EXR/HDR loaders produce; pbrt indexes the square top-down, and
110
+ * reading it the wrong way round turns out to rotate the sky 180° about the light's z axis.
111
+ * `srgb` when the samples are 8-bit sRGB, as a PNG read back through a canvas is.
89
112
  * @param {number[]} lightToWorld - the light's pbrt CTM (column-major 16), or null
90
113
  * @param {number} scale - the light's `scale` parameter, baked into the output
91
114
  * @param {number} [outWidth] - defaults to the source width, capped at 4096
@@ -100,6 +123,7 @@ export function octahedralToEquirect( src, lightToWorld, scale = 1, outWidth = 0
100
123
  // Directions go world → light, the opposite of the CTM, matching pbrt's
101
124
  // renderFromLight.ApplyInverse(ray.d) before the square lookup.
102
125
  const worldToLight = lightToWorld ? M.invert( lightToWorld ) : null;
126
+ const table = src.srgb ? SRGB_TO_LINEAR : null;
103
127
  const rgb = [ 0, 0, 0 ];
104
128
 
105
129
  for ( let j = 0; j < height; j ++ ) {
@@ -132,7 +156,7 @@ export function octahedralToEquirect( src, lightToWorld, scale = 1, outWidth = 0
132
156
  }
133
157
 
134
158
  const [ su, sv ] = equalAreaSphereToSquare( dx, dy, dz );
135
- sampleBilinear( src.data, src.width, src.height, src.channels, su, src.bottomUp ? 1 - sv : sv, rgb );
159
+ sampleBilinear( src.data, src.width, src.height, src.channels, su, src.bottomUp ? 1 - sv : sv, rgb, table );
136
160
 
137
161
  const o = ( j * width + i ) * 4;
138
162
  out[ o ] = rgb[ 0 ] * scale;
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Tessellation for pbrt-v4's `Shape "curve"`.
3
+ *
4
+ * pbrt intersects curves analytically as swept cubic splines. The engine only traces
5
+ * triangles, so each curve becomes a strip: one ribbon for `flat`/`ribbon`, a crossed
6
+ * pair for `cylinder` (view-independent and a quarter the cost of a real tube), or a
7
+ * closed tube when asked for one.
8
+ *
9
+ * Cost is the whole story here — Moana's isMountainB ground cover alone declares 5.18
10
+ * million curve shapes, so `steps` and `sides` are the knobs that decide whether a
11
+ * scene is 20 million triangles or 200 million.
12
+ */
13
+
14
+ const DEFAULT_STEPS = 2;
15
+
16
+ /** Uniform cubic B-spline basis at t for the four control points of one span. */
17
+ function bsplineWeights( t, out ) {
18
+
19
+ const t2 = t * t;
20
+ const t3 = t2 * t;
21
+ out[ 0 ] = ( 1 - 3 * t + 3 * t2 - t3 ) / 6;
22
+ out[ 1 ] = ( 4 - 6 * t2 + 3 * t3 ) / 6;
23
+ out[ 2 ] = ( 1 + 3 * t + 3 * t2 - 3 * t3 ) / 6;
24
+ out[ 3 ] = t3 / 6;
25
+
26
+ }
27
+
28
+ /** Cubic Bezier basis at t. */
29
+ function bezierWeights( t, out ) {
30
+
31
+ const u = 1 - t;
32
+ out[ 0 ] = u * u * u;
33
+ out[ 1 ] = 3 * u * u * t;
34
+ out[ 2 ] = 3 * u * t * t;
35
+ out[ 3 ] = t * t * t;
36
+
37
+ }
38
+
39
+ function normalize( v ) {
40
+
41
+ const l = Math.hypot( v[ 0 ], v[ 1 ], v[ 2 ] );
42
+ if ( l > 0 ) {
43
+
44
+ v[ 0 ] /= l; v[ 1 ] /= l; v[ 2 ] /= l;
45
+
46
+ }
47
+
48
+ return v;
49
+
50
+ }
51
+
52
+ function cross( a, b, out ) {
53
+
54
+ out[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ];
55
+ out[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ];
56
+ out[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ];
57
+ return out;
58
+
59
+ }
60
+
61
+ /**
62
+ * Sample a curve's centreline.
63
+ * @returns {{points: Float64Array, count: number}} count points, 3 floats each
64
+ */
65
+ function sampleCenterline( P, basis, steps ) {
66
+
67
+ const cps = P.length / 3;
68
+ // bspline: every window of 4 control points is one span. bezier: 4 points per span,
69
+ // sharing an endpoint between consecutive spans.
70
+ const spans = basis === 'bspline' ? cps - 3 : Math.max( 1, ( cps - 1 ) / 3 );
71
+ if ( spans < 1 ) return null;
72
+
73
+ const total = Math.max( 1, Math.round( steps * spans ) );
74
+ const points = new Float64Array( ( total + 1 ) * 3 );
75
+ const w = [ 0, 0, 0, 0 ];
76
+
77
+ for ( let i = 0; i <= total; i ++ ) {
78
+
79
+ const u = ( i / total ) * spans;
80
+ let span = Math.floor( u );
81
+ if ( span >= spans ) span = spans - 1;
82
+ const t = u - span;
83
+
84
+ if ( basis === 'bspline' ) bsplineWeights( t, w );
85
+ else bezierWeights( t, w );
86
+
87
+ const base = basis === 'bspline' ? span : span * 3;
88
+ let x = 0, y = 0, z = 0;
89
+ for ( let k = 0; k < 4; k ++ ) {
90
+
91
+ const o = ( base + k ) * 3;
92
+ x += w[ k ] * P[ o ];
93
+ y += w[ k ] * P[ o + 1 ];
94
+ z += w[ k ] * P[ o + 2 ];
95
+
96
+ }
97
+
98
+ points[ i * 3 ] = x; points[ i * 3 + 1 ] = y; points[ i * 3 + 2 ] = z;
99
+
100
+ }
101
+
102
+ return { points, count: total + 1 };
103
+
104
+ }
105
+
106
+ /**
107
+ * @param {object} spec
108
+ * @param {ArrayLike<number>} spec.P - control points, 3 floats each
109
+ * @param {string} [spec.basis='bezier'] - 'bezier' | 'bspline'
110
+ * @param {number} [spec.width0=1]
111
+ * @param {number} [spec.width1=1]
112
+ * @param {ArrayLike<number>} [spec.N] - ribbon normals (start, end), 3 floats each
113
+ * @param {number} [spec.steps] - samples per spline span
114
+ * @param {number} [spec.sides=1] - 1 ribbon, 2 crossed ribbons, >=3 closed tube
115
+ * @returns {{positions: Float32Array, indices: Uint32Array}|null}
116
+ */
117
+ export function tessellateCurve( spec ) {
118
+
119
+ const { P, basis = 'bezier', width0 = 1, width1 = 1, N = null } = spec;
120
+ const steps = Math.max( 1, Math.round( spec.steps ?? DEFAULT_STEPS ) );
121
+ const sides = Math.max( 1, Math.round( spec.sides ?? 1 ) );
122
+
123
+ if ( ! P || P.length < 12 ) return null;
124
+
125
+ const line = sampleCenterline( P, basis, steps );
126
+ if ( ! line ) return null;
127
+
128
+ const { points, count } = line;
129
+ const rings = sides >= 3 ? sides : sides * 2;
130
+ const positions = new Float32Array( count * rings * 3 );
131
+
132
+ const tangent = [ 0, 0, 0 ];
133
+ const side = [ 0, 0, 0 ];
134
+ const up = [ 0, 0, 0 ];
135
+ const tmp = [ 0, 0, 0 ];
136
+ let haveSide = false;
137
+
138
+ for ( let i = 0; i < count; i ++ ) {
139
+
140
+ const a = Math.max( 0, i - 1 ), b = Math.min( count - 1, i + 1 );
141
+ tangent[ 0 ] = points[ b * 3 ] - points[ a * 3 ];
142
+ tangent[ 1 ] = points[ b * 3 + 1 ] - points[ a * 3 + 1 ];
143
+ tangent[ 2 ] = points[ b * 3 + 2 ] - points[ a * 3 + 2 ];
144
+ normalize( tangent );
145
+
146
+ if ( ! haveSide ) {
147
+
148
+ // Seed from the ribbon normal when the scene gives one, else from whichever
149
+ // axis the tangent leans on least.
150
+ if ( N && N.length >= 3 ) {
151
+
152
+ side[ 0 ] = N[ 0 ]; side[ 1 ] = N[ 1 ]; side[ 2 ] = N[ 2 ];
153
+ cross( tangent, side, tmp );
154
+ side[ 0 ] = tmp[ 0 ]; side[ 1 ] = tmp[ 1 ]; side[ 2 ] = tmp[ 2 ];
155
+
156
+ } else {
157
+
158
+ const ax = Math.abs( tangent[ 0 ] ), ay = Math.abs( tangent[ 1 ] ), az = Math.abs( tangent[ 2 ] );
159
+ const ref = ax < ay && ax < az ? [ 1, 0, 0 ] : ay < az ? [ 0, 1, 0 ] : [ 0, 0, 1 ];
160
+ cross( tangent, ref, side );
161
+
162
+ }
163
+
164
+ if ( Math.hypot( side[ 0 ], side[ 1 ], side[ 2 ] ) < 1e-9 ) cross( tangent, [ 0, 1, 0 ], side );
165
+ normalize( side );
166
+ haveSide = true;
167
+
168
+ } else {
169
+
170
+ // Rotation-minimising: drop the previous side back onto the new normal plane,
171
+ // so a coiled curve does not twist its ribbon.
172
+ const d = side[ 0 ] * tangent[ 0 ] + side[ 1 ] * tangent[ 1 ] + side[ 2 ] * tangent[ 2 ];
173
+ side[ 0 ] -= d * tangent[ 0 ];
174
+ side[ 1 ] -= d * tangent[ 1 ];
175
+ side[ 2 ] -= d * tangent[ 2 ];
176
+ if ( Math.hypot( side[ 0 ], side[ 1 ], side[ 2 ] ) < 1e-9 ) cross( tangent, [ 0, 1, 0 ], side );
177
+ normalize( side );
178
+
179
+ }
180
+
181
+ cross( tangent, side, up );
182
+ normalize( up );
183
+
184
+ const u = i / ( count - 1 || 1 );
185
+ const radius = ( width0 + ( width1 - width0 ) * u ) * 0.5;
186
+ const cx = points[ i * 3 ], cy = points[ i * 3 + 1 ], cz = points[ i * 3 + 2 ];
187
+
188
+ for ( let r = 0; r < rings; r ++ ) {
189
+
190
+ let dx, dy, dz;
191
+ if ( sides >= 3 ) {
192
+
193
+ const a2 = ( r / sides ) * Math.PI * 2;
194
+ const c = Math.cos( a2 ), s = Math.sin( a2 );
195
+ dx = side[ 0 ] * c + up[ 0 ] * s;
196
+ dy = side[ 1 ] * c + up[ 1 ] * s;
197
+ dz = side[ 2 ] * c + up[ 2 ] * s;
198
+
199
+ } else {
200
+
201
+ // Ribbon r spans -1..+1 along its own axis: `side` first, `up` for the cross.
202
+ const axis = r < 2 ? side : up;
203
+ const sign = r % 2 === 0 ? - 1 : 1;
204
+ dx = axis[ 0 ] * sign; dy = axis[ 1 ] * sign; dz = axis[ 2 ] * sign;
205
+
206
+ }
207
+
208
+ const o = ( i * rings + r ) * 3;
209
+ positions[ o ] = cx + dx * radius;
210
+ positions[ o + 1 ] = cy + dy * radius;
211
+ positions[ o + 2 ] = cz + dz * radius;
212
+
213
+ }
214
+
215
+ }
216
+
217
+ const quads = sides >= 3 ? sides : sides;
218
+ const indices = new Uint32Array( ( count - 1 ) * quads * 6 );
219
+ let w = 0;
220
+
221
+ for ( let i = 0; i < count - 1; i ++ ) {
222
+
223
+ for ( let q = 0; q < quads; q ++ ) {
224
+
225
+ const r0 = sides >= 3 ? q : q * 2;
226
+ const r1 = sides >= 3 ? ( q + 1 ) % sides : q * 2 + 1;
227
+ const a = i * rings + r0, b = i * rings + r1;
228
+ const c = ( i + 1 ) * rings + r1, d = ( i + 1 ) * rings + r0;
229
+ indices[ w ++ ] = a; indices[ w ++ ] = b; indices[ w ++ ] = c;
230
+ indices[ w ++ ] = a; indices[ w ++ ] = c; indices[ w ++ ] = d;
231
+
232
+ }
233
+
234
+ }
235
+
236
+ return { positions, indices: w === indices.length ? indices : indices.slice( 0, w ) };
237
+
238
+ }
239
+
240
+ /** Triangles `tessellateCurve` would produce, without building anything. */
241
+ export function curveTriangleCount( controlPoints, basis, steps, sides ) {
242
+
243
+ const cps = controlPoints / 3;
244
+ const spans = basis === 'bspline' ? cps - 3 : Math.max( 1, ( cps - 1 ) / 3 );
245
+ if ( spans < 1 ) return 0;
246
+ const segments = Math.max( 1, Math.round( Math.max( 1, steps ) * spans ) );
247
+ return segments * Math.max( 1, Math.round( sides ) ) * 2;
248
+
249
+ }
@@ -27,6 +27,25 @@ export function determinant3( m ) {
27
27
  }
28
28
 
29
29
  /** Matrix product a*b (applies b first, then a, to a column vector). */
30
+ /** a*b written into `out`, so a hot loop over millions of placements allocates nothing. */
31
+ export function multiplyInto( out, a, aOff, b ) {
32
+
33
+ for ( let c = 0; c < 4; c ++ ) {
34
+
35
+ const b0 = b[ c * 4 ], b1 = b[ c * 4 + 1 ], b2 = b[ c * 4 + 2 ], b3 = b[ c * 4 + 3 ];
36
+ for ( let r = 0; r < 4; r ++ ) {
37
+
38
+ out[ c * 4 + r ] = a[ aOff + r ] * b0 + a[ aOff + 4 + r ] * b1
39
+ + a[ aOff + 8 + r ] * b2 + a[ aOff + 12 + r ] * b3;
40
+
41
+ }
42
+
43
+ }
44
+
45
+ return out;
46
+
47
+ }
48
+
30
49
  export function multiply( a, b ) {
31
50
 
32
51
  const a11 = a[ 0 ], a21 = a[ 1 ], a31 = a[ 2 ], a41 = a[ 3 ];