rayzee 7.18.0 → 7.19.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/README.md +1 -1
- package/dist/rayzee.es.js +2600 -2508
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +49 -49
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/PathTracerApp.js +31 -44
- package/src/Processor/utils.js +30 -0
- package/src/SceneHelpers.js +11 -1
- package/src/managers/OverlayManager.js +105 -48
- package/src/managers/helpers/OutlineHelper.js +42 -40
- package/src/managers/helpers/TransformGizmoHelper.js +61 -0
- package/src/managers/helpers/ViewOverlayRenderer.js +140 -0
- package/src/managers/helpers/overlaySurface.js +53 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TransformGizmoHelper — adapts {@link TransformManager} to the OverlayManager
|
|
3
|
+
* helper interface so the translate/rotate/scale gizmo draws on the same
|
|
4
|
+
* view-resolution surface as the light gizmos and the selection outline.
|
|
5
|
+
*
|
|
6
|
+
* An adapter rather than the interface on TransformManager itself: the manager
|
|
7
|
+
* is public engine API with its own lifecycle (PathTracerApp owns dispose), and
|
|
8
|
+
* the overlay must not dispose it a second time.
|
|
9
|
+
*
|
|
10
|
+
* Layer: 'scene'.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* overlayManager.register( 'transform', new TransformGizmoHelper( app.transformManager ) );
|
|
14
|
+
*/
|
|
15
|
+
export class TransformGizmoHelper {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('../TransformManager.js').TransformManager} transformManager
|
|
19
|
+
*/
|
|
20
|
+
constructor( transformManager ) {
|
|
21
|
+
|
|
22
|
+
this.layer = 'scene';
|
|
23
|
+
this._transformManager = transformManager;
|
|
24
|
+
this._enabled = true;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Nothing to draw unless the gizmo is attached to something. */
|
|
29
|
+
get visible() {
|
|
30
|
+
|
|
31
|
+
return this._enabled && this._transformManager.attachedObject !== null;
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render( renderer ) {
|
|
36
|
+
|
|
37
|
+
this._transformManager.render( renderer );
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
show() {
|
|
42
|
+
|
|
43
|
+
this._enabled = true;
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
hide() {
|
|
48
|
+
|
|
49
|
+
this._enabled = false;
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
dispose() {
|
|
54
|
+
|
|
55
|
+
// Only stops drawing — TransformManager is owned by the app, which
|
|
56
|
+
// disposes it separately.
|
|
57
|
+
this._enabled = false;
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { WebGPURenderer } from 'three/webgpu';
|
|
2
|
+
import { SRGBColorSpace } from 'three';
|
|
3
|
+
import { disposeRenderer } from '../../Processor/utils.js';
|
|
4
|
+
import { createOverlayCanvas, viewPixelSize, MAX_VIEW_DPR } from './overlaySurface.js';
|
|
5
|
+
|
|
6
|
+
/** Device-pixel ceiling per axis — guards against huge surfaces at high viewport zoom. */
|
|
7
|
+
const MAX_OVERLAY_DIM = 4096;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ViewOverlayRenderer — a transparent canvas sized to what the user actually
|
|
11
|
+
* sees, driven by its own WebGPURenderer.
|
|
12
|
+
*
|
|
13
|
+
* The main canvas' backing store is the *render* resolution (512 in preview,
|
|
14
|
+
* up to 4K for a final render), and CSS stretches it to fit the viewport. Any
|
|
15
|
+
* helper drawn into that backbuffer inherits the render resolution and is then
|
|
16
|
+
* rescaled by the browser — soft gizmos at low render scale, wasted pixels at
|
|
17
|
+
* high. This surface instead matches the on-screen size (bounding rect × DPR,
|
|
18
|
+
* so the viewport zoom is included), so gizmos and outlines stay crisp and
|
|
19
|
+
* screen-space-correct at any render resolution.
|
|
20
|
+
*
|
|
21
|
+
* The renderer shares the main renderer's `GPUDevice`, so this costs a
|
|
22
|
+
* swapchain, not a second WebGPU context. Helpers rendered here are also
|
|
23
|
+
* physically absent from the main canvas, so they can never leak into a saved
|
|
24
|
+
* image.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const overlay = new ViewOverlayRenderer();
|
|
28
|
+
* await overlay.init( { device: renderer.backend.device, sizeSource: canvas } );
|
|
29
|
+
* container.appendChild( overlay.canvas );
|
|
30
|
+
* // per frame:
|
|
31
|
+
* overlay.syncSize();
|
|
32
|
+
* overlay.begin();
|
|
33
|
+
* helperScene.render( overlay.renderer, camera );
|
|
34
|
+
*/
|
|
35
|
+
export class ViewOverlayRenderer {
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
|
|
39
|
+
// Starts parked: three allocates the output-pass framebuffer (~30 MiB at
|
|
40
|
+
// 1600²) on the first clear, so a session that never shows a helper never
|
|
41
|
+
// pays for it.
|
|
42
|
+
this.canvas = createOverlayCanvas( true );
|
|
43
|
+
this._visible = false;
|
|
44
|
+
|
|
45
|
+
/** @type {import('three/webgpu').WebGPURenderer|null} */
|
|
46
|
+
this.renderer = null;
|
|
47
|
+
|
|
48
|
+
this._sizeSource = null;
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates the renderer on the shared device.
|
|
54
|
+
*
|
|
55
|
+
* @param {Object} config
|
|
56
|
+
* @param {GPUDevice} config.device - Device to share with the main renderer
|
|
57
|
+
* @param {HTMLElement} config.sizeSource - Element whose on-screen box defines the overlay size
|
|
58
|
+
* @returns {Promise<ViewOverlayRenderer>}
|
|
59
|
+
*/
|
|
60
|
+
async init( { device, sizeSource } ) {
|
|
61
|
+
|
|
62
|
+
this._sizeSource = sizeSource;
|
|
63
|
+
|
|
64
|
+
// A second backend claims the device's single uncaptured-error slot during
|
|
65
|
+
// init(). Hand it back so the owner's handler keeps receiving errors.
|
|
66
|
+
const deviceErrorHandler = device.onuncapturederror;
|
|
67
|
+
|
|
68
|
+
// At DPR ≥ 2 the browser's downscale already supersamples the edges, so MSAA
|
|
69
|
+
// would only add a multisampled attachment for no visible gain. Below that
|
|
70
|
+
// there is nothing to hide jaggies, so pay for it.
|
|
71
|
+
const renderer = new WebGPURenderer( {
|
|
72
|
+
canvas: this.canvas,
|
|
73
|
+
device,
|
|
74
|
+
alpha: true,
|
|
75
|
+
antialias: ( window.devicePixelRatio || 1 ) < MAX_VIEW_DPR,
|
|
76
|
+
} );
|
|
77
|
+
|
|
78
|
+
await renderer.init();
|
|
79
|
+
device.onuncapturederror = deviceErrorHandler;
|
|
80
|
+
|
|
81
|
+
renderer.outputColorSpace = SRGBColorSpace;
|
|
82
|
+
renderer.setPixelRatio( 1.0 );
|
|
83
|
+
renderer.setClearColor( 0x000000, 0 );
|
|
84
|
+
// Every frame starts with an explicit clear() in begin(); helpers then
|
|
85
|
+
// stack on top of each other without wiping what came before.
|
|
86
|
+
renderer.autoClear = false;
|
|
87
|
+
|
|
88
|
+
this.renderer = renderer;
|
|
89
|
+
this.syncSize();
|
|
90
|
+
|
|
91
|
+
return this;
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get isVisible() {
|
|
96
|
+
|
|
97
|
+
return this._visible;
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Matches the backing store to the size source's on-screen box. */
|
|
102
|
+
syncSize() {
|
|
103
|
+
|
|
104
|
+
if ( ! this.renderer ) return;
|
|
105
|
+
|
|
106
|
+
const size = viewPixelSize( this._sizeSource, MAX_OVERLAY_DIM );
|
|
107
|
+
if ( ! size || ( size.width === this.canvas.width && size.height === this.canvas.height ) ) return;
|
|
108
|
+
|
|
109
|
+
this.renderer.setSize( size.width, size.height, false );
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Clears the surface to fully transparent. Call once before drawing helpers. */
|
|
114
|
+
begin() {
|
|
115
|
+
|
|
116
|
+
if ( ! this.renderer ) return;
|
|
117
|
+
|
|
118
|
+
this.renderer.setRenderTarget( null );
|
|
119
|
+
this.renderer.clear();
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
setVisible( visible ) {
|
|
124
|
+
|
|
125
|
+
if ( this._visible === visible ) return;
|
|
126
|
+
|
|
127
|
+
this._visible = visible;
|
|
128
|
+
this.canvas.style.display = visible ? '' : 'none';
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
dispose() {
|
|
133
|
+
|
|
134
|
+
disposeRenderer( this.renderer );
|
|
135
|
+
this.renderer = null;
|
|
136
|
+
this.canvas.parentElement?.removeChild( this.canvas );
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing for the overlay canvases stacked over the main WebGPU canvas
|
|
3
|
+
* (the 3D scene layer and the 2D HUD). Both must agree on positioning and on
|
|
4
|
+
* how an on-screen box maps to device pixels, so both come from here.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Beyond 2× the extra pixels are invisible but the cost is not. */
|
|
8
|
+
export const MAX_VIEW_DPR = 2;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a transparent, click-through canvas that covers its container.
|
|
12
|
+
* @param {boolean} [hidden=false] - Start parked (`display:none`)
|
|
13
|
+
* @returns {HTMLCanvasElement}
|
|
14
|
+
*/
|
|
15
|
+
export function createOverlayCanvas( hidden = false ) {
|
|
16
|
+
|
|
17
|
+
const canvas = document.createElement( 'canvas' );
|
|
18
|
+
canvas.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;'
|
|
19
|
+
+ ( hidden ? 'display:none;' : '' );
|
|
20
|
+
return canvas;
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Device-pixel size of an element as it actually appears on screen. Uses the
|
|
26
|
+
* bounding rect (not clientWidth) so the viewport's CSS zoom is included.
|
|
27
|
+
*
|
|
28
|
+
* @param {HTMLElement} element
|
|
29
|
+
* @param {number} [maxDim=Infinity] - Per-axis ceiling; the box is scaled down uniformly to fit
|
|
30
|
+
* @returns {{ width: number, height: number }|null} null when the element has no layout box
|
|
31
|
+
*/
|
|
32
|
+
export function viewPixelSize( element, maxDim = Infinity ) {
|
|
33
|
+
|
|
34
|
+
const rect = element?.getBoundingClientRect();
|
|
35
|
+
if ( ! rect || rect.width === 0 || rect.height === 0 ) return null;
|
|
36
|
+
|
|
37
|
+
const dpr = Math.min( window.devicePixelRatio || 1, MAX_VIEW_DPR );
|
|
38
|
+
let width = Math.round( rect.width * dpr );
|
|
39
|
+
let height = Math.round( rect.height * dpr );
|
|
40
|
+
|
|
41
|
+
// Uniform scale — a per-axis clamp would skew the aspect and slide the
|
|
42
|
+
// helpers out of alignment with the rendered image underneath.
|
|
43
|
+
const clamp = maxDim / Math.max( width, height );
|
|
44
|
+
if ( clamp < 1 ) {
|
|
45
|
+
|
|
46
|
+
width = Math.max( 1, Math.round( width * clamp ) );
|
|
47
|
+
height = Math.max( 1, Math.round( height * clamp ) );
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { width, height };
|
|
52
|
+
|
|
53
|
+
}
|