@wave3d/core 0.1.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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/_virtual/_rolldown/runtime.js +13 -0
  4. package/dist/config/model.d.ts +240 -0
  5. package/dist/config/model.js +496 -0
  6. package/dist/config/model.js.map +1 -0
  7. package/dist/core-loader.d.ts +10 -0
  8. package/dist/core-loader.js +12 -0
  9. package/dist/core-loader.js.map +1 -0
  10. package/dist/index.d.ts +4 -0
  11. package/dist/index.js +3 -0
  12. package/dist/presets.d.ts +8 -0
  13. package/dist/presets.js +544 -0
  14. package/dist/presets.js.map +1 -0
  15. package/dist/renderer/WaveGeometry.d.ts +31 -0
  16. package/dist/renderer/WaveGeometry.js +92 -0
  17. package/dist/renderer/WaveGeometry.js.map +1 -0
  18. package/dist/renderer/WaveRenderer.d.ts +232 -0
  19. package/dist/renderer/WaveRenderer.js +1118 -0
  20. package/dist/renderer/WaveRenderer.js.map +1 -0
  21. package/dist/renderer/heroPalette.d.ts +10 -0
  22. package/dist/renderer/heroPalette.js +34 -0
  23. package/dist/renderer/heroPalette.js.map +1 -0
  24. package/dist/renderer/index.d.ts +4 -0
  25. package/dist/renderer/index.js +4 -0
  26. package/dist/renderer/palette.d.ts +67 -0
  27. package/dist/renderer/palette.js +535 -0
  28. package/dist/renderer/palette.js.map +1 -0
  29. package/dist/renderer/shaders.js +545 -0
  30. package/dist/renderer/shaders.js.map +1 -0
  31. package/dist/shell/createWave.d.ts +58 -0
  32. package/dist/shell/createWave.js +161 -0
  33. package/dist/shell/createWave.js.map +1 -0
  34. package/dist/shell/poster.js +64 -0
  35. package/dist/shell/poster.js.map +1 -0
  36. package/dist/shell/probe.js +34 -0
  37. package/dist/shell/probe.js.map +1 -0
  38. package/dist/standalone/wave3d.standalone.js +13507 -0
  39. package/dist/standalone.d.ts +13 -0
  40. package/dist/standalone.js +17 -0
  41. package/dist/standalone.js.map +1 -0
  42. package/dist/studio/StudioWaveRenderer.d.ts +187 -0
  43. package/dist/studio/StudioWaveRenderer.js +905 -0
  44. package/dist/studio/StudioWaveRenderer.js.map +1 -0
  45. package/dist/studio/index.d.ts +3 -0
  46. package/dist/studio/index.js +3 -0
  47. package/dist/studio/randomize.d.ts +28 -0
  48. package/dist/studio/randomize.js +264 -0
  49. package/dist/studio/randomize.js.map +1 -0
  50. package/dist/util/base64.js +14 -0
  51. package/dist/util/base64.js.map +1 -0
  52. package/dist/util/math.js +18 -0
  53. package/dist/util/math.js.map +1 -0
  54. package/package.json +76 -0
  55. package/skills/wave3d/SKILL.md +158 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WaveGeometry.js","names":[],"sources":["../../src/renderer/WaveGeometry.ts"],"sourcesContent":["import * as THREE from \"three\";\n\n/** Native plane size for folded() — keep this exact (400) so the vertex\n * shader's displace/twist frequencies (calibrated to this scale) stay faithful. */\nconst NATIVE = 400;\nconst FOLD_X = 16; // |x| < 16 is the semicircular hinge; outside it the two flat arms\nconst SHIFT = NATIVE / 4; // recentre the folded cross-section along x\n\nconst X_AXIS = new THREE.Vector3(1, 0, 0);\nconst Y_AXIS = new THREE.Vector3(0, 1, 0);\n\n/**\n * Base wave geometry — `folded()`: a flat PlaneGeometry folded into a hairpin\n * (sideways-U) cross-section, then stood up so the fold runs along the wave's length.\n *\n * - Each vertex gets a half-thickness `r` (per-vertex math below): tight along the\n * width centreline, flaring toward the long edges.\n * - The strip |x| < FOLD_X becomes a semicircular hinge; the plane's two halves bend\n * around it into parallel arms offset to +r and -r.\n * - Two −90° rotations (about X then Y) orient the U upright and down its length.\n *\n * folded() leaves the U open along one side and hollow at both ends, so at oblique\n * camera angles you could see straight through it. We weld the open side and cap both\n * ends with extra triangles so the mesh is a watertight solid — welding/capping adds\n * faces only, no vertex positions move.\n *\n * All further deformation (displacement, twist, transform) happens in the vertex shader\n * on top of this base. UVs: u along the fold/length, v across the width.\n */\nexport class WaveGeometry {\n readonly geometry: THREE.BufferGeometry;\n private segments = -1;\n\n constructor(segments: number) {\n this.geometry = new THREE.BufferGeometry();\n this.resize(segments);\n }\n\n resize(segments: number): void {\n if (segments === this.segments) return;\n this.segments = segments;\n\n // subX along the fold, subY across the width (twice as dense).\n const subX = THREE.MathUtils.clamp(Math.round(segments), 48, 200);\n const subY = subX * 2;\n\n const plane = new THREE.PlaneGeometry(NATIVE, NATIVE, subX, subY);\n const pos = plane.attributes.position as THREE.BufferAttribute;\n const uv = plane.attributes.uv as THREE.BufferAttribute;\n const v = new THREE.Vector3();\n\n for (let i = 0; i < pos.count; i++) {\n v.fromBufferAttribute(pos, i);\n const uy = uv.getY(i);\n // r: cross-section half-thickness — tight (2) along the width centreline, flaring (4)\n // toward the long edges. The pow() term is a sharp parabolic bump peaking at uv.y = 0.5.\n const r = 4 - 2 * Math.pow(4 * uy * (1 - uy), 9.5);\n\n if (v.x < -FOLD_X) {\n v.z += r; // long arm, at +r\n } else if (v.x < FOLD_X) {\n // semicircular hinge: z sweeps +r → -r, x collapses to the bend\n v.z = Math.cos(THREE.MathUtils.mapLinear(v.x, -FOLD_X, FOLD_X, 0, Math.PI)) * r;\n v.x =\n Math.cos(THREE.MathUtils.mapLinear(v.x, -FOLD_X, FOLD_X, -Math.PI / 2, Math.PI / 2)) * r -\n FOLD_X;\n } else {\n v.z -= r; // folded-over arm, mirrored back at -r\n v.x = -v.x;\n }\n\n v.x += SHIFT;\n v.applyAxisAngle(X_AXIS, -Math.PI / 2);\n v.applyAxisAngle(Y_AXIS, -Math.PI / 2);\n pos.setXYZ(i, v.x, v.y, v.z);\n }\n pos.needsUpdate = true;\n\n // Seal the hairpin's OPEN side. folded() leaves the two arm tips unconnected — the\n // plane's u=0 and u=subX edges, which fold to adjacent tips at +r and -r — so at oblique\n // camera angles you can see through the U to the background. Weld those two edges with a\n // strip of triangles, closing the tube. No vertex positions move; this only adds faces\n // over the previously-open seam.\n const cols = subX + 1;\n const srcIdx = plane.getIndex();\n const merged = srcIdx ? Array.from(srcIdx.array as ArrayLike<number>) : [];\n // (a) Weld the U's side opening: the u=0 and u=subX edges fold to adjacent tips at ±r.\n for (let iy = 0; iy < subY; iy++) {\n const a = iy * cols; // (row iy, col 0) — arm-A tip\n const b = (iy + 1) * cols; // (row iy+1, col 0)\n const c = a + subX; // (row iy, col subX) — arm-B tip\n const d = b + subX; // (row iy+1, col subX)\n merged.push(a, c, b, b, c, d);\n }\n // (b) Cap the two length-ends (v=0 and v=subX rows): the folded sheet is a hollow channel\n // open at both ends, so an edge-on camera sees straight through it. Fan-triangulate each\n // end's U cross-section (apex = the col-0 tip) to close it — making the wave a closed solid.\n for (const row of [0, subY]) {\n const apex = row * cols;\n for (let ix = 1; ix < subX; ix++) merged.push(apex, row * cols + ix, row * cols + ix + 1);\n }\n plane.setIndex(merged);\n\n plane.computeVertexNormals();\n\n // Move the baked attributes onto our reusable geometry, then drop the temp.\n this.geometry.setIndex(plane.getIndex());\n this.geometry.setAttribute(\"position\", plane.getAttribute(\"position\"));\n this.geometry.setAttribute(\"uv\", plane.getAttribute(\"uv\"));\n this.geometry.setAttribute(\"normal\", plane.getAttribute(\"normal\"));\n this.geometry.computeBoundingSphere();\n plane.dispose();\n }\n\n dispose(): void {\n this.geometry.dispose();\n }\n}\n"],"mappings":";;;;AAIA,MAAM,SAAS;AACf,MAAM,SAAS;AACf,MAAM,QAAQ,SAAS;AAEvB,MAAM,SAAS,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;AACxC,MAAM,SAAS,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;;;;;;;;;;;;;;;;;;;AAoBxC,IAAa,eAAb,MAA0B;CACxB;CACA,WAAmB;CAEnB,YAAY,UAAkB;EAC5B,KAAK,WAAW,IAAI,MAAM,eAAe;EACzC,KAAK,OAAO,QAAQ;CACtB;CAEA,OAAO,UAAwB;EAC7B,IAAI,aAAa,KAAK,UAAU;EAChC,KAAK,WAAW;EAGhB,MAAM,OAAO,MAAM,UAAU,MAAM,KAAK,MAAM,QAAQ,GAAG,IAAI,GAAG;EAChE,MAAM,OAAO,OAAO;EAEpB,MAAM,QAAQ,IAAI,MAAM,cAAc,QAAQ,QAAQ,MAAM,IAAI;EAChE,MAAM,MAAM,MAAM,WAAW;EAC7B,MAAM,KAAK,MAAM,WAAW;EAC5B,MAAM,IAAI,IAAI,MAAM,QAAQ;EAE5B,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,OAAO,KAAK;GAClC,EAAE,oBAAoB,KAAK,CAAC;GAC5B,MAAM,KAAK,GAAG,KAAK,CAAC;GAGpB,MAAM,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,GAAG;GAEjD,IAAI,EAAE,IAAI,KACR,EAAE,KAAK;QACF,IAAI,EAAE,IAAI,QAAQ;IAEvB,EAAE,IAAI,KAAK,IAAI,MAAM,UAAU,UAAU,EAAE,GAAG,KAAS,QAAQ,GAAG,KAAK,EAAE,CAAC,IAAI;IAC9E,EAAE,IACA,KAAK,IAAI,MAAM,UAAU,UAAU,EAAE,GAAG,KAAS,QAAQ,CAAC,KAAK,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,IAAI,IACvF;GACJ,OAAO;IACL,EAAE,KAAK;IACP,EAAE,IAAI,CAAC,EAAE;GACX;GAEA,EAAE,KAAK;GACP,EAAE,eAAe,QAAQ,CAAC,KAAK,KAAK,CAAC;GACrC,EAAE,eAAe,QAAQ,CAAC,KAAK,KAAK,CAAC;GACrC,IAAI,OAAO,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;EAC7B;EACA,IAAI,cAAc;EAOlB,MAAM,OAAO,OAAO;EACpB,MAAM,SAAS,MAAM,SAAS;EAC9B,MAAM,SAAS,SAAS,MAAM,KAAK,OAAO,KAA0B,IAAI,CAAC;EAEzE,KAAK,IAAI,KAAK,GAAG,KAAK,MAAM,MAAM;GAChC,MAAM,IAAI,KAAK;GACf,MAAM,KAAK,KAAK,KAAK;GACrB,MAAM,IAAI,IAAI;GACd,MAAM,IAAI,IAAI;GACd,OAAO,KAAK,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;EAC9B;EAIA,KAAK,MAAM,OAAO,CAAC,GAAG,IAAI,GAAG;GAC3B,MAAM,OAAO,MAAM;GACnB,KAAK,IAAI,KAAK,GAAG,KAAK,MAAM,MAAM,OAAO,KAAK,MAAM,MAAM,OAAO,IAAI,MAAM,OAAO,KAAK,CAAC;EAC1F;EACA,MAAM,SAAS,MAAM;EAErB,MAAM,qBAAqB;EAG3B,KAAK,SAAS,SAAS,MAAM,SAAS,CAAC;EACvC,KAAK,SAAS,aAAa,YAAY,MAAM,aAAa,UAAU,CAAC;EACrE,KAAK,SAAS,aAAa,MAAM,MAAM,aAAa,IAAI,CAAC;EACzD,KAAK,SAAS,aAAa,UAAU,MAAM,aAAa,QAAQ,CAAC;EACjE,KAAK,SAAS,sBAAsB;EACpC,MAAM,QAAQ;CAChB;CAEA,UAAgB;EACd,KAAK,SAAS,QAAQ;CACxB;AACF"}
