rayzee 5.10.2 → 5.11.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": "5.10.2",
3
+ "version": "5.11.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",
@@ -65,7 +65,10 @@ export class PathTracerApp extends EventDispatcher {
65
65
  * @param {Object} [options] - Engine options
66
66
  * @param {boolean} [options.autoResize=true] - Automatically listen for window resize events
67
67
  * @param {boolean} [options.showStats=true] - Show the performance stats panel
68
- * @param {HTMLElement} [options.statsContainer] - DOM element to append the stats panel to (defaults to document.body)
68
+ * @param {HTMLElement} [options.container] - Single DOM parent the engine mounts all auxiliary
69
+ * elements into (HUD overlay, denoiser canvas, stats). Defaults to `canvas.parentNode`.
70
+ * @param {HTMLElement} [options.statsContainer] - Override mount target for the stats panel only.
71
+ * Defaults to `options.container`.
69
72
  */
70
73
  constructor( canvas, options = {} ) {
71
74
 
@@ -86,6 +89,7 @@ export class PathTracerApp extends EventDispatcher {
86
89
  this.canvas = canvas;
87
90
  this._autoResize = options.autoResize !== false;
88
91
  this._showStats = options.showStats !== false;
92
+ this._container = options.container || null;
89
93
  this._statsContainer = options.statsContainer || null;
90
94
 
91
95
  // ── Settings (single source of truth for all render parameters) ──
@@ -1550,7 +1554,7 @@ export class PathTracerApp extends EventDispatcher {
1550
1554
 
1551
1555
  _initStats() {
1552
1556
 
1553
- const container = this._statsContainer || this.canvas.parentElement || document.body;
1557
+ const container = this._statsContainer || this._container || this.canvas.parentElement || document.body;
1554
1558
  this._stats = createStats( this.renderer, container );
1555
1559
 
1556
1560
  }
@@ -1592,6 +1596,9 @@ export class PathTracerApp extends EventDispatcher {
1592
1596
  renderHeight: this.denoisingManager?._lastRenderHeight || this.canvas.clientHeight || 1,
1593
1597
  } );
1594
1598
 
1599
+ this._container = this._container || this.canvas.parentNode || null;
1600
+ this.overlayManager.mount( this._container );
1601
+
1595
1602
  }
1596
1603
 
1597
1604
 
@@ -351,7 +351,8 @@ class TextureCache {
351
351
  const width = texture.image.width || 0;
352
352
  const height = texture.image.height || 0;
353
353
  const src = texture.image.src || texture.uuid || '';
354
- hash += `${width}x${height}_${src.slice( - 8 )}_`;
354
+ const flipFlag = texture.flipY === false ? 'n' : 'f';
355
+ hash += `${width}x${height}_${src.slice( - 8 )}_${flipFlag}_`;
355
356
 
356
357
  }
357
358
 
@@ -681,12 +682,16 @@ export class TextureCreator {
681
682
 
682
683
  if ( ! texture?.image ) continue;
683
684
 
685
+ const flipY = texture.flipY !== false;
686
+
684
687
  try {
685
688
 
686
689
  // Option 1: Direct ImageBitmap transfer (when supported)
687
690
  if ( typeof createImageBitmap !== 'undefined' && texture.image instanceof HTMLImageElement ) {
688
691
 
689
- const bitmap = await createImageBitmap( texture.image );
692
+ const bitmap = await createImageBitmap( texture.image, {
693
+ imageOrientation: flipY ? 'flipY' : 'none',
694
+ } );
690
695
  texturesData.push( {
691
696
  bitmap: bitmap,
692
697
  width: texture.image.width,
@@ -696,16 +701,22 @@ export class TextureCreator {
696
701
 
697
702
  } else { // Option 2: Efficient canvas-based transfer
698
703
 
699
- const pair = this.canvasPool.getCanvasWithContext( texture.image.width, texture.image.height );
704
+ const w = texture.image.width;
705
+ const h = texture.image.height;
706
+ const bitmap = await createImageBitmap( texture.image, {
707
+ imageOrientation: flipY ? 'flipY' : 'none',
708
+ } );
709
+ const pair = this.canvasPool.getCanvasWithContext( w, h );
700
710
 
701
- pair.context.drawImage( texture.image, 0, 0 );
702
- const imageData = pair.context.getImageData( 0, 0, texture.image.width, texture.image.height );
711
+ pair.context.drawImage( bitmap, 0, 0 );
712
+ bitmap.close();
713
+ const imageData = pair.context.getImageData( 0, 0, w, h );
703
714
 
704
715
  // Transfer the underlying ArrayBuffer directly
705
716
  texturesData.push( {
706
717
  data: imageData.data.buffer, // Direct buffer transfer
707
- width: texture.image.width,
708
- height: texture.image.height,
718
+ width: w,
719
+ height: h,
709
720
  isImageData: true
710
721
  } );
711
722
 
@@ -759,11 +770,13 @@ export class TextureCreator {
759
770
  for ( let i = batchStart; i < batchEnd; i ++ ) {
760
771
 
761
772
  const texture = validTextures[ i ];
773
+ const flipY = texture.flipY !== false;
762
774
 
763
775
  const bitmapPromise = createImageBitmap( texture.image, {
764
776
  resizeWidth: maxWidth,
765
777
  resizeHeight: maxHeight,
766
- resizeQuality: 'high'
778
+ resizeQuality: 'high',
779
+ imageOrientation: flipY ? 'flipY' : 'none',
767
780
  } );
768
781
 
769
782
  batchPromises.push(
@@ -815,9 +828,16 @@ export class TextureCreator {
815
828
  for ( let i = 0; i < validTextures.length; i ++ ) {
816
829
 
817
830
  const texture = validTextures[ i ];
831
+ const bitmap = await createImageBitmap( texture.image, {
832
+ resizeWidth: maxWidth,
833
+ resizeHeight: maxHeight,
834
+ resizeQuality: 'high',
835
+ imageOrientation: texture.flipY !== false ? 'flipY' : 'none',
836
+ } );
818
837
 
819
838
  pair.context.clearRect( 0, 0, maxWidth, maxHeight );
820
- pair.context.drawImage( texture.image, 0, 0, maxWidth, maxHeight );
839
+ pair.context.drawImage( bitmap, 0, 0 );
840
+ bitmap.close();
821
841
 
822
842
  const imageData = pair.context.getImageData( 0, 0, maxWidth, maxHeight );
823
843
  const offset = maxWidth * maxHeight * 4 * i;
@@ -853,9 +873,16 @@ export class TextureCreator {
853
873
  for ( let i = 0; i < validTextures.length; i ++ ) {
854
874
 
855
875
  const texture = validTextures[ i ];
876
+ const bitmap = await createImageBitmap( texture.image, {
877
+ resizeWidth: maxWidth,
878
+ resizeHeight: maxHeight,
879
+ resizeQuality: 'high',
880
+ imageOrientation: texture.flipY !== false ? 'flipY' : 'none',
881
+ } );
856
882
 
857
883
  pair.context.clearRect( 0, 0, maxWidth, maxHeight );
858
- pair.context.drawImage( texture.image, 0, 0, maxWidth, maxHeight );
884
+ pair.context.drawImage( bitmap, 0, 0 );
885
+ bitmap.close();
859
886
 
860
887
  const imageData = pair.context.getImageData( 0, 0, maxWidth, maxHeight );
861
888
  const offset = maxWidth * maxHeight * 4 * i;
@@ -1301,7 +1328,7 @@ export class TextureCreator {
1301
1328
  const mip = tex.mipmaps[ 0 ];
1302
1329
  const idx = normalized.length;
1303
1330
  normalized.push( null ); // placeholder — filled after Promise.all
1304
- bitmapJobs.push( { index: idx, promise: _rawPixelsToBitmap( mip.data, mip.width, mip.height ) } );
1331
+ bitmapJobs.push( { index: idx, flipY: tex.flipY, promise: _rawPixelsToBitmap( mip.data, mip.width, mip.height ) } );
1305
1332
  continue;
1306
1333
 
1307
1334
  }
@@ -1323,7 +1350,7 @@ export class TextureCreator {
1323
1350
 
1324
1351
  const idx = normalized.length;
1325
1352
  normalized.push( null );
1326
- bitmapJobs.push( { index: idx, promise: _rawPixelsToBitmap( tex.image.data, tex.image.width, tex.image.height ) } );
1353
+ bitmapJobs.push( { index: idx, flipY: tex.flipY, promise: _rawPixelsToBitmap( tex.image.data, tex.image.width, tex.image.height ) } );
1327
1354
  continue;
1328
1355
 
1329
1356
  }
@@ -1339,14 +1366,14 @@ export class TextureCreator {
1339
1366
 
1340
1367
  for ( let i = 0; i < bitmapJobs.length; i ++ ) {
1341
1368
 
1342
- const { index } = bitmapJobs[ i ];
1369
+ const { index, flipY } = bitmapJobs[ i ];
1343
1370
  const result = results[ i ];
1344
1371
 
1345
1372
  if ( result.status === 'fulfilled' ) {
1346
1373
 
1347
1374
  const bitmap = result.value;
1348
1375
  bitmapsToClose.push( bitmap );
1349
- normalized[ index ] = { image: bitmap };
1376
+ normalized[ index ] = { image: bitmap, flipY };
1350
1377
 
1351
1378
  } else {
1352
1379
 
@@ -1367,7 +1394,7 @@ export class TextureCreator {
1367
1394
  const placeholder = new Uint8ClampedArray( [ 255, 255, 255, 255 ] );
1368
1395
  const bitmap = await createImageBitmap( new ImageData( placeholder, 1, 1 ) );
1369
1396
  bitmapsToClose.push( bitmap );
1370
- normalized[ i ] = { image: bitmap };
1397
+ normalized[ i ] = { image: bitmap, flipY: false };
1371
1398
 
1372
1399
  }
1373
1400
 
@@ -57,8 +57,8 @@ export class OverlayManager {
57
57
  }
58
58
 
59
59
  /**
60
- * Returns the HUD canvas element. The app should mount this on top of the
61
- * WebGPU canvas (absolute-positioned, pointer-events: none).
60
+ * Returns the HUD canvas element. Normally mounted automatically by
61
+ * {@link PathTracerApp}; exposed for advanced clients that mount it manually.
62
62
  * @returns {HTMLCanvasElement}
63
63
  */
64
64
  getHUDCanvas() {
@@ -67,6 +67,18 @@ export class OverlayManager {
67
67
 
68
68
  }
69
69
 
70
+ /**
71
+ * Mounts the HUD canvas into the given container. Idempotent — safe to call
72
+ * multiple times; re-mounts only when the parent differs.
73
+ * @param {HTMLElement} container
74
+ */
75
+ mount( container ) {
76
+
77
+ if ( ! container || this._hudCanvas.parentElement === container ) return;
78
+ container.appendChild( this._hudCanvas );
79
+
80
+ }
81
+
70
82
  // ═══════════════════════════════════════════════════════════════
71
83
  // Default helpers setup
72
84
  // ═══════════════════════════════════════════════════════════════