@wave3d/core 0.8.0 → 0.10.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 +3 -1
- package/dist/config/model.d.ts +48 -2
- package/dist/config/model.js +8 -1
- package/dist/config/model.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/renderer/WaveRenderer.d.ts +142 -10
- package/dist/renderer/WaveRenderer.js +209 -53
- package/dist/renderer/WaveRenderer.js.map +1 -1
- package/dist/renderer/WaveRendererGPU.js +291 -0
- package/dist/renderer/WaveRendererGPU.js.map +1 -0
- package/dist/renderer/gpu-loader.js +2 -0
- package/dist/renderer/index.d.ts +3 -2
- package/dist/renderer/interaction.d.ts +29 -0
- package/dist/renderer/interaction.js +77 -31
- package/dist/renderer/interaction.js.map +1 -1
- package/dist/renderer/interactionGates.js +59 -0
- package/dist/renderer/interactionGates.js.map +1 -0
- package/dist/renderer/particleField.d.ts +1 -59
- package/dist/renderer/particleField.js +49 -29
- package/dist/renderer/particleField.js.map +1 -1
- package/dist/renderer/particleFieldGPU.js +141 -0
- package/dist/renderer/particleFieldGPU.js.map +1 -0
- package/dist/renderer/tilt.d.ts +17 -0
- package/dist/renderer/tilt.js +124 -0
- package/dist/renderer/tilt.js.map +1 -0
- package/dist/renderer/tsl/color.js +129 -0
- package/dist/renderer/tsl/color.js.map +1 -0
- package/dist/renderer/tsl/noise.js +83 -0
- package/dist/renderer/tsl/noise.js.map +1 -0
- package/dist/renderer/tsl/packedArray.js +76 -0
- package/dist/renderer/tsl/packedArray.js.map +1 -0
- package/dist/renderer/tsl/particleMaterial.js +133 -0
- package/dist/renderer/tsl/particleMaterial.js.map +1 -0
- package/dist/renderer/tsl/particleUniforms.js +44 -0
- package/dist/renderer/tsl/particleUniforms.js.map +1 -0
- package/dist/renderer/tsl/pointerField.js +73 -0
- package/dist/renderer/tsl/pointerField.js.map +1 -0
- package/dist/renderer/tsl/post.js +63 -0
- package/dist/renderer/tsl/post.js.map +1 -0
- package/dist/renderer/tsl/postChain.js +56 -0
- package/dist/renderer/tsl/postChain.js.map +1 -0
- package/dist/renderer/tsl/postEffects.js +221 -0
- package/dist/renderer/tsl/postEffects.js.map +1 -0
- package/dist/renderer/tsl/types.js +28 -0
- package/dist/renderer/tsl/types.js.map +1 -0
- package/dist/renderer/tsl/uniforms.js +152 -0
- package/dist/renderer/tsl/uniforms.js.map +1 -0
- package/dist/renderer/tsl/waveMaterial.js +185 -0
- package/dist/renderer/tsl/waveMaterial.js.map +1 -0
- package/dist/renderer/tsl/waveShape.js +108 -0
- package/dist/renderer/tsl/waveShape.js.map +1 -0
- package/dist/shell/createWave.d.ts +29 -0
- package/dist/shell/createWave.js +39 -11
- package/dist/shell/createWave.js.map +1 -1
- package/dist/shell/probe.js +18 -1
- package/dist/shell/probe.js.map +1 -1
- package/dist/standalone/wave3d.standalone.js +1811 -1594
- package/dist/standalone/wave3d.standalone.webgpu.js +36647 -0
- package/dist/standalone.d.ts +10 -3
- package/dist/standalone.js +10 -3
- package/dist/standalone.js.map +1 -1
- package/dist/studio/StudioWaveRenderer.d.ts +4 -0
- package/dist/studio/StudioWaveRenderer.js +9 -0
- package/dist/studio/StudioWaveRenderer.js.map +1 -1
- package/dist/studio/StudioWaveRendererGPU.js +18 -0
- package/dist/studio/StudioWaveRendererGPU.js.map +1 -0
- package/dist/studio/index.d.ts +12 -2
- package/dist/studio/index.js +14 -1
- package/dist/studio/index.js.map +1 -0
- package/package.json +12 -3
- package/skills/wave3d/SKILL.md +29 -2
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { CameraFit, StudioConfig, WaveConfig } from "../config/model.js";
|
|
1
|
+
import { BlendMode, CameraFit, StudioConfig, WaveConfig } from "../config/model.js";
|
|
2
2
|
import { WaveGeometry } from "./WaveGeometry.js";
|
|
3
|
-
import {
|
|
3
|
+
import { ParticleFrame } from "./particleField.js";
|
|
4
|
+
import { TiltStatus } from "./tilt.js";
|
|
4
5
|
import { InteractionController } from "./interaction.js";
|
|
5
6
|
import * as THREE from "three";
|
|
7
|
+
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
|
|
8
|
+
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
|
|
6
9
|
//#region src/renderer/WaveRenderer.d.ts
|
|
7
10
|
/** Reference frame (world units) the orthographic camera fills at cameraZoom 1. The wave is
|
|
8
11
|
* framed by mapping this FRAME_W × FRAME_H rectangle (centred on cameraTarget) onto the canvas,
|
|
@@ -61,15 +64,50 @@ declare class WavePalette {
|
|
|
61
64
|
private clearVideo;
|
|
62
65
|
dispose(): void;
|
|
63
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The uniform surface both backends expose. The GLSL path holds `THREE.IUniform`s; the TSL path
|
|
69
|
+
* holds TSL uniform nodes and array wrappers. Both are written the same way — `u.uFoo.value = x`,
|
|
70
|
+
* `u.uColors.value[i].set(...)` — which is what lets the ~116 config-sync writes in `refresh()`
|
|
71
|
+
* stay backend-agnostic.
|
|
72
|
+
*/
|
|
73
|
+
type WaveUniforms = Record<string, {
|
|
74
|
+
value: unknown;
|
|
75
|
+
}>;
|
|
76
|
+
/** A wave material, whichever backend built it. */
|
|
77
|
+
type WaveMaterial = THREE.Material & {
|
|
78
|
+
uniforms: WaveUniforms;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* A wave's particle field, whichever backend built it.
|
|
82
|
+
*
|
|
83
|
+
* The TSL field is a `THREE.Sprite` where the GLSL one is a `THREE.Points` (WebGPU has no point
|
|
84
|
+
* size), so the scene node is reached through `object` rather than either concrete type.
|
|
85
|
+
*/
|
|
86
|
+
interface WaveParticleField {
|
|
87
|
+
readonly object: THREE.Object3D;
|
|
88
|
+
sync(cfg: NonNullable<WaveConfig["particles"]>, loopSeconds: number): void;
|
|
89
|
+
frame(f: ParticleFrame, pixelRatio: number): void;
|
|
90
|
+
configure(shape: {
|
|
91
|
+
defines?: Record<string, string>;
|
|
92
|
+
uniforms?: Record<string, {
|
|
93
|
+
value: unknown;
|
|
94
|
+
}>;
|
|
95
|
+
matrixWorld: THREE.Matrix4;
|
|
96
|
+
speed: number;
|
|
97
|
+
seed: number;
|
|
98
|
+
}): void;
|
|
99
|
+
setTime(t: number): void;
|
|
100
|
+
dispose(): void;
|
|
101
|
+
}
|
|
64
102
|
type Wave = {
|
|
65
103
|
mesh: THREE.Mesh;
|
|
66
|
-
material:
|
|
104
|
+
material: WaveMaterial;
|
|
67
105
|
geometry: WaveGeometry;
|
|
68
106
|
/** This wave's own 2D palette texture + optional video. */
|
|
69
107
|
palette: WavePalette;
|
|
70
108
|
/** This wave's own particle / dust field — created when its `particles.count` first goes >0,
|
|
71
109
|
* disposed at 0 / absent (the WavePalette lifecycle pattern). Undefined = no dust for this wave. */
|
|
72
|
-
particleField?:
|
|
110
|
+
particleField?: WaveParticleField;
|
|
73
111
|
};
|
|
74
112
|
/** Convert an sRGB hex string to a linear-space RGB vector (three's ColorManagement does the
|
|
75
113
|
* sRGB→linear conversion on parse). Exported for the studio subclass's live light-uniform push. */
|
|
@@ -81,11 +119,17 @@ declare function hexToLinearVec3(hex: string, target: THREE.Vector3): THREE.Vect
|
|
|
81
119
|
*/
|
|
82
120
|
declare class WaveRenderer {
|
|
83
121
|
readonly renderer: THREE.WebGLRenderer;
|
|
122
|
+
/**
|
|
123
|
+
* Whether this renderer can draw yet. The WebGL backend is ready the moment it is constructed;
|
|
124
|
+
* the WebGPU subclass has to `await renderer.init()` first, and `renderOnce()` throws before that.
|
|
125
|
+
* The base class is always ready, so the WebGL path is unaffected.
|
|
126
|
+
*/
|
|
127
|
+
protected ready: boolean;
|
|
84
128
|
protected readonly scene: THREE.Scene<THREE.Object3DEventMap>;
|
|
85
129
|
protected readonly camera: THREE.OrthographicCamera;
|
|
86
130
|
protected readonly group: THREE.Group<THREE.Object3DEventMap>;
|
|
87
|
-
|
|
88
|
-
|
|
131
|
+
protected composer: EffectComposer;
|
|
132
|
+
protected postPass: ShaderPass;
|
|
89
133
|
/** Optional bloom pass — created lazily when bloomStrength first goes >0, removed at 0. */
|
|
90
134
|
private bloomPass?;
|
|
91
135
|
private ditherPass?;
|
|
@@ -118,8 +162,17 @@ declare class WaveRenderer {
|
|
|
118
162
|
private readonly clipSphere;
|
|
119
163
|
private readonly clipTmpA;
|
|
120
164
|
private readonly clipTmpB;
|
|
121
|
-
/** Created by
|
|
165
|
+
/** Created by loadInteraction() once the runtime chunk lands, disposed when interaction turns off.
|
|
166
|
+
* Stays undefined for the frames between the config turning it on and the chunk arriving. */
|
|
122
167
|
protected interaction?: InteractionController;
|
|
168
|
+
/** The chunk itself, kept for the applier tables. Set and cleared with `interaction`. */
|
|
169
|
+
private interactionModule?;
|
|
170
|
+
/** A fetch already in flight, so a per-frame refresh can't start a second one. */
|
|
171
|
+
private interactionLoading;
|
|
172
|
+
/** setInteractionInput() calls made while the chunk was in flight, replayed once it lands. */
|
|
173
|
+
private stagedInputs?;
|
|
174
|
+
/** Set by dispose(), so an in-flight chunk can't attach listeners to a dead renderer. */
|
|
175
|
+
private disposed;
|
|
123
176
|
/** Extra ortho-zoom MULTIPLIER from a cameraZoom binding (1 = none); applied in applyZoom().
|
|
124
177
|
* Protected so the studio's writeCameraToConfig() can divide it back out (keep it out of config). */
|
|
125
178
|
protected interactionZoom: number;
|
|
@@ -150,6 +203,17 @@ declare class WaveRenderer {
|
|
|
150
203
|
* this unset and continue to resize responsively with their container and device DPR. */
|
|
151
204
|
private outputSize?;
|
|
152
205
|
constructor(container: HTMLElement, config: StudioConfig, options?: WaveRendererOptions);
|
|
206
|
+
/**
|
|
207
|
+
* Build the backing three renderer. Overridden by the WebGPU subclass; called from the
|
|
208
|
+
* constructor, so it must not depend on subclass FIELDS (prototype methods are available during
|
|
209
|
+
* `super()`, field initialisers are not).
|
|
210
|
+
*/
|
|
211
|
+
protected createRenderer(): THREE.WebGLRenderer;
|
|
212
|
+
/**
|
|
213
|
+
* Await any asynchronous backend startup. A no-op on WebGL; the WebGPU subclass overrides it to
|
|
214
|
+
* `await renderer.init()` and then draw the first frame. Safe to call more than once.
|
|
215
|
+
*/
|
|
216
|
+
init(): Promise<void>;
|
|
153
217
|
private get segments();
|
|
154
218
|
private makeUniforms;
|
|
155
219
|
/** Vertex-shader #defines for a wave: TWIST_MOTION (per-wave animated twist wobble) and
|
|
@@ -157,6 +221,19 @@ declare class WaveRenderer {
|
|
|
157
221
|
* object compiles the default (linear-time) program. */
|
|
158
222
|
private waveDefines;
|
|
159
223
|
private addWave;
|
|
224
|
+
/**
|
|
225
|
+
* Build the material for one wave. The GLSL backend compiles a ShaderMaterial with `#define`
|
|
226
|
+
* variants; the WebGPU subclass builds a TSL node graph instead. Both expose the same `uniforms`
|
|
227
|
+
* surface, so everything downstream in `refresh()` is shared.
|
|
228
|
+
*/
|
|
229
|
+
protected createWaveMaterial(sc: WaveConfig | undefined): WaveMaterial;
|
|
230
|
+
/**
|
|
231
|
+
* Re-select this wave's shader variant when its config changes shape (theme, twist motion, helix,
|
|
232
|
+
* …). Returns true if the material needs recompiling. The GLSL backend swaps `defines` and the
|
|
233
|
+
* fragment source; the WebGPU subclass rebuilds the node graph, since a TSL variant is a
|
|
234
|
+
* different graph rather than a different define set.
|
|
235
|
+
*/
|
|
236
|
+
protected applyWaveVariant(wave: Wave, sc: WaveConfig): boolean;
|
|
160
237
|
/**
|
|
161
238
|
* Apply config.blendMode to a material. "squared" (the default) is the hero blend:
|
|
162
239
|
* CustomBlending with AddEquation, src = SrcColorFactor, dst = ZeroFactor, so the
|
|
@@ -166,7 +243,7 @@ declare class WaveRenderer {
|
|
|
166
243
|
* fragment shaders premultiply their output when Three injects PREMULTIPLIED_ALPHA.
|
|
167
244
|
* Returns true if material state changed (caller flags needsUpdate).
|
|
168
245
|
*/
|
|
169
|
-
|
|
246
|
+
protected applyBlendMode(material: WaveMaterial, mode: BlendMode): boolean;
|
|
170
247
|
private disposeWaves;
|
|
171
248
|
/**
|
|
172
249
|
* Reconcile the wave pool to `waveCount` WITHOUT tearing everything down:
|
|
@@ -185,6 +262,22 @@ declare class WaveRenderer {
|
|
|
185
262
|
/** Rebuild geometry + waves (call when waveCount or quality changes). */
|
|
186
263
|
rebuild(): void;
|
|
187
264
|
private applyBackground;
|
|
265
|
+
/**
|
|
266
|
+
* Build a wave's particle field. The GLSL backend returns a `THREE.Points` field; the TSL backend
|
|
267
|
+
* returns an instanced-sprite one wired to this wave's own uniform registry.
|
|
268
|
+
*/
|
|
269
|
+
protected createParticleField(_wave: Wave, _sc: WaveConfig, onReady: () => void): WaveParticleField;
|
|
270
|
+
/**
|
|
271
|
+
* Largest texture edge the backend will accept, used to cap the background canvas. WebGL reports
|
|
272
|
+
* it on `capabilities`; WebGPU has no such object, so the subclass reads the device limit.
|
|
273
|
+
*/
|
|
274
|
+
protected maxTextureSize(): number;
|
|
275
|
+
/**
|
|
276
|
+
* Called whenever the scene background changes. A no-op on WebGL, where `EffectComposer`'s
|
|
277
|
+
* RenderPass reads the scene afresh each frame; the WebGPU subclass rebuilds its post chain,
|
|
278
|
+
* because a `pass()` node captures the background state at the point its render context is built.
|
|
279
|
+
*/
|
|
280
|
+
protected onBackgroundChanged(): void;
|
|
188
281
|
private applyColorBackground;
|
|
189
282
|
private applyGradientBackground;
|
|
190
283
|
/** Image mode: a live video, a user-loaded image, or a built-in map. `matte` shows while an
|
|
@@ -265,6 +358,12 @@ declare class WaveRenderer {
|
|
|
265
358
|
/** Advance the per-frame time uniforms (geometry itself is static). Time model:
|
|
266
359
|
* time = elapsed·introTimeRamp + timeOffset — the ramp eases the animation in on load. */
|
|
267
360
|
private updateTime;
|
|
361
|
+
/** Draw the composed frame. WebGL runs the EffectComposer; WebGPU runs a node post chain. */
|
|
362
|
+
protected renderComposed(): void;
|
|
363
|
+
/** Resize the post chain's render targets. */
|
|
364
|
+
protected resizePost(w: number, h: number, dpr: number): void;
|
|
365
|
+
/** Release the post chain's GPU resources. */
|
|
366
|
+
protected disposePost(): void;
|
|
268
367
|
/** Render exactly one frame at the current time. */
|
|
269
368
|
renderOnce(): void;
|
|
270
369
|
/** Insert / sync / remove EACH wave's particle field — mirrors applyBloom's lazy lifecycle, per wave.
|
|
@@ -285,6 +384,20 @@ declare class WaveRenderer {
|
|
|
285
384
|
/** Create/dispose the interaction controller as config toggles interaction on/off. Called from
|
|
286
385
|
* refresh(); the compiled define set (POINTER_FX etc.) is handled separately by waveDefines(). */
|
|
287
386
|
private syncInteraction;
|
|
387
|
+
/**
|
|
388
|
+
* Fetch the interactivity runtime and attach it. Deliberately a DYNAMIC import: the controller,
|
|
389
|
+
* its listeners, the applier tables and the tilt sensor are ~3.8 KB gzipped that a scene with no
|
|
390
|
+
* `interaction` block never runs, and a static import would put all of it in every bundle. The
|
|
391
|
+
* cost is that interaction goes live a chunk-fetch after the first frame instead of on it —
|
|
392
|
+
* invisible in practice, since there is nothing to react to until a reader moves.
|
|
393
|
+
*
|
|
394
|
+
* A failed fetch is not fatal: the wave renders exactly as an inert one does, which is the same
|
|
395
|
+
* thing that happens on a config with no interaction at all.
|
|
396
|
+
*/
|
|
397
|
+
private loadInteraction;
|
|
398
|
+
/** Hook for subclasses that hold state the controller must be told about once it exists (the
|
|
399
|
+
* studio's scroll preview). No-op in the base renderer. */
|
|
400
|
+
protected onInteractionReady(): void;
|
|
288
401
|
/** Per-frame interaction write: dynamic pointer-field uniforms + bindings. No-op without a
|
|
289
402
|
* controller. While capturing it writes the REST state instead (pointer field zeroed, every bound
|
|
290
403
|
* param at its authored base) — merely skipping the write would freeze whatever live hover/scroll
|
|
@@ -304,8 +417,27 @@ declare class WaveRenderer {
|
|
|
304
417
|
/** Evaluate bindings via the applier tables: value = mix(from ?? base, to, smoothedSource). Scene
|
|
305
418
|
* bindings drive scene params; each wave's bindings drive that wave's uniforms. */
|
|
306
419
|
private applyBindings;
|
|
307
|
-
/** Feed a `custom:<name>` interaction input (developer API). No-op when interaction is off.
|
|
420
|
+
/** Feed a `custom:<name>` interaction input (developer API). No-op when interaction is off.
|
|
421
|
+
* Values fed before the interaction chunk lands are STAGED (last one per name wins) and replayed
|
|
422
|
+
* when it does — otherwise a one-shot input sent right after `onReady` would vanish into the
|
|
423
|
+
* fetch window. */
|
|
308
424
|
setInteractionInput(name: string, value: number): void;
|
|
425
|
+
/**
|
|
426
|
+
* Explicitly ask for the device-orientation sensor. OPTIONAL, and on iOS it opens a modal
|
|
427
|
+
* permission dialog — so this belongs to a page where tilt is the point, not to a decorative
|
|
428
|
+
* background, which should simply go without tilt there. Nothing calls this for you.
|
|
429
|
+
*
|
|
430
|
+
* CALL IT FROM A USER GESTURE: iOS 13+ only grants the sensor from inside a tap handler, and
|
|
431
|
+
* awaiting anything before it (a fetch, a timeout) spends the gesture. Resolves true once
|
|
432
|
+
* readings can flow — false when
|
|
433
|
+
* the platform has no sensor, the scene declares no `interaction.tilt`, or the reader refused.
|
|
434
|
+
* Where no permission is required the sensor is already live and this resolves true.
|
|
435
|
+
*/
|
|
436
|
+
enableTilt(): Promise<boolean>;
|
|
437
|
+
/** Where the tilt sensor stands. `"prompt"` is exactly when a tap-to-enable affordance helps. */
|
|
438
|
+
tiltStatus(): TiltStatus;
|
|
439
|
+
/** Take the next orientation reading as the neutral pose — for when the reader has changed grip. */
|
|
440
|
+
recenterTilt(): void;
|
|
309
441
|
/** Re-evaluate play/pause after `config.paused` changes. */
|
|
310
442
|
refreshPlayback(): void;
|
|
311
443
|
/** Jump the camera to the config's authored framing (cameraPosition / cameraTarget /
|
|
@@ -357,5 +489,5 @@ declare class WaveRenderer {
|
|
|
357
489
|
dispose(): void;
|
|
358
490
|
}
|
|
359
491
|
//#endregion
|
|
360
|
-
export { FRAME_H, FRAME_W, WaveRenderer, WaveRendererOptions, frameZoom, hexToLinearVec3 };
|
|
492
|
+
export { FRAME_H, FRAME_W, WaveMaterial, WaveParticleField, WaveRenderer, WaveRendererOptions, WaveUniforms, frameZoom, hexToLinearVec3 };
|
|
361
493
|
//# sourceMappingURL=WaveRenderer.d.ts.map
|