@@ -0,0 +1,232 @@
1
+ import { StudioConfig, WaveConfig } from "../config/model.js";
2
+ import { WaveGeometry } from "./WaveGeometry.js";
3
+ import * as THREE from "three";
4
+
5
+ //#region src/renderer/WaveRenderer.d.ts
6
+ /** Reference frame (world units) the orthographic camera fills at cameraZoom 1. The wave is
7
+ * framed by COVERING this FRAME_W × FRAME_H rectangle (centred on cameraTarget) into the canvas
8
+ * — scaled to fill both dimensions, cropping the aspect overflow — so a given cameraZoom /
9
+ * cameraTarget frames the wave the SAME at any canvas size or aspect (only the cropped margin
10
+ * differs). FRAME_H = FRAME_W / (16/9) makes the reference a 16:9 rectangle; for canvases wider
11
+ * than that the width binds, narrower ones zoom in to fill instead of
12
+ * showing empty bands. This is what makes a saved preset reproduce on anyone's screen. */
13
+ declare const FRAME_W = 1333;
14
+ declare const FRAME_H = 750;
15
+ interface WaveRendererOptions {
16
+ /** Honor prefers-reduced-motion by freezing animation. Default true. */
17
+ respectReducedMotion?: boolean;
18
+ /**
19
+ * Skip the intro time-ramp (the ~1s ease-in of animation on load), rendering at full speed
20
+ * immediately. The studio passes `import.meta.env.DEV` here so a fresh renderer on every HMR
21
+ * hot-swap doesn't replay the ease-in (which reads as a "speed up" when you tab back). Default
22
+ * false — production embeds keep the ease-in. Ignored while paused (a paused frame is always the
23
+ * full frame). Default false.
24
+ */
25
+ skipIntroRamp?: boolean;
26
+ }
27
+ /**
28
+ * Per-wave 2D palette texture (+ optional looping video). One instance per wave, so each
29
+ * wave carries its own palette. Guarded by a signature so it only rebuilds when that wave's
30
+ * palette actually changes (not every refresh).
31
+ */
32
+ declare class WavePalette {
33
+ private readonly makeVideo;
34
+ private readonly onVideoReady;
35
+ texture?: THREE.Texture;
36
+ private sig;
37
+ private video?;
38
+ private videoUrl;
39
+ private failedUrl;
40
+ constructor(makeVideo: (url: string) => HTMLVideoElement, onVideoReady: () => void);
41
+ /** Rebuild this wave's palette texture from its config (video / custom image / stops /
42
+ * built-in map / hero LUT) and point the wave's palette uniforms at it. */
43
+ apply(cfg: WaveConfig, uniforms: Record<string, THREE.IUniform>): void;
44
+ /** Play/pause this wave's palette video with the render loop. */
45
+ syncPlayback(cfg: WaveConfig, running: boolean): void;
46
+ private ensureVideo;
47
+ private clearVideo;
48
+ dispose(): void;
49
+ }
50
+ type Wave = {
51
+ mesh: THREE.Mesh;
52
+ material: THREE.ShaderMaterial;
53
+ geometry: WaveGeometry; /** This wave's own 2D palette texture + optional video. */
54
+ palette: WavePalette;
55
+ };
56
+ /** Convert an sRGB hex string to a linear-space RGB vector (three's ColorManagement does the
57
+ * sRGB→linear conversion on parse). Exported for the studio subclass's live light-uniform push. */
58
+ declare function hexToLinearVec3(hex: string, target: THREE.Vector3): THREE.Vector3;
59
+ /**
60
+ * Renders a gradient "wave of light" from a {@link StudioConfig}. Framework-agnostic:
61
+ * it needs only a DOM container and a config. The studio mutates the config in
62
+ * place and calls `refresh()` / `rebuild()`.
63
+ */
64
+ declare class WaveRenderer {
65
+ readonly renderer: THREE.WebGLRenderer;
66
+ protected readonly scene: THREE.Scene<THREE.Object3DEventMap>;
67
+ protected readonly camera: THREE.OrthographicCamera;
68
+ protected readonly group: THREE.Group<THREE.Object3DEventMap>;
69
+ private readonly composer;
70
+ private readonly postPass;
71
+ /** Optional bloom pass — created lazily when bloomStrength first goes >0, removed at 0. */
72
+ private bloomPass?;
73
+ protected readonly container: HTMLElement;
74
+ private readonly respectReducedMotion;
75
+ private readonly skipIntroRamp;
76
+ protected config: StudioConfig;
77
+ protected waves: Wave[];
78
+ private backgroundTexture?;
79
+ private backgroundSig;
80
+ private backgroundImage?;
81
+ private backgroundImageUrl;
82
+ private failedBackgroundImageUrl;
83
+ private backgroundVideo?;
84
+ private backgroundVideoUrl;
85
+ private failedBackgroundVideoUrl;
86
+ private backgroundVideoCanvas?;
87
+ /** Authored default camera pose, for "Reset camera". */
88
+ protected readonly homeCamPos: THREE.Vector3;
89
+ protected readonly homeCamTarget: THREE.Vector3;
90
+ private readonly clipBox;
91
+ private readonly clipSphere;
92
+ private readonly clipTmpA;
93
+ private readonly clipTmpB;
94
+ private readonly clock;
95
+ private time;
96
+ private rafId;
97
+ protected running: boolean;
98
+ private started;
99
+ private visible;
100
+ private pageVisible;
101
+ private reducedMotion;
102
+ /** Intro ramp: eases animation time 0→1 over ~1s on load (when config.introRamp). */
103
+ private introTimeRamp;
104
+ private readonly resizeObserver;
105
+ private readonly intersectionObserver;
106
+ private readonly motionQuery;
107
+ protected capturing: boolean;
108
+ /** Fixed backing-buffer dimensions used by the studio's visible export frame. Embeds leave
109
+ * this unset and continue to resize responsively with their container and device DPR. */
110
+ private outputSize?;
111
+ constructor(container: HTMLElement, config: StudioConfig, options?: WaveRendererOptions);
112
+ private get segments();
113
+ private makeUniforms;
114
+ /** Vertex-shader #defines for a wave: TWIST_MOTION (per-wave animated twist wobble) and
115
+ * LOOP_MOTION (scene-level seamless loop). Both select #ifdef-gated code paths; an empty
116
+ * object compiles the default (linear-time) program. */
117
+ private waveDefines;
118
+ private addWave;
119
+ /**
120
+ * Apply config.blendMode to a material. "squared" (the default) is the hero blend:
121
+ * CustomBlending with AddEquation, src = SrcColorFactor, dst = ZeroFactor, so the
122
+ * framebuffer result is fragColor² — the squaring deepens the colours into the vivid
123
+ * hero look (without it the wave reads pastel). "additive"/"normal"/"multiply" are
124
+ * authoring overrides. Multiply uses Three's premultiplied-alpha path; the custom
125
+ * fragment shaders premultiply their output when Three injects PREMULTIPLIED_ALPHA.
126
+ * Returns true if material state changed (caller flags needsUpdate).
127
+ */
128
+ private applyBlendMode;
129
+ private disposeWaves;
130
+ /**
131
+ * Reconcile the wave pool to `waveCount` WITHOUT tearing everything down:
132
+ * keep existing waves (so the compiled shader program is never deleted and
133
+ * re-compiled — that churn can crash some GPU drivers), add/remove only the
134
+ * delta, and resize each geometry to the current quality.
135
+ */
136
+ private buildWaves;
137
+ /** Re-read per-frame-independent values from the (mutated) config. */
138
+ refresh(): void;
139
+ /** Point every wave's palette sampler at its own WavePalette texture (each rebuilt only
140
+ * when that wave's palette actually changes — see WavePalette.apply). */
141
+ private updatePaletteTextures;
142
+ /** Dev: 0 = normal, 1 = visualise the crease value, 2 = visualise derivative normal. */
143
+ setDebug(v: number): void;
144
+ /** Rebuild geometry + waves (call when waveCount or quality changes). */
145
+ rebuild(): void;
146
+ private applyBackground;
147
+ private applyColorBackground;
148
+ private applyGradientBackground;
149
+ /** Image mode: a live video, a user-loaded image, or a built-in map. `matte` shows while an
150
+ * async source is still loading. */
151
+ private applyImageBackground;
152
+ private backgroundCanvasSize;
153
+ private loadBackgroundImage;
154
+ private ensureBackgroundVideo;
155
+ private clearBackgroundVideo;
156
+ private createLoopingVideo;
157
+ private syncVideoPlayback;
158
+ private updateBackgroundVideoFrame;
159
+ private applyPost;
160
+ /** Insert / tune / remove the bloom pass. strength 0 removes it from the composer entirely, so
161
+ * cost and pixels are identical to bloom-off; the pass (and its mip-chain render targets) is
162
+ * created lazily the first time bloom is enabled and disposed when turned back off. It sits
163
+ * right after the scene RenderPass so it blooms the wave before the grain/blur pass. */
164
+ private applyBloom;
165
+ private onResize;
166
+ private onContextLost;
167
+ private onContextRestored;
168
+ resize(): void;
169
+ /** Set an exact output buffer while CSS scales the canvas into the on-screen export frame. */
170
+ setOutputSize(width: number, height: number): void;
171
+ start(): void;
172
+ stop(): void;
173
+ private onMotionChange;
174
+ private onVisibilityChange;
175
+ private updateRunning;
176
+ private loop;
177
+ /** Advance the per-frame clock uniforms (geometry itself is static). Time model:
178
+ * time = elapsed·introTimeRamp + timeOffset — the ramp eases the animation in on load. */
179
+ private updateTime;
180
+ /** Render exactly one frame at the current time. */
181
+ renderOnce(): void;
182
+ /** Re-evaluate play/pause after `config.paused` changes. */
183
+ refreshPlayback(): void;
184
+ /** Jump the camera to the config's authored framing (cameraPosition / cameraTarget /
185
+ * cameraZoom). Called on whole-config swaps (preset / reset / randomize / import). The base
186
+ * applies the pose directly; the studio subclass overrides it to also drive the orbit target
187
+ * and sync the panel. Hook ⑤. */
188
+ protected applyCameraFromConfig(): void;
189
+ /** Hook ①: true while orbit or an edit gizmo owns the camera, so refresh() won't reset it. */
190
+ protected isCameraExternallyDriven(): boolean;
191
+ /** Hook ②: called at the end of refresh(), before the trailing renderOnce(). */
192
+ protected onAfterRefresh(): void;
193
+ /** Hook ③: called at the end of renderOnce(), after the composed frame is drawn. */
194
+ protected onAfterRenderFrame(): void;
195
+ /** Hook ④: called at the end of resize(), before the trailing renderOnce(). */
196
+ protected onAfterResize(): void;
197
+ /** Responsive ortho zoom: COVER the FRAME_W × FRAME_H reference frame onto the canvas so the
198
+ * wave frames the same at any size/aspect/dpr (only the cropped margin differs), times the
199
+ * user's cameraZoom. `max(...)` = cover (fill both axes, crop overflow); `min(...)` would be
200
+ * contain (fit with letterbox bands). Cover keeps the wave filling the frame on every screen. */
201
+ protected applyZoom(): void;
202
+ /** Fit the orthographic near/far planes to the scene before every render, so no part of a wave
203
+ * is ever clipped as the camera orbits / dollies / pans (or when waves are added or scaled).
204
+ *
205
+ * The camera is *constructed* with fixed 1..10000 planes that only suit the authored hero
206
+ * framing; once the view moves, the wave's depth extent along the view axis easily crosses
207
+ * them and the GPU hard-slices the geometry along a flat plane — chunks of the wave vanish.
208
+ *
209
+ * The in-shader twist rotates each vertex about its LOCAL origin, so it can never move a vertex
210
+ * further from that origin than the base geometry already sits. A sphere at each mesh's origin
211
+ * (radius = base extent × the mesh's largest world scale, plus the Y displacement, ×1.2 slack)
212
+ * therefore safely contains the fully-deformed wave. We union those, then bracket the union
213
+ * along the view axis with a margin. Bracketing to the scene (rather than a fixed huge slab)
214
+ * keeps clip-space depth scene-normalised — so the wireframe theme's depth fade, which reads
215
+ * gl_Position.z, looks the same at any camera distance instead of washing out. Runs each frame;
216
+ * it's a handful of vector ops over 1–8 meshes with no allocation. */
217
+ private updateClipPlanes;
218
+ /** A world-space position delta that drops a duplicated wave into open frame space beside the
219
+ * one it was copied from — screen-left and a touch down, sized to the visible frame — instead
220
+ * of hidden exactly on top of it or (for the hero, which fills the right of the frame) pushed
221
+ * off-frame. Camera-relative: it's "left on screen" no matter how the view is rotated/zoomed,
222
+ * because it's built from the camera's right/up axes and the frame's visible world size. */
223
+ captureImage(mime: string, transparent?: boolean, quality?: number): Promise<Blob>;
224
+ captureStream(fps?: number): MediaStream;
225
+ get canvas(): HTMLCanvasElement;
226
+ getConfig(): StudioConfig;
227
+ setConfig(config: StudioConfig): void;
228
+ dispose(): void;
229
+ }
230
+ //#endregion
231
+ export { FRAME_H, FRAME_W, WaveRenderer, WaveRendererOptions, hexToLinearVec3 };
232
+ //# sourceMappingURL=WaveRenderer.d.ts.map