rayzee 9.3.0 → 10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "9.3.0",
3
+ "version": "10.0.0",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -544,7 +544,10 @@ export const TRIANGLE_DATA_LAYOUT = {
544
544
  };
545
545
 
546
546
  export const TRI_MATERIAL_MASK = 0xffffff;
547
- export const TRI_SIDE_SHIFT = 24; // 0 front, 1 back, 2 double
547
+ export const TRI_SIDE_SHIFT = 24; // 0 front, 1 back, 2 double, 3 cutaway
548
+ // Front only for every ray, shadow rays too: a cut-away wall or roof. Set by `material.userData.cutaway`
549
+ // at extraction, or `app.setMaterialsSide( indices, MATERIAL_SIDE_CUTAWAY )` on a built scene.
550
+ export const MATERIAL_SIDE_CUTAWAY = 3;
548
551
  export const TRI_BLOCKER_SHIFT = 26; // 1 = blocks shadow rays whatever the settings
549
552
  export const TRI_BLOCKER_ALPHA_SHIFT = 27; // 1 = blocks them unless alpha-cutout shadows are on
550
553
 
@@ -3531,6 +3531,27 @@ export class PathTracerApp extends EventDispatcher {
3531
3531
 
3532
3532
  }
3533
3533
 
3534
+ /**
3535
+ * Sets one side on many materials at once — `MATERIAL_SIDE_CUTAWAY` for a cut-away room — in a
3536
+ * single pass over the triangles, where `setMaterialProperty( i, 'side', … )` makes one per material.
3537
+ * @param {Iterable<number>} materialIndices
3538
+ * @param {number} side
3539
+ */
3540
+ setMaterialsSide( materialIndices, side ) {
3541
+
3542
+ const indices = [ ...materialIndices ];
3543
+ if ( this._sdf?.spilled ) {
3544
+
3545
+ this.ensureSceneResident().then( () => this.setMaterialsSide( indices, side ) );
3546
+ return;
3547
+
3548
+ }
3549
+
3550
+ this.stages.pathTracer?.materialData.setMaterialsSide( indices, side );
3551
+ this.reset();
3552
+
3553
+ }
3554
+
3534
3555
  setMaterialProperty( materialIndex, property, value ) {
3535
3556
 
3536
3557
  // These rewrite triangle records, which a spilled scene has to read back first.
@@ -1,6 +1,6 @@
1
1
  import { BufferAttribute, Vector3, Vector2, Color, Matrix3, Matrix4, FrontSide, BackSide, DoubleSide, RGBAFormat } from "three";
2
2
  import {
3
- TEXTURE_CONSTANTS, TRIANGLE_DATA_LAYOUT, MATERIAL_DEFAULTS, packNormalOct, packTriangleFlags
3
+ TEXTURE_CONSTANTS, TRIANGLE_DATA_LAYOUT, MATERIAL_DEFAULTS, packNormalOct, packTriangleFlags, MATERIAL_SIDE_CUTAWAY
4
4
  } from '../EngineDefaults.js';
5
5
  import { ISSUE_CODES } from '../EngineIssues.js';
6
6
  import { ChunkedRecords, SHARED_MEMORY_AVAILABLE } from './ChunkedRecords.js';
@@ -1171,6 +1171,7 @@ export class GeometryExtractor {
1171
1171
  getMaterialSide( material ) {
1172
1172
 
1173
1173
  if ( material.transmission > 0.0 ) return 2;
1174
+ if ( material.userData?.cutaway === true ) return MATERIAL_SIDE_CUTAWAY;
1174
1175
  switch ( material.side ) {
1175
1176
 
1176
1177
  case FrontSide: return 0;
@@ -24,7 +24,7 @@ import {
24
24
  bool as tslBool,
25
25
  } from 'three/tsl';
26
26
 
27
- import { BVH_LEAF_MARKERS, BVH_MAX_INDEX, BVH_FOLDED_LEAF_MAX, TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, TLAS_LEAF_IDENTITY } from '../EngineDefaults.js';
27
+ import { BVH_LEAF_MARKERS, BVH_MAX_INDEX, BVH_FOLDED_LEAF_MAX, TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, TLAS_LEAF_IDENTITY, MATERIAL_SIDE_CUTAWAY } from '../EngineDefaults.js';
28
28
  import { HitInfo } from './Struct.js';
29
29
  import {
30
30
  getDatafromStorageBuffer, instanceRows, instanceNormalToWorld, unpackTriangleNormal, triangleRow, shadowFlagsSettle
@@ -213,12 +213,12 @@ const fastRayAABBDst = wgslFn( `
213
213
  // MAIN BVH TRAVERSAL
214
214
  // ================================================================================
215
215
 
216
- // Side from a triangle's flags word (row 4 .z): 0 front, 1 back, 2 double.
216
+ // Side from a triangle's flags word (row 4 .z): 0 front, 1 back, 2 double, 3 cutaway (front, every ray).
217
217
  export const triangleSide = ( flags ) => int( flags.shiftRight( uint( TRI_SIDE_SHIFT ) ).bitAnd( uint( 3 ) ) );
218
218
 
219
219
  // Whether a face of this side is seen by a ray; rayDotNormal < 0 is a front hit.
220
220
  export const sideAccepts = ( side, rayDotNormal ) => side.equal( int( 2 ) )
221
- .or( side.equal( int( 0 ) ).and( rayDotNormal.lessThan( - 0.0001 ) ) )
221
+ .or( side.equal( int( 0 ) ).or( side.equal( int( MATERIAL_SIDE_CUTAWAY ) ) ).and( rayDotNormal.lessThan( - 0.0001 ) ) )
222
222
  .or( side.equal( int( 1 ) ).and( rayDotNormal.greaterThan( 0.0001 ) ) );
223
223
 
224
224
  // Factory: the boxTests/triTests debug counters are compiled OUT of the hot closest-hit
@@ -324,7 +324,7 @@ const makeTraverseBVH = ( trackStats ) => Fn( ( [
324
324
  );
325
325
 
326
326
  // Side culling (inline; per-mesh visibility is at the BLAS-pointer level).
327
- If( eitherSide.or( sideAccepts( side, rayDotNormal ) ), () => {
327
+ If( eitherSide.and( side.notEqual( int( MATERIAL_SIDE_CUTAWAY ) ) ).or( sideAccepts( side, rayDotNormal ) ), () => {
328
328
 
329
329
  closestHit.didHit.assign( true );
330
330
  closestHit.dst.assign( t );
@@ -579,27 +579,43 @@ export const traverseBVHShadow = Fn( ( [
579
579
  // Per-mesh visibility is handled at the BLAS-pointer level.
580
580
  const uvData2 = triangleRow( triangleBuffer, triIndex, 4 ).toVar();
581
581
 
582
- closestHit.didHit.assign( true );
583
- closestHit.dst.assign( triResult.x );
584
- closestHit.materialIndex.assign( int( uvData2.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ) );
585
- closestHit.meshIndex.assign( int( uvData2.w ) );
586
- closestHit.instanceLeaf.assign( instLeaf );
582
+ // A cutaway face is see-through from behind for shadow rays too.
583
+ const seen = tslBool( true ).toVar();
584
+ If( triangleSide( uvData2.z ).equal( int( MATERIAL_SIDE_CUTAWAY ) ), () => {
587
585
 
588
- // Hit point is cheap (origin + dir*t). Geometric normal is deferred
589
- // to traceShadowRay — only the transmission branch needs it, so we
590
- // skip the cross+normalize for the (much more common) opaque-blocker
591
- // and alpha-cutout paths. Normal stays vec3(0) from struct init.
592
- closestHit.hitPoint.assign( worldOrigin.add( worldDirection.mul( triResult.x ) ) );
586
+ const w = float( 1.0 ).sub( triResult.y ).sub( triResult.z );
587
+ const n = unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 0 ).w ).mul( w )
588
+ .add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 1 ).w ).mul( triResult.y ) )
589
+ .add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 2 ).w ).mul( triResult.z ) );
590
+ seen.assign( rayDirection.dot( normalize( n ) ).lessThan( - 0.0001 ) );
593
591
 
594
- closestHit.uv.assign( vec2( triResult.y, triResult.z ) );
595
- closestHit.triangleIndex.assign( triIndex );
592
+ } );
593
+
594
+ If( seen, () => {
595
+
596
+ closestHit.didHit.assign( true );
597
+ closestHit.dst.assign( triResult.x );
598
+ closestHit.materialIndex.assign( int( uvData2.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ) );
599
+ closestHit.meshIndex.assign( int( uvData2.w ) );
600
+ closestHit.instanceLeaf.assign( instLeaf );
596
601
 
597
- // An opaque blocker settles the ray. A surface light passes through has to be
598
- // the nearest one, or the layers behind the first find are never counted.
599
- If( shadowFlagsSettle( uvData2.z ), () => {
602
+ // Hit point is cheap (origin + dir*t). Geometric normal is deferred
603
+ // to traceShadowRay — only the transmission branch needs it, so we
604
+ // skip the cross+normalize for the (much more common) opaque-blocker
605
+ // and alpha-cutout paths. Normal stays vec3(0) from struct init.
606
+ closestHit.hitPoint.assign( worldOrigin.add( worldDirection.mul( triResult.x ) ) );
600
607
 
601
- blocked.assign( true );
602
- Break();
608
+ closestHit.uv.assign( vec2( triResult.y, triResult.z ) );
609
+ closestHit.triangleIndex.assign( triIndex );
610
+
611
+ // An opaque blocker settles the ray. A surface light passes through has to be
612
+ // the nearest one, or the layers behind the first find are never counted.
613
+ If( shadowFlagsSettle( uvData2.z ), () => {
614
+
615
+ blocked.assign( true );
616
+ Break();
617
+
618
+ } );
603
619
 
604
620
  } );
605
621
 
package/src/index.js CHANGED
@@ -78,6 +78,7 @@ export {
78
78
  MAX_RESERVABLE_RENDER_SIZE,
79
79
  RENDER_PROFILES,
80
80
  getRenderProfile,
81
+ MATERIAL_SIDE_CUTAWAY,
81
82
  } from './EngineDefaults.js';
82
83
  export { sunPosition, timeForSunElevation, dayOfYearForMonth } from './Processor/SunPosition.js';
83
84
 
@@ -672,12 +672,43 @@ export class MaterialDataManager {
672
672
 
673
673
  }
674
674
 
675
+ /**
676
+ * One side for several materials in a single pass over the triangles; per-material edits scan
677
+ * every triangle once each.
678
+ * @param {Iterable<number>} materialIndices
679
+ * @param {number} side - 0 front, 1 back, 2 double, MATERIAL_SIDE_CUTAWAY
680
+ */
681
+ setMaterialsSide( materialIndices, side ) {
682
+
683
+ if ( ! this.materialStorageAttr ) return;
684
+
685
+ const indices = new Set( materialIndices );
686
+ const data = this.materialStorageAttr.array;
687
+ for ( const i of indices ) {
688
+
689
+ data[ i * M.FLOATS_PER_MATERIAL + M.SIDE ] = side;
690
+ ( this._hostSet[ i ] ??= new Map() ).set( 'side', side );
691
+
692
+ }
693
+
694
+ this._patchTriangleFlagForMaterials( indices, TRI_SIDE_SHIFT, 2, side );
695
+ this.materialStorageAttr.needsUpdate = true;
696
+ this._notifyReset();
697
+
698
+ }
699
+
700
+ _patchTriangleFlagForMaterial( materialIndex, shift, width, value ) {
701
+
702
+ this._patchTriangleFlagForMaterials( new Set( [ materialIndex ] ), shift, width, value );
703
+
704
+ }
705
+
675
706
  /**
676
707
  * Generic helper: rewrite `width` bits at `shift` in the flags lane of every triangle
677
- * whose materialIndex matches, then fire onTriangleDataChanged.
708
+ * whose materialIndex is in `indices`, then fire onTriangleDataChanged.
678
709
  * @private
679
710
  */
680
- _patchTriangleFlagForMaterial( materialIndex, shift, width, value ) {
711
+ _patchTriangleFlagForMaterials( indices, shift, width, value ) {
681
712
 
682
713
  const triInfo = this.callbacks.getTriangleData?.();
683
714
  // Chunked past the ~2 GB array cap; a flat array is the single-chunk case.
@@ -694,7 +725,7 @@ export class MaterialDataManager {
694
725
 
695
726
  const triData = records ? records.chunkFor( i ) : flat;
696
727
  const base = records ? records.baseOf( i ) : i * stride;
697
- if ( ( triData[ base + TRI_FLAGS_OFFSET ] & TRI_MATERIAL_MASK ) === materialIndex ) {
728
+ if ( indices.has( triData[ base + TRI_FLAGS_OFFSET ] & TRI_MATERIAL_MASK ) ) {
698
729
 
699
730
  triData[ base + TRI_FLAGS_OFFSET ] = ( ( triData[ base + TRI_FLAGS_OFFSET ] & ~ mask ) | bits ) >>> 0;
700
731
  patched ++;