egaki 0.8.0 → 0.9.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/CHANGELOG.md +24 -0
- package/dist/vite/angled-screen-shader.d.ts +45 -0
- package/dist/vite/angled-screen-shader.d.ts.map +1 -0
- package/dist/vite/angled-screen-shader.js +573 -0
- package/dist/vite/angled-screen.d.ts +12 -3
- package/dist/vite/angled-screen.d.ts.map +1 -1
- package/dist/vite/angled-screen.js +12 -3
- package/dist/vite/mdx-video.d.ts +4 -2
- package/dist/vite/mdx-video.d.ts.map +1 -1
- package/dist/vite/mdx-video.js +4 -2
- package/dist/vite/mdx-video.test.js +1 -0
- package/dist/vite/render-client.d.ts +27 -0
- package/dist/vite/render-client.d.ts.map +1 -1
- package/dist/vite/render-client.js +47 -51
- package/dist/vite/sdk.d.ts.map +1 -1
- package/dist/vite/sdk.js +4 -3
- package/dist/vite/shader-renderer.d.ts +4 -0
- package/dist/vite/shader-renderer.d.ts.map +1 -1
- package/dist/vite/shader-renderer.js +3 -3
- package/dist/vite/tweakpane-hook.d.ts +6 -0
- package/dist/vite/tweakpane-hook.d.ts.map +1 -1
- package/dist/vite/tweakpane-hook.js +101 -4
- package/package.json +6 -6
- package/src/vite/angled-screen-shader.tsx +706 -0
- package/src/vite/angled-screen.tsx +13 -4
- package/src/vite/mdx-video.test.tsx +1 -0
- package/src/vite/mdx-video.tsx +4 -1
- package/src/vite/render-client.ts +52 -51
- package/src/vite/sdk.ts +4 -3
- package/src/vite/shader-renderer.tsx +3 -3
- package/src/vite/tweakpane-hook.tsx +102 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.9.0
|
|
6
|
+
|
|
7
|
+
1. **`<AngledScreen>` with true WebGL depth-of-field** — the 3D tilted screen component is now shader-based. Children DOM is captured per frame with Chromium's HTML-in-canvas API and rendered as a perspective-tilted plane in a WebGL2 fragment shader, giving true per-pixel depth. Depth drives bokeh blur (the exact Three.js BokehShader 41-tap ring kernel), fog toward `backgroundColor`, and subtle film grain. Available in MDX without imports:
|
|
8
|
+
|
|
9
|
+
```mdx
|
|
10
|
+
# Hero duration=5s
|
|
11
|
+
|
|
12
|
+
<AngledScreen rotateX={9} rotateY={-17} translateZ={120} backgroundColor="#0a0608">
|
|
13
|
+
<Img src="/screenshot.png" style={{ width: '100%', borderRadius: 14 }} />
|
|
14
|
+
</AngledScreen>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
New depth-of-field props: `bokeh`, `aperture` (default 0.5), `maxBlur` (default 0.12), `focus` (default 0 = auto-track the near side so blur ramps toward the far edge), plus `fog` and `grainIntensity`. Transform props (`perspective`, `rotateX/Y/Z`, `translateX`, `translateZ`) keep the same semantics as before.
|
|
18
|
+
|
|
19
|
+
Requires Chrome 149+ with `chrome://flags/#canvas-draw-element`. When HTML-in-canvas is unsupported, the previous CSS version renders automatically as a fallback (also exported directly as `BasicAngledScreen`).
|
|
20
|
+
|
|
21
|
+
2. **Tweakpane controls now follow animated props** — `useTweakpane`-backed components (like `AngledScreen`) previously froze all values at mount, so animated props like `translateX={interpolate(frame, ...)}` stayed stuck at frame 0. Untouched keys now return the live value every frame and their sliders visibly animate during playback. Once you touch a control, that key freezes to your value until remount, so manual overrides survive seeks. "Copy changes" now only reports keys you actually edited.
|
|
22
|
+
|
|
23
|
+
3. **Fixed flat exports and screenshots of shader scenes** — web-renderer exports, SDK `screenshot()`, `filmstrip()`, and `export()` of scenes with HTML-in-canvas shaders came out without the shader output (the export scaffold is hidden, so Chromium produced no paint records for the capture). The composition root is now wrapped in a visible container for all web-renderer entry points, restoring shader output in every export path.
|
|
24
|
+
|
|
25
|
+
4. **Fixed export hang with hidden `<AngledScreen>` instances** — instances inside hidden ancestors (LayoutTransition ghosts, inactive timed instances) never fired their paint callback, leaving an unreleased delayRender handle that stalled exports ~28s per frame. Handle registration is now gated on actual visibility.
|
|
26
|
+
|
|
27
|
+
5. **Remotion bumped to 4.0.494** — required for WebGL contexts inside HTML-in-canvas components (earlier versions grabbed a 2D context on the offscreen canvas first, blocking WebGL).
|
|
28
|
+
|
|
5
29
|
## 0.8.0
|
|
6
30
|
|
|
7
31
|
1. **`egaki bpm` command** — detect BPM of audio files locally using peak detection and interval analysis. No API call needed, runs entirely on device via `node-web-audio-api`:
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface AngledScreenProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
/** CSS perspective distance in px. Smaller = more dramatic. Default 1200. */
|
|
5
|
+
perspective?: number;
|
|
6
|
+
/** X-axis rotation in degrees (tilts top/bottom). Default 8. */
|
|
7
|
+
rotateX?: number;
|
|
8
|
+
/** Y-axis rotation in degrees (angles left/right). Default -12. */
|
|
9
|
+
rotateY?: number;
|
|
10
|
+
/** Z-axis rotation in degrees. Default 0. */
|
|
11
|
+
rotateZ?: number;
|
|
12
|
+
/** Shift the plane horizontally in px. Positive = right. Default 0. */
|
|
13
|
+
translateX?: number;
|
|
14
|
+
/** Push the plane forward/back in px. Positive = closer/larger. Default 0. */
|
|
15
|
+
translateZ?: number;
|
|
16
|
+
/** Enable depth-based bokeh blur. Default true. */
|
|
17
|
+
bokeh?: boolean;
|
|
18
|
+
/** Aperture — bigger values for shallower depth of field. Blur grows
|
|
19
|
+
* linearly with distance from the focus plane, scaled by this.
|
|
20
|
+
* Same semantics as Three's BokehPass. Default 0.5 (plugin value). */
|
|
21
|
+
aperture?: number;
|
|
22
|
+
/** Max blur in screen-UV units (fraction of frame size), the saturation
|
|
23
|
+
* cap of the blur. Same as Three's `maxblur`. Default 0.12 (plugin value). */
|
|
24
|
+
maxBlur?: number;
|
|
25
|
+
/** Focus distance as a fraction of `perspective`. Default 0 = auto: focus
|
|
26
|
+
* tracks the nearest visible point of the plane, so the near side stays
|
|
27
|
+
* sharp and blur ramps progressively toward the far edge only. */
|
|
28
|
+
focus?: number;
|
|
29
|
+
/** Fog toward backgroundColor at the far depth (0-1). Default 0.35. */
|
|
30
|
+
fog?: number;
|
|
31
|
+
/** Film grain intensity. Default 0.02. */
|
|
32
|
+
grainIntensity?: number;
|
|
33
|
+
/** Background color behind the tilted screen. Default '#000000'. */
|
|
34
|
+
backgroundColor?: string;
|
|
35
|
+
/** Width of the inner content wrapper. Default '80%'. */
|
|
36
|
+
width?: string | number;
|
|
37
|
+
/** Height of the inner content wrapper. Default 'auto'. */
|
|
38
|
+
height?: string | number;
|
|
39
|
+
/** Render normalized depth as grayscale for debugging the DOF ramp. */
|
|
40
|
+
debug?: boolean;
|
|
41
|
+
/** Optional inline style on the outer container. */
|
|
42
|
+
style?: CSSProperties;
|
|
43
|
+
}
|
|
44
|
+
export declare function AngledScreen(props: AngledScreenProps): import("react").JSX.Element;
|
|
45
|
+
//# sourceMappingURL=angled-screen-shader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angled-screen-shader.d.ts","sourceRoot":"","sources":["../../src/vite/angled-screen-shader.tsx"],"names":[],"mappings":"AAyEA,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAKhG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,CAAA;IAGnB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IAGnB,mDAAmD;IACnD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;2EAEuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;mFAC+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;uEAEmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAA;IAGd,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0CAA0C;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAA;IAGxB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAExB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAA;IAEf,oDAAoD;IACpD,KAAK,CAAC,EAAE,aAAa,CAAA;CACtB;AAwND,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAuWpD"}
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* AngledScreen — WebGL depth-of-field 3D screen via Remotion HtmlInCanvas.
|
|
5
|
+
*
|
|
6
|
+
* Shader-based successor to BasicAngledScreen (angled-screen.tsx). Children
|
|
7
|
+
* DOM is captured per frame with the HTML-in-canvas API (texElementImage2D)
|
|
8
|
+
* and rendered as a 3D-tilted plane in a WebGL2 fragment shader:
|
|
9
|
+
*
|
|
10
|
+
* screen pixel → ray from CSS-style perspective camera → ray/plane
|
|
11
|
+
* intersection → plane-local UV → sample DOM texture
|
|
12
|
+
*
|
|
13
|
+
* The intersection distance gives true per-pixel depth, which drives:
|
|
14
|
+
* - bokeh blur (poisson-disc, radius grows with distance from focus)
|
|
15
|
+
* - fog toward backgroundColor (like the original Three.js plugin's
|
|
16
|
+
* scene fog, unframer-private/plugin-angled-screen BokehPass setup)
|
|
17
|
+
* - film grain (from the plugin's filmGrainShader, u_time-seeded)
|
|
18
|
+
*
|
|
19
|
+
* Prop names match BasicAngledScreen where semantics carry over:
|
|
20
|
+
* perspective/rotateX/rotateY/rotateZ/translateZ are interpreted exactly
|
|
21
|
+
* like the CSS transform version. Depth of field uses the exact Three.js
|
|
22
|
+
* BokehShader algorithm and the plugin's defaults (aperture 0.5,
|
|
23
|
+
* maxblur 0.12, focus at the plane center): blur grows linearly with the
|
|
24
|
+
* signed distance from the focus plane and saturates at maxBlur, sampled
|
|
25
|
+
* with the 41-tap Martins Upitis ring kernel in screen space.
|
|
26
|
+
*
|
|
27
|
+
* Requires Chrome 149+ with chrome://flags/#canvas-draw-element enabled.
|
|
28
|
+
* Falls back to the CSS BasicAngledScreen when unsupported.
|
|
29
|
+
*
|
|
30
|
+
* EXPORT PATH (DOM-composer fallback), hard-won — don't re-debug:
|
|
31
|
+
* When Remotion's nested HTML-in-canvas probe fails (Chromium changes the
|
|
32
|
+
* API shape between builds), web-renderer composes exports/stills with the
|
|
33
|
+
* DOM composer instead of native capture. Three things must line up for the
|
|
34
|
+
* shader output to survive that path:
|
|
35
|
+
* 1. The scaffold wrapper is visibility:hidden, and Chromium creates no
|
|
36
|
+
* paint records for hidden subtrees — captureElementImage() throws
|
|
37
|
+
* "missing paint record" and HtmlInCanvas SILENTLY skips onInit/onPaint
|
|
38
|
+
* (canRetryMissingPaintRecord swallows it). Fix: the egaki render entry
|
|
39
|
+
* points wrap the composition root in a visibility:visible div
|
|
40
|
+
* (wrapForWebRenderer in render-client.ts), restoring paint records
|
|
41
|
+
* while preserving deeper visibility:hidden semantics (e.g.
|
|
42
|
+
* LayoutTransition's hidden inactive instances). Do NOT force
|
|
43
|
+
* visibility on this component's canvas — that leaks into preview.
|
|
44
|
+
* 2. remotion's HtmlInCanvas has a scoping bug (present through 4.0.495):
|
|
45
|
+
* it registers delayRender() on the GLOBAL window scope but the
|
|
46
|
+
* scaffold's waitForReady() polls its own delayRenderScope — so the
|
|
47
|
+
* renderer never waits for the nested canvas paint. Fix: this component
|
|
48
|
+
* registers its own properly-scoped useDelayRender() handle per frame
|
|
49
|
+
* while isRendering, calls requestPaint(), and releases the handle only
|
|
50
|
+
* after onPaint has drawn and blitted the frame.
|
|
51
|
+
* TODO: remove the scoped-delayRender workaround (and re-evaluate the
|
|
52
|
+
* visibility wrapper in render-client.ts) once this upstream issue is
|
|
53
|
+
* fixed: https://github.com/remotion-dev/remotion/issues/9367
|
|
54
|
+
* 3. The DOM composer cannot read the transferred placeholder canvas, so
|
|
55
|
+
* every painted GL frame is blitted into a plain 2D mirror canvas that
|
|
56
|
+
* the composer picks up like any regular canvas.
|
|
57
|
+
*
|
|
58
|
+
* Coordinate mapping notes (hard-won, don't re-derive):
|
|
59
|
+
* - GL space is y-up, z toward viewer, camera at (0, 0, perspective).
|
|
60
|
+
* - CSS rotateX(+)/rotateY(+) match standard GL Rx/Ry; CSS rotateZ is
|
|
61
|
+
* visually clockwise (y-down), so we negate it for GL.
|
|
62
|
+
* - UNPACK_FLIP_Y_WEBGL=true puts DOM top at v=1, matching y-up plane UVs.
|
|
63
|
+
*/
|
|
64
|
+
import { HtmlInCanvas, useCurrentFrame, useDelayRender, useRemotionEnvironment, useVideoConfig, } from 'remotion';
|
|
65
|
+
import { useCallback, useLayoutEffect, useRef } from 'react';
|
|
66
|
+
import { useTweakpane } from "./tweakpane-hook.js";
|
|
67
|
+
import { compileGlShader, linkGlProgram, parseHexColor } from "./shader-renderer.js";
|
|
68
|
+
import { BasicAngledScreen } from "./angled-screen.js";
|
|
69
|
+
const VERTEX_SHADER = `#version 300 es
|
|
70
|
+
precision highp float;
|
|
71
|
+
in vec2 a_position;
|
|
72
|
+
in vec2 a_texCoord;
|
|
73
|
+
out vec2 v_uv;
|
|
74
|
+
void main() {
|
|
75
|
+
v_uv = a_texCoord;
|
|
76
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
const FRAGMENT_SHADER = `#version 300 es
|
|
80
|
+
precision highp float;
|
|
81
|
+
|
|
82
|
+
uniform sampler2D u_tex;
|
|
83
|
+
uniform mat3 u_rot; // plane local -> world rotation
|
|
84
|
+
uniform vec2 u_planeSize; // logical composition px
|
|
85
|
+
uniform float u_perspective; // camera z in px
|
|
86
|
+
uniform float u_translateX; // plane center x in px
|
|
87
|
+
uniform float u_translateZ; // plane center z in px
|
|
88
|
+
uniform float u_depthMin; // nearest corner distance to camera
|
|
89
|
+
uniform float u_depthMax; // farthest corner distance to camera
|
|
90
|
+
uniform float u_bokehEnabled;
|
|
91
|
+
uniform float u_aperture; // Three BokehShader aperture (plugin: 0.5)
|
|
92
|
+
uniform float u_maxblur; // Three BokehShader maxblur, screen-UV units (plugin: 0.12)
|
|
93
|
+
uniform float u_focusDist; // focus distance in px along the ray
|
|
94
|
+
uniform float u_aspect; // width / height, for circular bokeh
|
|
95
|
+
uniform float u_fog;
|
|
96
|
+
uniform float u_grain;
|
|
97
|
+
uniform vec4 u_background;
|
|
98
|
+
uniform float u_debug;
|
|
99
|
+
uniform float u_time;
|
|
100
|
+
|
|
101
|
+
in vec2 v_uv;
|
|
102
|
+
out vec4 fragColor;
|
|
103
|
+
|
|
104
|
+
float rand(vec2 co) {
|
|
105
|
+
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Three.js BokehShader ring kernel (Martins Upitis lens blur):
|
|
109
|
+
// 16 taps at radius 1.0, 8 at 0.9, 8 at 0.7, 8 at 0.4, plus center = 41.
|
|
110
|
+
const vec2 RING1[16] = vec2[](
|
|
111
|
+
vec2(0.0, 0.4), vec2(0.15, 0.37), vec2(0.29, 0.29), vec2(-0.37, 0.15),
|
|
112
|
+
vec2(0.40, 0.0), vec2(0.37, -0.15), vec2(0.29, -0.29), vec2(-0.15, -0.37),
|
|
113
|
+
vec2(0.0, -0.4), vec2(-0.15, 0.37), vec2(-0.29, 0.29), vec2(0.37, 0.15),
|
|
114
|
+
vec2(-0.4, 0.0), vec2(-0.37, -0.15), vec2(-0.29, -0.29), vec2(0.15, -0.37)
|
|
115
|
+
);
|
|
116
|
+
const vec2 RING9[8] = vec2[](
|
|
117
|
+
vec2(0.15, 0.37), vec2(-0.37, 0.15), vec2(0.37, -0.15), vec2(-0.15, -0.37),
|
|
118
|
+
vec2(-0.15, 0.37), vec2(0.37, 0.15), vec2(-0.37, -0.15), vec2(0.15, -0.37)
|
|
119
|
+
);
|
|
120
|
+
const vec2 RING7[8] = vec2[](
|
|
121
|
+
vec2(0.29, 0.29), vec2(0.40, 0.0), vec2(0.29, -0.29), vec2(0.0, -0.4),
|
|
122
|
+
vec2(-0.29, 0.29), vec2(-0.4, 0.0), vec2(-0.29, -0.29), vec2(0.0, 0.4)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Intersect the camera ray through screen uv suv with the tilted plane.
|
|
126
|
+
// Returns ray distance t, or -1.0 on miss. Outputs plane uv + edge coverage.
|
|
127
|
+
float intersectPlane(vec2 suv, out vec2 uv, out float coverage) {
|
|
128
|
+
vec2 screen = (suv - 0.5) * u_planeSize;
|
|
129
|
+
vec3 C = vec3(0.0, 0.0, u_perspective);
|
|
130
|
+
vec3 dir = normalize(vec3(screen, 0.0) - C);
|
|
131
|
+
vec3 n = u_rot * vec3(0.0, 0.0, 1.0);
|
|
132
|
+
vec3 q0 = vec3(u_translateX, 0.0, u_translateZ);
|
|
133
|
+
float denom = dot(n, dir);
|
|
134
|
+
if (abs(denom) < 1e-6) return -1.0;
|
|
135
|
+
float t = dot(n, q0 - C) / denom;
|
|
136
|
+
if (t <= 0.0) return -1.0;
|
|
137
|
+
vec3 local = transpose(u_rot) * (C + t * dir - q0);
|
|
138
|
+
uv = local.xy / u_planeSize + 0.5;
|
|
139
|
+
vec2 edgePx = (0.5 - abs(uv - 0.5)) * u_planeSize;
|
|
140
|
+
coverage = clamp(min(edgePx.x, edgePx.y) + 0.5, 0.0, 1.0);
|
|
141
|
+
return t;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Full scene color at a screen position: plane composited over background.
|
|
145
|
+
// Screen-space sampling like Three's BokehPass (which blurs the rendered
|
|
146
|
+
// frame), so blur also softens the plane edges into the background.
|
|
147
|
+
vec4 sampleScene(vec2 suv) {
|
|
148
|
+
vec2 uv;
|
|
149
|
+
float coverage;
|
|
150
|
+
float t = intersectPlane(suv, uv, coverage);
|
|
151
|
+
if (t < 0.0 || coverage <= 0.0) return u_background;
|
|
152
|
+
vec4 c = texture(u_tex, uv);
|
|
153
|
+
c = vec4(mix(u_background.rgb, c.rgb, c.a), 1.0);
|
|
154
|
+
return mix(u_background, c, coverage);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
void main() {
|
|
158
|
+
vec4 bg = u_background;
|
|
159
|
+
|
|
160
|
+
vec2 uv;
|
|
161
|
+
float coverage;
|
|
162
|
+
float t = intersectPlane(v_uv, uv, coverage);
|
|
163
|
+
bool hit = t > 0.0 && coverage > 0.0;
|
|
164
|
+
|
|
165
|
+
// Normalized depth across the visible plane (0 = near, 1 = far) — used
|
|
166
|
+
// for fog and the debug view.
|
|
167
|
+
float depth01 = hit
|
|
168
|
+
? clamp((t - u_depthMin) / max(u_depthMax - u_depthMin, 1e-3), 0.0, 1.0)
|
|
169
|
+
: 1.0;
|
|
170
|
+
|
|
171
|
+
if (u_debug > 0.5) {
|
|
172
|
+
fragColor = hit ? mix(bg, vec4(vec3(depth01), 1.0), coverage) : bg;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Three.js BokehShader blur factor, made one-sided: only content BEYOND
|
|
177
|
+
// the focus plane blurs (the far side of the tilted screen). The near side
|
|
178
|
+
// stays tack sharp. Background pixels (no hit) count as far away, so the
|
|
179
|
+
// plane silhouette melts softly where it is out of focus.
|
|
180
|
+
float factor = hit ? max(t - u_focusDist, 0.0) / u_perspective : 1.0;
|
|
181
|
+
vec2 dofblur = vec2(clamp(factor * u_aperture, 0.0, u_maxblur)) * u_bokehEnabled;
|
|
182
|
+
|
|
183
|
+
vec2 aspectcorrect = vec2(1.0, u_aspect);
|
|
184
|
+
vec2 dofblur9 = dofblur * 0.9;
|
|
185
|
+
vec2 dofblur7 = dofblur * 0.7;
|
|
186
|
+
vec2 dofblur4 = dofblur * 0.4;
|
|
187
|
+
|
|
188
|
+
vec4 col = sampleScene(v_uv);
|
|
189
|
+
for (int i = 0; i < 16; i++) {
|
|
190
|
+
col += sampleScene(v_uv + RING1[i] * aspectcorrect * dofblur);
|
|
191
|
+
}
|
|
192
|
+
for (int i = 0; i < 8; i++) {
|
|
193
|
+
col += sampleScene(v_uv + RING9[i] * aspectcorrect * dofblur9);
|
|
194
|
+
}
|
|
195
|
+
for (int i = 0; i < 8; i++) {
|
|
196
|
+
col += sampleScene(v_uv + RING7[i] * aspectcorrect * dofblur7);
|
|
197
|
+
}
|
|
198
|
+
for (int i = 0; i < 8; i++) {
|
|
199
|
+
col += sampleScene(v_uv + RING7[i] * aspectcorrect * dofblur4);
|
|
200
|
+
}
|
|
201
|
+
col /= 41.0;
|
|
202
|
+
|
|
203
|
+
// Fog toward background with depth (plugin's THREE.Fog equivalent)
|
|
204
|
+
col.rgb = mix(col.rgb, bg.rgb, u_fog * depth01 * (hit ? 1.0 : 0.0));
|
|
205
|
+
|
|
206
|
+
// Film grain (plugin's filmGrainShader)
|
|
207
|
+
col.rgb += (rand(v_uv + fract(u_time)) - 0.5) * u_grain;
|
|
208
|
+
col.a = 1.0;
|
|
209
|
+
|
|
210
|
+
fragColor = col;
|
|
211
|
+
}
|
|
212
|
+
`;
|
|
213
|
+
const UNIFORM_NAMES = [
|
|
214
|
+
'u_tex',
|
|
215
|
+
'u_rot',
|
|
216
|
+
'u_planeSize',
|
|
217
|
+
'u_perspective',
|
|
218
|
+
'u_translateX',
|
|
219
|
+
'u_translateZ',
|
|
220
|
+
'u_depthMin',
|
|
221
|
+
'u_depthMax',
|
|
222
|
+
'u_bokehEnabled',
|
|
223
|
+
'u_aperture',
|
|
224
|
+
'u_maxblur',
|
|
225
|
+
'u_focusDist',
|
|
226
|
+
'u_aspect',
|
|
227
|
+
'u_fog',
|
|
228
|
+
'u_grain',
|
|
229
|
+
'u_background',
|
|
230
|
+
'u_debug',
|
|
231
|
+
'u_time',
|
|
232
|
+
];
|
|
233
|
+
/**
|
|
234
|
+
* Build the CSS-order rotation matrix M = Rx * Ry * Rz(-z) in GL coords
|
|
235
|
+
* (y up, z toward viewer). Returns row-major 3x3 as number[][].
|
|
236
|
+
*/
|
|
237
|
+
function rotationMatrix(rxDeg, ryDeg, rzDeg) {
|
|
238
|
+
const rx = (rxDeg * Math.PI) / 180;
|
|
239
|
+
const ry = (ryDeg * Math.PI) / 180;
|
|
240
|
+
const rz = (-rzDeg * Math.PI) / 180;
|
|
241
|
+
const cx = Math.cos(rx);
|
|
242
|
+
const sx = Math.sin(rx);
|
|
243
|
+
const cy = Math.cos(ry);
|
|
244
|
+
const sy = Math.sin(ry);
|
|
245
|
+
const cz = Math.cos(rz);
|
|
246
|
+
const sz = Math.sin(rz);
|
|
247
|
+
const Rx = [
|
|
248
|
+
[1, 0, 0],
|
|
249
|
+
[0, cx, -sx],
|
|
250
|
+
[0, sx, cx],
|
|
251
|
+
];
|
|
252
|
+
const Ry = [
|
|
253
|
+
[cy, 0, sy],
|
|
254
|
+
[0, 1, 0],
|
|
255
|
+
[-sy, 0, cy],
|
|
256
|
+
];
|
|
257
|
+
const Rz = [
|
|
258
|
+
[cz, -sz, 0],
|
|
259
|
+
[sz, cz, 0],
|
|
260
|
+
[0, 0, 1],
|
|
261
|
+
];
|
|
262
|
+
const mul = (a, b) => a.map((row, i) => row.map((_, j) => a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]));
|
|
263
|
+
return mul(mul(Rx, Ry), Rz);
|
|
264
|
+
}
|
|
265
|
+
export function AngledScreen(props) {
|
|
266
|
+
const frame = useCurrentFrame();
|
|
267
|
+
const { width: compW, height: compH, fps } = useVideoConfig();
|
|
268
|
+
const glRef = useRef(null);
|
|
269
|
+
// Mirror canvas: the web-renderer's DOM composer (used for exports and SDK
|
|
270
|
+
// screenshots whenever nested HTML-in-canvas capture is unavailable)
|
|
271
|
+
// cannot read the HtmlInCanvas offscreen and paints the raw children
|
|
272
|
+
// instead. Every painted GL frame is blitted into this plain 2D canvas
|
|
273
|
+
// overlaid on top; the DOM composer draws it like any regular canvas.
|
|
274
|
+
// It is always mounted (not only during export) because sdk.screenshot()
|
|
275
|
+
// captures the live tree without ExportContext.
|
|
276
|
+
const mirrorRef = useRef(null);
|
|
277
|
+
const mirrorLoggedRef = useRef(false);
|
|
278
|
+
// Export sync: while web-renderer renders (isRendering), hold a scoped
|
|
279
|
+
// delayRender handle per frame and force a paint on the layout canvas.
|
|
280
|
+
// Released in onPaint after the GL frame is blitted to the mirror, so
|
|
281
|
+
// waitForReady() blocks until the shader output for THIS frame exists.
|
|
282
|
+
// Needed because remotion's own "waiting for first paint" delayRender
|
|
283
|
+
// registers on the window scope, which the export scaffold ignores.
|
|
284
|
+
// TODO: remove once fixed upstream:
|
|
285
|
+
// https://github.com/remotion-dev/remotion/issues/9367
|
|
286
|
+
const { isRendering } = useRemotionEnvironment();
|
|
287
|
+
const { delayRender, continueRender } = useDelayRender();
|
|
288
|
+
const continueRenderRef = useRef(continueRender);
|
|
289
|
+
continueRenderRef.current = continueRender;
|
|
290
|
+
const layoutCanvasRef = useRef(null);
|
|
291
|
+
const pendingPaintHandleRef = useRef(null);
|
|
292
|
+
const tp = useTweakpane('AngledScreen', {
|
|
293
|
+
perspective: { value: props.perspective ?? 1200, min: 100, max: 3000, step: 10 },
|
|
294
|
+
rotateX: { value: props.rotateX ?? 8, min: -90, max: 90, step: 0.5 },
|
|
295
|
+
rotateY: { value: props.rotateY ?? -12, min: -90, max: 90, step: 0.5 },
|
|
296
|
+
rotateZ: { value: props.rotateZ ?? 0, min: -180, max: 180, step: 0.5 },
|
|
297
|
+
translateX: { value: props.translateX ?? 0, min: -1000, max: 1000, step: 1 },
|
|
298
|
+
translateZ: { value: props.translateZ ?? 0, min: -500, max: 500, step: 1 },
|
|
299
|
+
bokeh: props.bokeh ?? true,
|
|
300
|
+
aperture: { value: props.aperture ?? 0.5, min: 0, max: 2, step: 0.01 },
|
|
301
|
+
maxBlur: { value: props.maxBlur ?? 0.12, min: 0, max: 0.3, step: 0.005 },
|
|
302
|
+
// 0 = auto: focus tracks the nearest visible point of the plane, so only
|
|
303
|
+
// the far side blurs, progressively. Any other value = fraction of
|
|
304
|
+
// perspective, like Three's focus uniform.
|
|
305
|
+
focus: {
|
|
306
|
+
value: props.focus ?? 0,
|
|
307
|
+
min: 0,
|
|
308
|
+
max: 2,
|
|
309
|
+
step: 0.01,
|
|
310
|
+
},
|
|
311
|
+
fog: { value: props.fog ?? 0.35, min: 0, max: 1, step: 0.01 },
|
|
312
|
+
grainIntensity: { value: props.grainIntensity ?? 0.02, min: 0, max: 0.2, step: 0.005 },
|
|
313
|
+
backgroundColor: props.backgroundColor ?? '#000000',
|
|
314
|
+
debug: props.debug ?? false,
|
|
315
|
+
});
|
|
316
|
+
const { perspective, rotateX, rotateY, rotateZ, translateX, translateZ } = tp;
|
|
317
|
+
const { aperture, maxBlur, focus, fog, grainIntensity } = tp;
|
|
318
|
+
const { bokeh, debug, backgroundColor } = tp;
|
|
319
|
+
const time = frame / fps;
|
|
320
|
+
const onInit = useCallback(({ canvas }) => {
|
|
321
|
+
const gl = canvas.getContext('webgl2', {
|
|
322
|
+
alpha: true,
|
|
323
|
+
premultipliedAlpha: true,
|
|
324
|
+
antialias: false,
|
|
325
|
+
// Required so the mirror canvas (export path) can drawImage() the GL
|
|
326
|
+
// buffer after the draw call instead of getting a cleared buffer.
|
|
327
|
+
preserveDrawingBuffer: true,
|
|
328
|
+
});
|
|
329
|
+
if (!gl) {
|
|
330
|
+
throw new Error('[AngledScreen] WebGL2 unavailable');
|
|
331
|
+
}
|
|
332
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
333
|
+
const vs = compileGlShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
334
|
+
const fs = compileGlShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
|
|
335
|
+
const program = linkGlProgram(gl, vs, fs);
|
|
336
|
+
gl.deleteShader(vs);
|
|
337
|
+
gl.deleteShader(fs);
|
|
338
|
+
const texture = gl.createTexture();
|
|
339
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
340
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
341
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
342
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
343
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
344
|
+
// Fullscreen quad: interleaved position + uv, 6 vertices
|
|
345
|
+
const buffer = gl.createBuffer();
|
|
346
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
347
|
+
// prettier-ignore
|
|
348
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
349
|
+
-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1,
|
|
350
|
+
-1, 1, 0, 1, 1, -1, 1, 0, 1, 1, 1, 1,
|
|
351
|
+
]), gl.STATIC_DRAW);
|
|
352
|
+
const vao = gl.createVertexArray();
|
|
353
|
+
gl.bindVertexArray(vao);
|
|
354
|
+
const posLoc = gl.getAttribLocation(program, 'a_position');
|
|
355
|
+
const uvLoc = gl.getAttribLocation(program, 'a_texCoord');
|
|
356
|
+
gl.enableVertexAttribArray(posLoc);
|
|
357
|
+
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 16, 0);
|
|
358
|
+
gl.enableVertexAttribArray(uvLoc);
|
|
359
|
+
gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 16, 8);
|
|
360
|
+
const locs = {};
|
|
361
|
+
for (const name of UNIFORM_NAMES) {
|
|
362
|
+
locs[name] = gl.getUniformLocation(program, name);
|
|
363
|
+
}
|
|
364
|
+
gl.useProgram(program);
|
|
365
|
+
gl.bindVertexArray(vao);
|
|
366
|
+
glRef.current = { gl, program, texture, locs, canvas };
|
|
367
|
+
return () => {
|
|
368
|
+
gl.deleteProgram(program);
|
|
369
|
+
gl.deleteTexture(texture);
|
|
370
|
+
gl.deleteVertexArray(vao);
|
|
371
|
+
gl.deleteBuffer(buffer);
|
|
372
|
+
glRef.current = null;
|
|
373
|
+
};
|
|
374
|
+
}, []);
|
|
375
|
+
const onPaint = useCallback(({ elementImage }) => {
|
|
376
|
+
const state = glRef.current;
|
|
377
|
+
if (!state)
|
|
378
|
+
return;
|
|
379
|
+
const { gl, locs } = state;
|
|
380
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
381
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
382
|
+
gl.bindTexture(gl.TEXTURE_2D, state.texture);
|
|
383
|
+
// The HTML-in-canvas API changed shape between Chromium builds:
|
|
384
|
+
// Chrome 149-151: texElementImage2D(target, level, internalformat, format, type, element)
|
|
385
|
+
// Chrome 152+: texElementImage2D(target, internalformat, element)
|
|
386
|
+
// where internalformat must be sized (RGBA8, SRGB8_ALPHA8, RGBA16F, RGBA32F)
|
|
387
|
+
// Detect via Function.length (3 = new signature).
|
|
388
|
+
if (gl.texElementImage2D.length <= 3) {
|
|
389
|
+
;
|
|
390
|
+
gl.texElementImage2D(gl.TEXTURE_2D, gl.RGBA8, elementImage);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, elementImage);
|
|
394
|
+
}
|
|
395
|
+
// CPU-side scene setup: rotation matrix + VISIBLE depth statistics.
|
|
396
|
+
// Depth is sampled on a 7x7 grid of rays across the screen and
|
|
397
|
+
// summarized with percentiles. Percentiles are area-weighted, which
|
|
398
|
+
// matters: rays toward the plane's vanishing direction produce huge t
|
|
399
|
+
// values, and using the raw corner min/max would compress the blur
|
|
400
|
+
// ramp so much that most of the image reads sharp. Quantiles keep the
|
|
401
|
+
// ramp perceptually spread across the screen.
|
|
402
|
+
const M = rotationMatrix(rotateX, rotateY, rotateZ);
|
|
403
|
+
const hw = compW / 2;
|
|
404
|
+
const hh = compH / 2;
|
|
405
|
+
// Plane normal (third column of M)
|
|
406
|
+
const nx = M[0][2];
|
|
407
|
+
const ny = M[1][2];
|
|
408
|
+
const nz = M[2][2];
|
|
409
|
+
const depths = [];
|
|
410
|
+
for (let gy = 0; gy < 7; gy++) {
|
|
411
|
+
for (let gx = 0; gx < 7; gx++) {
|
|
412
|
+
// Ray from camera C=(0,0,perspective) through screen point (sx,sy,0)
|
|
413
|
+
const sx = ((gx / 6) * 2 - 1) * hw;
|
|
414
|
+
const sy = ((gy / 6) * 2 - 1) * hh;
|
|
415
|
+
const len = Math.hypot(sx, sy, perspective);
|
|
416
|
+
const denom = (nx * sx + ny * sy - nz * perspective) / len;
|
|
417
|
+
if (Math.abs(denom) < 1e-6)
|
|
418
|
+
continue;
|
|
419
|
+
// t = dot(n, q0 - C) / dot(n, dir), q0 = (translateX,0,translateZ)
|
|
420
|
+
const t = (nx * translateX + nz * (translateZ - perspective)) / denom;
|
|
421
|
+
if (t > 0 && Number.isFinite(t))
|
|
422
|
+
depths.push(t);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
depths.sort((a, b) => a - b);
|
|
426
|
+
const quantile = (q) => depths[Math.min(depths.length - 1, Math.round(q * (depths.length - 1)))] ?? perspective;
|
|
427
|
+
const depthMin = depths.length > 0 ? depths[0] : perspective;
|
|
428
|
+
const depthMax = depths.length > 0 ? quantile(0.95) : perspective + 1;
|
|
429
|
+
if (locs.u_tex)
|
|
430
|
+
gl.uniform1i(locs.u_tex, 0);
|
|
431
|
+
if (locs.u_rot) {
|
|
432
|
+
// GLSL mat3 is column-major; M is row-major → transpose on flatten
|
|
433
|
+
// prettier-ignore
|
|
434
|
+
gl.uniformMatrix3fv(locs.u_rot, false, [
|
|
435
|
+
M[0][0], M[1][0], M[2][0],
|
|
436
|
+
M[0][1], M[1][1], M[2][1],
|
|
437
|
+
M[0][2], M[1][2], M[2][2],
|
|
438
|
+
]);
|
|
439
|
+
}
|
|
440
|
+
if (locs.u_planeSize)
|
|
441
|
+
gl.uniform2f(locs.u_planeSize, compW, compH);
|
|
442
|
+
if (locs.u_perspective)
|
|
443
|
+
gl.uniform1f(locs.u_perspective, perspective);
|
|
444
|
+
if (locs.u_translateX)
|
|
445
|
+
gl.uniform1f(locs.u_translateX, translateX);
|
|
446
|
+
if (locs.u_translateZ)
|
|
447
|
+
gl.uniform1f(locs.u_translateZ, translateZ);
|
|
448
|
+
if (locs.u_depthMin)
|
|
449
|
+
gl.uniform1f(locs.u_depthMin, depthMin);
|
|
450
|
+
if (locs.u_depthMax)
|
|
451
|
+
gl.uniform1f(locs.u_depthMax, depthMax);
|
|
452
|
+
// focus=0 (auto): the focus plane sits at the 40th depth percentile,
|
|
453
|
+
// so roughly the near half of the image reads sharp and blur ramps
|
|
454
|
+
// progressively from there toward the far edge. The effective aperture
|
|
455
|
+
// is auto-scaled so blur reaches maxBlur at the 95th percentile
|
|
456
|
+
// regardless of scene scale (aperture prop acts as a multiplier
|
|
457
|
+
// relative to its 0.5 default: higher = saturates earlier).
|
|
458
|
+
const focusDist = focus > 0 ? focus * perspective : quantile(0.4);
|
|
459
|
+
const apertureEff = focus > 0
|
|
460
|
+
? aperture
|
|
461
|
+
: (aperture / 0.5) * ((maxBlur * perspective) / Math.max(depthMax - focusDist, 1));
|
|
462
|
+
if (locs.u_bokehEnabled)
|
|
463
|
+
gl.uniform1f(locs.u_bokehEnabled, bokeh ? 1 : 0);
|
|
464
|
+
if (locs.u_aperture)
|
|
465
|
+
gl.uniform1f(locs.u_aperture, apertureEff);
|
|
466
|
+
if (locs.u_maxblur)
|
|
467
|
+
gl.uniform1f(locs.u_maxblur, maxBlur);
|
|
468
|
+
if (locs.u_focusDist)
|
|
469
|
+
gl.uniform1f(locs.u_focusDist, focusDist);
|
|
470
|
+
if (locs.u_aspect)
|
|
471
|
+
gl.uniform1f(locs.u_aspect, compW / compH);
|
|
472
|
+
if (locs.u_fog)
|
|
473
|
+
gl.uniform1f(locs.u_fog, fog);
|
|
474
|
+
if (locs.u_grain)
|
|
475
|
+
gl.uniform1f(locs.u_grain, grainIntensity);
|
|
476
|
+
if (locs.u_background)
|
|
477
|
+
gl.uniform4fv(locs.u_background, parseHexColor(backgroundColor));
|
|
478
|
+
if (locs.u_debug)
|
|
479
|
+
gl.uniform1f(locs.u_debug, debug ? 1 : 0);
|
|
480
|
+
if (locs.u_time)
|
|
481
|
+
gl.uniform1f(locs.u_time, time);
|
|
482
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
483
|
+
// Export path: mirror the GL frame into the overlay 2D canvas so the
|
|
484
|
+
// DOM composer (used when nested HTML-in-canvas capture is
|
|
485
|
+
// unavailable) picks up the shader output instead of the raw children.
|
|
486
|
+
const mirror = mirrorRef.current;
|
|
487
|
+
if (mirror) {
|
|
488
|
+
if (mirror.width !== state.canvas.width || mirror.height !== state.canvas.height) {
|
|
489
|
+
mirror.width = state.canvas.width;
|
|
490
|
+
mirror.height = state.canvas.height;
|
|
491
|
+
}
|
|
492
|
+
const ctx2d = mirror.getContext('2d');
|
|
493
|
+
if (ctx2d) {
|
|
494
|
+
ctx2d.drawImage(state.canvas, 0, 0);
|
|
495
|
+
if (!mirrorLoggedRef.current) {
|
|
496
|
+
mirrorLoggedRef.current = true;
|
|
497
|
+
console.info('[egaki] AngledScreen: mirror canvas active — GL frames are blitted to a 2D canvas so DOM-composer captures (export/screenshot) include the shader output.');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// Release the export-sync handle now that this frame's shader output
|
|
502
|
+
// (including the mirror blit) is complete.
|
|
503
|
+
const pending = pendingPaintHandleRef.current;
|
|
504
|
+
if (pending !== null) {
|
|
505
|
+
pendingPaintHandleRef.current = null;
|
|
506
|
+
continueRenderRef.current(pending);
|
|
507
|
+
}
|
|
508
|
+
}, [
|
|
509
|
+
compW, compH, perspective, rotateX, rotateY, rotateZ, translateX, translateZ,
|
|
510
|
+
bokeh, aperture, maxBlur, focus, fog, grainIntensity,
|
|
511
|
+
backgroundColor, debug, time,
|
|
512
|
+
]);
|
|
513
|
+
const supported = typeof document !== 'undefined' && HtmlInCanvas.isSupported();
|
|
514
|
+
// Per-frame export sync (see comment on pendingPaintHandleRef). Layout
|
|
515
|
+
// effect so the handle is registered synchronously inside the renderer's
|
|
516
|
+
// flushSync time update, before waitForReady() first checks the scope.
|
|
517
|
+
// Gated on `supported`: the CSS BasicAngledScreen fallback never paints,
|
|
518
|
+
// so registering a handle there would hang the export until timeout.
|
|
519
|
+
// Also gated on computed visibility: instances under visibility:hidden
|
|
520
|
+
// ancestors (LayoutTransition ghosts and inactive timed instances) get no
|
|
521
|
+
// Chromium paint records, so their HtmlInCanvas never fires paint and a
|
|
522
|
+
// registered handle would hang the export. Premounted sections are fine —
|
|
523
|
+
// remotion premount hides via opacity:0, which keeps paint records.
|
|
524
|
+
useLayoutEffect(() => {
|
|
525
|
+
if (!isRendering || !supported)
|
|
526
|
+
return;
|
|
527
|
+
const canvas = layoutCanvasRef.current;
|
|
528
|
+
if (!canvas || getComputedStyle(canvas).visibility === 'hidden')
|
|
529
|
+
return;
|
|
530
|
+
const handle = delayRender('AngledScreen: waiting for shader paint');
|
|
531
|
+
pendingPaintHandleRef.current = handle;
|
|
532
|
+
layoutCanvasRef.current?.requestPaint?.();
|
|
533
|
+
return () => {
|
|
534
|
+
if (pendingPaintHandleRef.current === handle) {
|
|
535
|
+
pendingPaintHandleRef.current = null;
|
|
536
|
+
}
|
|
537
|
+
continueRenderRef.current(handle);
|
|
538
|
+
};
|
|
539
|
+
}, [frame, isRendering, supported, delayRender]);
|
|
540
|
+
if (!supported) {
|
|
541
|
+
if (typeof console !== 'undefined' && !globalThis.__egakiAngledScreenFallbackWarned) {
|
|
542
|
+
;
|
|
543
|
+
globalThis.__egakiAngledScreenFallbackWarned = true;
|
|
544
|
+
console.warn('[egaki] AngledScreen: HTML-in-canvas is not supported in this browser — ' +
|
|
545
|
+
'falling back to the CSS BasicAngledScreen (no true depth-of-field). ' +
|
|
546
|
+
'Use Chrome 149+ and enable chrome://flags/#canvas-draw-element for the WebGL version.');
|
|
547
|
+
}
|
|
548
|
+
return (_jsx(BasicAngledScreen, { perspective: perspective, rotateX: rotateX, rotateY: rotateY, rotateZ: rotateZ, translateZ: translateZ, bokeh: bokeh, bokehBlur: maxBlur * compW * 0.4, bokehOffset: 0.5, backgroundColor: backgroundColor, width: props.width, height: props.height, debug: debug, style: {
|
|
549
|
+
// BasicAngledScreen has no translateX — approximate with a CSS shift
|
|
550
|
+
transform: translateX !== 0 ? `translateX(${translateX}px)` : undefined,
|
|
551
|
+
...props.style,
|
|
552
|
+
}, children: props.children }));
|
|
553
|
+
}
|
|
554
|
+
return (_jsxs("div", { style: { position: 'relative', width: '100%', height: '100%', overflow: 'hidden', ...props.style }, children: [_jsx(HtmlInCanvas, { ref: layoutCanvasRef, width: compW, height: compH, pixelDensity: Math.min(typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1, 2), onInit: onInit, onPaint: onPaint, children: _jsx("div", { style: {
|
|
555
|
+
position: 'absolute',
|
|
556
|
+
inset: 0,
|
|
557
|
+
display: 'flex',
|
|
558
|
+
alignItems: 'center',
|
|
559
|
+
justifyContent: 'center',
|
|
560
|
+
}, children: _jsx("div", { style: {
|
|
561
|
+
width: typeof props.width === 'number' ? `${props.width}px` : (props.width ?? '80%'),
|
|
562
|
+
height: typeof props.height === 'number' ? `${props.height}px` : (props.height ?? 'auto'),
|
|
563
|
+
position: 'relative',
|
|
564
|
+
borderRadius: '12px',
|
|
565
|
+
overflow: 'hidden',
|
|
566
|
+
}, children: props.children }) }) }), _jsx("canvas", { ref: mirrorRef, style: {
|
|
567
|
+
position: 'absolute',
|
|
568
|
+
inset: 0,
|
|
569
|
+
width: '100%',
|
|
570
|
+
height: '100%',
|
|
571
|
+
pointerEvents: 'none',
|
|
572
|
+
} })] }));
|
|
573
|
+
}
|