@wave3d/core 0.9.0 → 0.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/dist/config/model.d.ts +209 -6
- package/dist/config/model.js +85 -3
- package/dist/config/model.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/presets.js +295 -0
- package/dist/presets.js.map +1 -1
- package/dist/renderer/WaveGeometry.js +21 -0
- package/dist/renderer/WaveGeometry.js.map +1 -1
- package/dist/renderer/WaveRenderer.d.ts +62 -0
- package/dist/renderer/WaveRenderer.js +349 -10
- package/dist/renderer/WaveRenderer.js.map +1 -1
- package/dist/renderer/WaveRendererGPU.js +32 -2
- package/dist/renderer/WaveRendererGPU.js.map +1 -1
- package/dist/renderer/interaction.js +12 -0
- package/dist/renderer/interaction.js.map +1 -1
- package/dist/renderer/particleField.js +26 -2
- package/dist/renderer/particleField.js.map +1 -1
- package/dist/renderer/particleFieldGPU.js +6 -0
- package/dist/renderer/particleFieldGPU.js.map +1 -1
- package/dist/renderer/shaders.js +748 -13
- package/dist/renderer/shaders.js.map +1 -1
- package/dist/renderer/tsl/dissolve.js +56 -0
- package/dist/renderer/tsl/dissolve.js.map +1 -0
- package/dist/renderer/tsl/particleMaterial.js +46 -8
- package/dist/renderer/tsl/particleMaterial.js.map +1 -1
- package/dist/renderer/tsl/uniforms.js +40 -1
- package/dist/renderer/tsl/uniforms.js.map +1 -1
- package/dist/renderer/tsl/waveMaterial.js +275 -26
- package/dist/renderer/tsl/waveMaterial.js.map +1 -1
- package/dist/renderer/tsl/waveShape.js +31 -10
- package/dist/renderer/tsl/waveShape.js.map +1 -1
- package/dist/renderer/wavePath.js +189 -0
- package/dist/renderer/wavePath.js.map +1 -0
- package/dist/shell/createWave.d.ts +23 -3
- package/dist/shell/createWave.js +5 -4
- package/dist/shell/createWave.js.map +1 -1
- package/dist/shell/probe.d.ts +28 -0
- package/dist/shell/probe.js +58 -11
- package/dist/shell/probe.js.map +1 -1
- package/dist/standalone/wave3d.standalone.js +3401 -2108
- package/dist/standalone/wave3d.standalone.webgpu.js +7372 -5848
- package/dist/standalone.d.ts +2 -2
- package/dist/standalone.js +2 -2
- package/dist/studio/StudioWaveRenderer.d.ts +115 -8
- package/dist/studio/StudioWaveRenderer.js +588 -14
- package/dist/studio/StudioWaveRenderer.js.map +1 -1
- package/dist/studio/index.d.ts +2 -2
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/randomize.js +0 -1
- package/dist/studio/randomize.js.map +1 -1
- package/package.json +1 -1
- package/skills/wave3d/SKILL.md +142 -5
package/dist/config/model.d.ts
CHANGED
|
@@ -40,6 +40,18 @@ declare const CAMERA_FITS: readonly CameraFit[];
|
|
|
40
40
|
/** What fills the 2D palette texture: the baked hero LUT, our editable stops, or
|
|
41
41
|
* a named built-in map (see PALETTE_MAPS). Any string is allowed for forward-compat. */
|
|
42
42
|
type PaletteSource = "hero" | "stops" | (string & {});
|
|
43
|
+
/**
|
|
44
|
+
* One control point of a {@link WaveConfig.path}: where the ribbon's centre passes, how wide it is
|
|
45
|
+
* there, and how far its cross-section has rotated. `width` 1 is the ribbon's natural width; `twist`
|
|
46
|
+
* is in degrees. Both are optional and default to 1 / 0.
|
|
47
|
+
*/
|
|
48
|
+
interface PathPoint {
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
z: number;
|
|
52
|
+
width?: number;
|
|
53
|
+
twist?: number;
|
|
54
|
+
}
|
|
43
55
|
/** A positionable light. `position` lives in the same 3D space as the wave. */
|
|
44
56
|
interface LightConfig {
|
|
45
57
|
position: Vec3;
|
|
@@ -103,6 +115,8 @@ declare function createDefaultMeshPoints(): MeshGradientPoint[];
|
|
|
103
115
|
* (normalizeWaveColour, randomize*) map 1:1.
|
|
104
116
|
*/
|
|
105
117
|
interface WaveConfig {
|
|
118
|
+
/** What to call this wave in the studio, if "Wave 3" is not enough. Absent ⇒ that numbering. */
|
|
119
|
+
name?: string;
|
|
106
120
|
palette: ColorStop[];
|
|
107
121
|
gradientType: GradientType;
|
|
108
122
|
gradientAngle: number;
|
|
@@ -139,7 +153,8 @@ interface WaveConfig {
|
|
|
139
153
|
edgeFade: number;
|
|
140
154
|
/** Softness of the ribbon's two ENDS — it smoothsteps on uv.y, which is the length, not the
|
|
141
155
|
* long edges. 0.1 = the original hardcoded value; smaller = razor-crisp graphic ribbons,
|
|
142
|
-
* larger = soft vapor.
|
|
156
|
+
* larger = soft vapor. Both themes honour it: on the wireframe it is what stops a sweep ending
|
|
157
|
+
* at a flat end-cap that reads as a straight cut drawn across the strands. */
|
|
143
158
|
edgeFeather: number;
|
|
144
159
|
/** Depth tint (solid theme): fade far fragments toward depthTintColor for atmospheric
|
|
145
160
|
* separation in multi-wave stacks (0 = off). */
|
|
@@ -181,17 +196,143 @@ interface WaveConfig {
|
|
|
181
196
|
radialSpread?: number;
|
|
182
197
|
radialRadius?: number;
|
|
183
198
|
radialCenter?: number;
|
|
184
|
-
|
|
199
|
+
/** Lift the fan out of its own plane as it spreads, turning the flat plume into a CONE — a
|
|
200
|
+
* trumpet / morning-glory mouth whose combed strands run down the slant into the throat, which is
|
|
201
|
+
* the one thing neither the twists nor the helix can reach (a helix carries the ribbon around an
|
|
202
|
+
* axis, but its WIDTH never follows the slant). Measured as lift per ribbon-length of radius:
|
|
203
|
+
* 0 = the flat fan (the default, byte-identical), ~0.6 a wide mouth, ~1.4 a narrow horn.
|
|
204
|
+
* Negative cones the other way. Inert unless `radialAmount` > 0. */
|
|
205
|
+
radialCone?: number;
|
|
206
|
+
/** Degrees of ANGLE the band gains along its own length. The fan's angle otherwise comes from uv.x
|
|
207
|
+
* alone, so every arm runs straight out from the throat; radius already grows with uv.y, and
|
|
208
|
+
* letting angle grow with it too is exactly what turns a straight arm into a SPIRAL one that
|
|
209
|
+
* curves around the throat. 0 = straight (the default, byte-identical); 150 wraps most of a
|
|
210
|
+
* half-turn. Negative spirals the other way. Inert unless `radialAmount` > 0. */
|
|
211
|
+
radialSwirl?: number;
|
|
212
|
+
/**
|
|
213
|
+
* PATH — the ribbon's centreline, as control points it is swept along. Absent ⇒ the straight
|
|
214
|
+
* centreline the folded geometry is born with (byte-identical: the shader block is not compiled).
|
|
215
|
+
*
|
|
216
|
+
* This is the shape control the others cannot substitute for. The twists rotate a ribbon whose
|
|
217
|
+
* centreline is fixed, the helix carries that fixed centreline around an axis, the radial fan
|
|
218
|
+
* splays it — so none of them can make a ribbon that changes direction more than once, crosses
|
|
219
|
+
* itself, or is wide here and narrow there. A path can, because it IS the centreline.
|
|
220
|
+
*
|
|
221
|
+
* Points are in the wave's LOCAL space, the same units the geometry uses: the un-pathed ribbon
|
|
222
|
+
* runs from x −200 to +200 along its length, so `straightPath()` reproduces it. They are swept by
|
|
223
|
+
* ARC LENGTH with a parallel-transported frame, which is what keeps the strand comb even however
|
|
224
|
+
* the points are dragged and stops the ribbon snapping through inflections.
|
|
225
|
+
*
|
|
226
|
+
* Per point, `width` scales the ribbon's width there (0.1 is a throat, 2 a flare — this is what a
|
|
227
|
+
* separate "pinch" knob would otherwise be) and `twist` rotates its cross-section in degrees.
|
|
228
|
+
* Both interpolate smoothly between points.
|
|
229
|
+
*/
|
|
230
|
+
path?: PathPoint[];
|
|
231
|
+
theme?: "solid" | "wireframe" | "glass";
|
|
232
|
+
/** Glass only: how far the ribbon bends what is behind it, in PIXELS at the silhouette. The bend
|
|
233
|
+
* is strongest where the surface turns away from the camera and falls to nothing face-on, which
|
|
234
|
+
* is what gives a sheet its edge compression. 0 is a clear pane. */
|
|
235
|
+
glassStrength?: number;
|
|
236
|
+
/** Glass only: per-channel split of that bend (dispersion). Past ~1 it reads as an oil sheen. */
|
|
237
|
+
glassChroma?: number;
|
|
238
|
+
/** Glass only: 0 clear · 1 frosted. Blurs the backdrop AT the refracted position, so the frost
|
|
239
|
+
* rides the bend rather than sitting flat under it. The scatter radius grows with the square of
|
|
240
|
+
* this, and the taps are skipped while it is under half a pixel (below ~0.1), where they could
|
|
241
|
+
* only average back to the sample they surround. */
|
|
242
|
+
glassFrost?: number;
|
|
243
|
+
/** Glass only: strength of the edge glint. It ADDS light over a dark backdrop and DARKENS over a
|
|
244
|
+
* bright one, which is what keeps a rim visible on white paper. */
|
|
245
|
+
glassSpec?: number;
|
|
246
|
+
/** Glass only: how far the interior pulls toward mid-grey — legibility for anything read through
|
|
247
|
+
* the sheet, and the haze that separates glass from a clear hole. */
|
|
248
|
+
glassVibrancy?: number;
|
|
249
|
+
/** Glass only: how much of the wave's own palette colour tints the glass (0 = colourless).
|
|
250
|
+
* Deliberately LOW by default. Glass reads as glass because of what is behind it being bent, not
|
|
251
|
+
* because the sheet carries colour — push this up and it stops looking like glass and starts
|
|
252
|
+
* looking like a filled material that happens to be shiny. */
|
|
253
|
+
glassTint?: number;
|
|
254
|
+
/** Glass only: HALF the optical path at normal incidence — the sheet's thickness. With density it
|
|
255
|
+
* sets how saturated the transmitted colour gets. This is what makes glass a material rather
|
|
256
|
+
* than a window: the colour comes from the ribbon's own palette absorbed over its own thickness,
|
|
257
|
+
* so it reads as glass with nothing behind it at all. */
|
|
258
|
+
glassPath?: number;
|
|
259
|
+
/** Glass only: absorption coefficient. High = deep, saturated glass; low = barely tinted. */
|
|
260
|
+
glassDensity?: number;
|
|
261
|
+
/** Glass only: edge whitening. The band is deliberately WIDE — a narrow one is thinner than a
|
|
262
|
+
* pixel on a thin ribbon and the knob does nothing at all. */
|
|
263
|
+
glassRim?: number;
|
|
264
|
+
/** Glass only: thin-film iridescence on the reflection, rim and specular — never on the
|
|
265
|
+
* transmission, which would read as dye rather than as a film. */
|
|
266
|
+
glassIrid?: number;
|
|
267
|
+
/** Glass only: optical film thickness in nm; 300–500 is the soap-bubble band. */
|
|
268
|
+
glassFilmNm?: number;
|
|
269
|
+
/** Glass only: index of refraction, used by the fresnel and the film. */
|
|
270
|
+
glassIor?: number;
|
|
271
|
+
/** Glass only: how much a fold over itself thickens the sheet. Glass draws opaque, so the layer
|
|
272
|
+
* behind is otherwise invisible and a doubled-back ribbon looks exactly as thin as a single
|
|
273
|
+
* sheet — this is the cue that reads as volume. 0 ignores overlap entirely. */
|
|
274
|
+
glassLayerGain?: number;
|
|
275
|
+
/** Glass only: DROPLET FUSION. Takes the refraction's direction from the gradient of the merged
|
|
276
|
+
* silhouette rather than from each surface's own normal, so two sheets passing close read as one
|
|
277
|
+
* blob of something viscous — in the neck between them the bend rotates smoothly from one rim to
|
|
278
|
+
* the other instead of tearing between two centres. 0 is off and each sheet keeps its own. */
|
|
279
|
+
glassFusion?: number;
|
|
280
|
+
/** Glass only: CAUSTICS — brightness where the refraction map compresses and neighbouring rays
|
|
281
|
+
* pile up, darkness where it spreads. Computed from the Jacobian of the sampling map, so it
|
|
282
|
+
* costs no extra pass and lands exactly where the optics put it rather than being painted on.
|
|
283
|
+
* Only visible where there is a backdrop to concentrate: a sheet over blank page has no light
|
|
284
|
+
* to gather. */
|
|
285
|
+
glassCaustic?: number;
|
|
286
|
+
/** Glass only: LIQUID — how hard four travelling waves tilt the surface normal. Everything
|
|
287
|
+
* downstream (dispersion, rim, specular) reads the rippled normal, so the shimmer stays coherent
|
|
288
|
+
* instead of sitting on top as a separate layer. 0 is still glass, just not moving. */
|
|
289
|
+
glassRipple?: number;
|
|
290
|
+
/** Glass only: waves per world unit. */
|
|
291
|
+
glassRippleScale?: number;
|
|
292
|
+
/** Glass only: how fast they travel, rad/s. */
|
|
293
|
+
glassFlow?: number;
|
|
294
|
+
/** Glass only: falloff exponent of the rim band. Low = the whole sheet bends; high = only the
|
|
295
|
+
* silhouette does, which is the crisp compression ring. */
|
|
296
|
+
glassRimPower?: number;
|
|
185
297
|
lineAmount?: number;
|
|
186
298
|
lineThickness?: number;
|
|
187
299
|
lineDerivativePower?: number;
|
|
300
|
+
/** Wireframe only: how hard the strands recede INTO the page background with depth. 1 (the
|
|
301
|
+
* default) is the original hardcoded fade — it gives a single ribbon its sense of depth, but on a
|
|
302
|
+
* tightly-fitted near/far slab it washes out the whole back half of a deep or stacked
|
|
303
|
+
* composition. 0 turns it off, so every strand holds full contrast wherever it sits: the flat,
|
|
304
|
+
* graphic, poster look. */
|
|
305
|
+
lineDepthFade?: number;
|
|
306
|
+
/** Wireframe only: 0..1, how HARD the edge of each strand is. The stripe is a soft ramp by
|
|
307
|
+
* default (0), which means {@link lineThickness} widens the strands by fading the gaps away with
|
|
308
|
+
* them — the surface goes from pale hairlines to flat solid without passing through dense ink.
|
|
309
|
+
* Raising this steepens the ramp about its midpoint, which splits the two controls apart:
|
|
310
|
+
* `lineThickness` becomes the DUTY CYCLE (how much of each period is strand rather than gap) and
|
|
311
|
+
* this becomes the edge. 0.9 with `lineThickness` ~1.5 is heavy ink with crisp gaps still
|
|
312
|
+
* reading — the engraved / guilloché look. Default 0 (the original soft ramp). */
|
|
313
|
+
lineSharpness?: number;
|
|
314
|
+
/** Wireframe only: what sits between the strands. ABSENT = the page background, which makes the
|
|
315
|
+
* wave a window onto the page (dark strands, paper showing through) — the theme as it has always
|
|
316
|
+
* drawn. A colour makes the ribbon its own BODY: a dark gap colour under a bright palette is an
|
|
317
|
+
* opaque striped surface, which is the other half of this theme's range and the one that reads as
|
|
318
|
+
* a lit solid rather than a drawing. `"transparent"` (or an 8-digit hex with a low alpha) leaves
|
|
319
|
+
* the gaps CLEAR instead, so stacked folds show through each other rather than occluding —
|
|
320
|
+
* airier, but the near fold no longer hides the far one, which is what makes a stack read solid. */
|
|
321
|
+
lineGapColor?: string;
|
|
322
|
+
/** Wireframe only: 0..1, how much the strands are LIT. The line theme is otherwise unlit — a
|
|
323
|
+
* strand's colour comes from its uv alone, so it holds one tone wherever the surface turns, which
|
|
324
|
+
* is why a dense wireframe reads as a printed pattern instead of an object. Turn this up and the
|
|
325
|
+
* same derivative normal and scene `lights` the solid theme uses shade it, AND each strand is
|
|
326
|
+
* given a round cross-section, so a specular runs along one strand and not its neighbour. (Those
|
|
327
|
+
* are one knob on purpose: shading a flat stripe barely reads — it is the round section that makes
|
|
328
|
+
* a strand look like a filament.) Default 0. */
|
|
329
|
+
lineLight?: number;
|
|
188
330
|
/** Wireframe RUNGS: a second line family carved at constant uv.y, so these run ACROSS the ribbon
|
|
189
331
|
* where `lineAmount`'s run along it — the two cross into a ladder. Frequency, like `lineAmount`
|
|
190
332
|
* (rungs ≈ amount / π). 0 = off, and the cross-wise path isn't compiled. */
|
|
191
333
|
rungAmount?: number;
|
|
192
334
|
/** Rung line width, in pixels (screen-space, so it holds at any zoom). */
|
|
193
335
|
rungThickness?: number;
|
|
194
|
-
maxWidth?: number;
|
|
195
336
|
position: Vec3;
|
|
196
337
|
rotation: Vec3;
|
|
197
338
|
scale: Vec3;
|
|
@@ -208,6 +349,53 @@ interface WaveConfig {
|
|
|
208
349
|
/** Optional per-wave particle / dust field emitted off THIS wave's deformed surface / edge.
|
|
209
350
|
* ABSENT ⇒ off (no THREE.Points for this wave, byte-identical). See {@link ParticlesConfig}. */
|
|
210
351
|
particles?: ParticlesConfig;
|
|
352
|
+
/** Optional disintegration ("the snap"): a front that sweeps across the ribbon eating it away in
|
|
353
|
+
* chunks, and — with a particle field — blowing those chunks off as dust. ABSENT ⇒ intact (no
|
|
354
|
+
* DISSOLVE program, byte-identical). See {@link DissolveConfig}. */
|
|
355
|
+
dissolve?: DissolveConfig;
|
|
356
|
+
}
|
|
357
|
+
/** Which way a {@link DissolveConfig} front sweeps. `length` / `width` follow the RIBBON's own uv
|
|
358
|
+
* axes, so the front travels with the sheet wherever the twist takes it; `screenX` / `screenY` are
|
|
359
|
+
* a straight line on the CANVAS, so every wave in a stack crumbles against the same edge whatever
|
|
360
|
+
* each one's orientation. */
|
|
361
|
+
type DissolveAxis = "length" | "width" | "screenX" | "screenY";
|
|
362
|
+
declare const DISSOLVE_AXES: readonly DissolveAxis[];
|
|
363
|
+
/**
|
|
364
|
+
* DISINTEGRATION — the "snap". A band sweeps across the ribbon in uv and everything behind it is
|
|
365
|
+
* eaten away chunk by chunk, so the surface CRUMBLES rather than fading: holes open in it, the holes
|
|
366
|
+
* merge, and the last fragments break off. Where the wave also has {@link ParticlesConfig}, `dust`
|
|
367
|
+
* pins that field to the same front, so the motes are the chunks that just left — the surface does
|
|
368
|
+
* not fade into an unrelated cloud, it becomes one.
|
|
369
|
+
*
|
|
370
|
+
* `amount` is the whole animation: 0 is intact and 1 is gone, whatever the band width, so binding it
|
|
371
|
+
* to `scroll` (or any other input — it is a {@link WaveInteractionTarget}) disintegrates the wave as
|
|
372
|
+
* the reader moves. Absent ⇒ the DISSOLVE shader path is never compiled.
|
|
373
|
+
*/
|
|
374
|
+
interface DissolveConfig {
|
|
375
|
+
/** 0..1 — how far the front has swept. 0 = the ribbon is whole; 1 = every chunk is gone. */
|
|
376
|
+
amount: number;
|
|
377
|
+
/** Which way the front travels. `"length"` (uv.y, end to end — the default) and `"width"` (uv.x,
|
|
378
|
+
* across the folded cross-section) ride the ribbon, so the front bends with it; mind the axes —
|
|
379
|
+
* uv.y is the LENGTH (see the UV AXES note atop WaveGeometry). `"screenX"` / `"screenY"` sweep a
|
|
380
|
+
* straight line across the CANVAS instead, which is what makes a multi-wave composition crumble
|
|
381
|
+
* as ONE object: give every wave the same axis and amount and they share one edge. The crumb
|
|
382
|
+
* pattern stays on the surface either way. */
|
|
383
|
+
axis?: DissolveAxis;
|
|
384
|
+
/** Sweep from the far end instead of the near one. */
|
|
385
|
+
reverse?: boolean;
|
|
386
|
+
/** Width of the crumbling band, in uv. 0.05 is a clean guillotine edge; 0.6 is a long ragged fray
|
|
387
|
+
* where half the ribbon is mid-flight at once. Default 0.35. */
|
|
388
|
+
band?: number;
|
|
389
|
+
/** How finely the ribbon is diced — chunks across its WIDTH (they are kept square on the sheet,
|
|
390
|
+
* so the length gets ~2.1× as many). Default 90; smaller = big slabs, larger = fine grit. */
|
|
391
|
+
scale?: number;
|
|
392
|
+
/** 0..1 — chunk character: 0 = organic torn tatters (smooth noise), 1 = hard quantized cells
|
|
393
|
+
* (blocky pixel debris). Default 0.6. */
|
|
394
|
+
blocky?: number;
|
|
395
|
+
/** 0..1 — how strongly this wave's own dust is pinned to the front: 1 = each mote peels off
|
|
396
|
+
* exactly where and when the surface under it crumbles and drifts on from there; 0 = the field
|
|
397
|
+
* free-runs on `life` as it always has. Default 1. Inert without {@link WaveConfig.particles}. */
|
|
398
|
+
dust?: number;
|
|
211
399
|
}
|
|
212
400
|
/**
|
|
213
401
|
* An interaction INPUT: a normalized signal that can smoothly drive config params through an
|
|
@@ -216,7 +404,7 @@ interface WaveConfig {
|
|
|
216
404
|
type InteractionSource = "scroll" | "hover" | "pointerX" | "pointerY" | "pointerSpeed" | "press" | "scrollVelocity" | "appear" | "tiltX" | "tiltY" | `custom:${string}`;
|
|
217
405
|
/** Per-WAVE params a binding may drive. Single source of truth for WAVE_APPLIERS in
|
|
218
406
|
* renderer/interaction.ts (checked via `satisfies`) and validated by normalizeWaveInteraction. */
|
|
219
|
-
declare const WAVE_TARGET_NAMES: readonly ["displaceAmount", "detailAmount", "twistPowerX", "twistPowerY", "twistPowerZ", "twistFrequencyX", "twistFrequencyY", "twistFrequencyZ", "helixPhase", "helixTurns", "helixRadius", "hueShift", "gradientShift", "colorSaturation", "opacity", "lineThickness", "lineAmount", "fiberStrength", "sheen", "iridescence", "positionX", "positionY"];
|
|
407
|
+
declare const WAVE_TARGET_NAMES: readonly ["displaceAmount", "detailAmount", "twistPowerX", "twistPowerY", "twistPowerZ", "twistFrequencyX", "twistFrequencyY", "twistFrequencyZ", "helixPhase", "helixTurns", "helixRadius", "dissolveAmount", "glassRipple", "glassStrength", "glassTint", "hueShift", "gradientShift", "colorSaturation", "opacity", "lineThickness", "lineAmount", "fiberStrength", "sheen", "iridescence", "positionX", "positionY"];
|
|
220
408
|
/** A per-wave param a {@link WaveInteractionBinding} can drive. */
|
|
221
409
|
type WaveInteractionTarget = (typeof WAVE_TARGET_NAMES)[number];
|
|
222
410
|
/** SCENE params a binding may drive (post / camera / time — shared, not per wave). Single source of
|
|
@@ -358,7 +546,7 @@ interface TiltConfig {
|
|
|
358
546
|
/** How each particle sprite is drawn (a per-field render style, not per-particle). All but
|
|
359
547
|
* "sprite" are drawn procedurally from `gl_PointCoord`; "sprite" samples {@link
|
|
360
548
|
* ParticlesConfig.spriteUrl} and falls back to "glitter" until that image has rasterized. */
|
|
361
|
-
type ParticleShape = "glitter" | "soft" | "ring" | "star" | "streak" | "sprite";
|
|
549
|
+
type ParticleShape = "glitter" | "soft" | "ring" | "star" | "streak" | "square" | "sprite";
|
|
362
550
|
declare const PARTICLE_SHAPES: readonly ParticleShape[];
|
|
363
551
|
interface ParticlesConfig {
|
|
364
552
|
count: number;
|
|
@@ -388,6 +576,14 @@ interface ParticlesConfig {
|
|
|
388
576
|
wander?: number;
|
|
389
577
|
/** Sprite render style. Default "glitter" (the soft round additive disc). */
|
|
390
578
|
shape?: ParticleShape;
|
|
579
|
+
/**
|
|
580
|
+
* How the sprites composite. `"additive"` (the default) ADDS light — glints, embers, sparks; it can
|
|
581
|
+
* only ever brighten, so additive dust is invisible on a white page and can never read as dark.
|
|
582
|
+
* `"normal"` alpha-blends them instead, which is what a dark mote on a light ground needs: soot,
|
|
583
|
+
* ash, ink, the blocky debris a {@link DissolveConfig} sheds across a pale background. Either way
|
|
584
|
+
* the field never writes depth, so it composites over the waves rather than occluding them.
|
|
585
|
+
*/
|
|
586
|
+
blend?: "additive" | "normal";
|
|
391
587
|
/**
|
|
392
588
|
* Artwork for `shape: "sprite"` — an SVG (or raster) `data:` URI or URL, rasterized ONCE into a
|
|
393
589
|
* square texture shared by every particle in the field, so the cost is one texture per field and
|
|
@@ -557,10 +753,17 @@ declare function normalizeSceneInteraction(config: StudioConfig): void;
|
|
|
557
753
|
* when the block is absent — absence is off and byte-identical (ensureStudioConfig gates on presence).
|
|
558
754
|
*/
|
|
559
755
|
declare function normalizeParticles(wave: WaveConfig): void;
|
|
756
|
+
/** Clamp a present {@link WaveConfig.path}: drop anything that is not a finite point, and drop the
|
|
757
|
+
* whole path if fewer than two survive (one point is not a centreline). Present-only, like the
|
|
758
|
+
* particle and dissolve blocks — absence means "the straight ribbon". */
|
|
759
|
+
declare function normalizePath(wave: WaveConfig): void;
|
|
760
|
+
/** Clamp a present {@link DissolveConfig} (present-only, like {@link normalizeParticles}: a wave
|
|
761
|
+
* with no `dissolve` block is left exactly as it is). */
|
|
762
|
+
declare function normalizeDissolve(wave: WaveConfig): void;
|
|
560
763
|
/** Normalize an ingested config to the wave model: backfill the scene + every wave, and drop in
|
|
561
764
|
* a default wave if none are present. Idempotent, so it is safe on the renderer's own config as
|
|
562
765
|
* well as freshly loaded save-states / share links. */
|
|
563
766
|
declare function ensureStudioConfig(input: StudioConfig): StudioConfig;
|
|
564
767
|
//#endregion
|
|
565
|
-
export { BackgroundImageFit, BackgroundMode, BasicGradientType, BlendMode, CAMERA_FITS, CameraFit, ColorStop, DEFAULT_LIGHT_POSITION, GradientType, InteractionSource, LightConfig, MAX_COLORS, MAX_LIGHTS, MAX_MESH_POINTS, MAX_NOISE_BANDS, MAX_WAVES, MeshGradientPoint, NoiseBand, PARTICLE_SHAPES, PaletteSource, ParticleShape, ParticlesConfig, SceneConfig, SceneInteractionBinding, SceneInteractionConfig, SceneInteractionTarget, StudioConfig, TiltConfig, Vec2, Vec3, WaveConfig, WaveHoverConfig, WaveInteractionBinding, WaveInteractionConfig, WaveInteractionTarget, WavePressConfig, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, normalizeBackground, normalizeParticles, normalizeSceneInteraction, normalizeWave, normalizeWaveInteraction, resizeWaves };
|
|
768
|
+
export { BackgroundImageFit, BackgroundMode, BasicGradientType, BlendMode, CAMERA_FITS, CameraFit, ColorStop, DEFAULT_LIGHT_POSITION, DISSOLVE_AXES, DissolveAxis, DissolveConfig, GradientType, InteractionSource, LightConfig, MAX_COLORS, MAX_LIGHTS, MAX_MESH_POINTS, MAX_NOISE_BANDS, MAX_WAVES, MeshGradientPoint, NoiseBand, PARTICLE_SHAPES, PaletteSource, ParticleShape, ParticlesConfig, PathPoint, SceneConfig, SceneInteractionBinding, SceneInteractionConfig, SceneInteractionTarget, StudioConfig, TiltConfig, Vec2, Vec3, WaveConfig, WaveHoverConfig, WaveInteractionBinding, WaveInteractionConfig, WaveInteractionTarget, WavePressConfig, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, normalizeBackground, normalizeDissolve, normalizeParticles, normalizePath, normalizeSceneInteraction, normalizeWave, normalizeWaveInteraction, resizeWaves };
|
|
566
769
|
//# sourceMappingURL=model.d.ts.map
|
package/dist/config/model.js
CHANGED
|
@@ -96,6 +96,12 @@ function createDefaultMeshPoints() {
|
|
|
96
96
|
}
|
|
97
97
|
];
|
|
98
98
|
}
|
|
99
|
+
const DISSOLVE_AXES = [
|
|
100
|
+
"length",
|
|
101
|
+
"width",
|
|
102
|
+
"screenX",
|
|
103
|
+
"screenY"
|
|
104
|
+
];
|
|
99
105
|
/** The built-in interaction input names (the open-ended `custom:*` family is handled separately).
|
|
100
106
|
* Kept in sync by hand with the {@link InteractionSource} union below. */
|
|
101
107
|
const INTERACTION_SOURCE_NAMES = [
|
|
@@ -124,6 +130,10 @@ const WAVE_TARGET_NAMES = [
|
|
|
124
130
|
"helixPhase",
|
|
125
131
|
"helixTurns",
|
|
126
132
|
"helixRadius",
|
|
133
|
+
"dissolveAmount",
|
|
134
|
+
"glassRipple",
|
|
135
|
+
"glassStrength",
|
|
136
|
+
"glassTint",
|
|
127
137
|
"hueShift",
|
|
128
138
|
"gradientShift",
|
|
129
139
|
"colorSaturation",
|
|
@@ -150,6 +160,7 @@ const PARTICLE_SHAPES = [
|
|
|
150
160
|
"ring",
|
|
151
161
|
"star",
|
|
152
162
|
"streak",
|
|
163
|
+
"square",
|
|
153
164
|
"sprite"
|
|
154
165
|
];
|
|
155
166
|
/** Spread a base wave into `count` overlapping waves — each with a slightly varied hue, width,
|
|
@@ -285,13 +296,17 @@ function defaultWave() {
|
|
|
285
296
|
radialSpread: 1,
|
|
286
297
|
radialRadius: 40,
|
|
287
298
|
radialCenter: 0,
|
|
299
|
+
radialCone: 0,
|
|
300
|
+
radialSwirl: 0,
|
|
288
301
|
theme: "solid",
|
|
289
302
|
lineAmount: 425,
|
|
290
303
|
lineThickness: 1,
|
|
291
304
|
lineDerivativePower: .95,
|
|
305
|
+
lineDepthFade: 1,
|
|
306
|
+
lineSharpness: 0,
|
|
307
|
+
lineLight: 0,
|
|
292
308
|
rungAmount: 0,
|
|
293
309
|
rungThickness: 1,
|
|
294
|
-
maxWidth: 1232,
|
|
295
310
|
position: {
|
|
296
311
|
x: -24.3,
|
|
297
312
|
y: -56.4,
|
|
@@ -542,13 +557,39 @@ function normalizeWave(s) {
|
|
|
542
557
|
if (!Number.isFinite(s.radialSpread)) s.radialSpread = 1;
|
|
543
558
|
if (!Number.isFinite(s.radialRadius)) s.radialRadius = 40;
|
|
544
559
|
if (!Number.isFinite(s.radialCenter)) s.radialCenter = 0;
|
|
560
|
+
if (!Number.isFinite(s.radialCone)) s.radialCone = 0;
|
|
561
|
+
if (!Number.isFinite(s.radialSwirl)) s.radialSwirl = 0;
|
|
545
562
|
if (typeof s.theme !== "string") s.theme = "solid";
|
|
563
|
+
if (s.theme === "glass") {
|
|
564
|
+
if (!Number.isFinite(s.glassStrength)) s.glassStrength = 90;
|
|
565
|
+
if (!Number.isFinite(s.glassChroma)) s.glassChroma = .7;
|
|
566
|
+
if (!Number.isFinite(s.glassFrost)) s.glassFrost = .08;
|
|
567
|
+
if (!Number.isFinite(s.glassSpec)) s.glassSpec = 1.2;
|
|
568
|
+
if (!Number.isFinite(s.glassVibrancy)) s.glassVibrancy = .05;
|
|
569
|
+
if (!Number.isFinite(s.glassTint)) s.glassTint = .12;
|
|
570
|
+
if (!Number.isFinite(s.glassRimPower)) s.glassRimPower = 1.2;
|
|
571
|
+
if (!Number.isFinite(s.glassRipple)) s.glassRipple = 0;
|
|
572
|
+
if (!Number.isFinite(s.glassRippleScale)) s.glassRippleScale = .012;
|
|
573
|
+
if (!Number.isFinite(s.glassFlow)) s.glassFlow = .9;
|
|
574
|
+
if (!Number.isFinite(s.glassPath)) s.glassPath = .45;
|
|
575
|
+
if (!Number.isFinite(s.glassDensity)) s.glassDensity = 1.2;
|
|
576
|
+
if (!Number.isFinite(s.glassRim)) s.glassRim = .5;
|
|
577
|
+
if (!Number.isFinite(s.glassIrid)) s.glassIrid = 0;
|
|
578
|
+
if (!Number.isFinite(s.glassFilmNm)) s.glassFilmNm = 380;
|
|
579
|
+
if (!Number.isFinite(s.glassIor)) s.glassIor = 1.45;
|
|
580
|
+
if (!Number.isFinite(s.glassLayerGain)) s.glassLayerGain = .6;
|
|
581
|
+
if (!Number.isFinite(s.glassFusion)) s.glassFusion = 0;
|
|
582
|
+
if (!Number.isFinite(s.glassCaustic)) s.glassCaustic = .4;
|
|
583
|
+
}
|
|
546
584
|
if (!Number.isFinite(s.lineAmount)) s.lineAmount = 425;
|
|
547
585
|
if (!Number.isFinite(s.lineThickness)) s.lineThickness = 1;
|
|
548
586
|
if (!Number.isFinite(s.lineDerivativePower)) s.lineDerivativePower = .95;
|
|
587
|
+
if (!Number.isFinite(s.lineDepthFade)) s.lineDepthFade = 1;
|
|
588
|
+
if (!Number.isFinite(s.lineSharpness)) s.lineSharpness = 0;
|
|
589
|
+
if (s.lineGapColor !== void 0 && typeof s.lineGapColor !== "string") delete s.lineGapColor;
|
|
590
|
+
if (!Number.isFinite(s.lineLight)) s.lineLight = 0;
|
|
549
591
|
if (!Number.isFinite(s.rungAmount)) s.rungAmount = 0;
|
|
550
592
|
if (!Number.isFinite(s.rungThickness)) s.rungThickness = 1;
|
|
551
|
-
if (!Number.isFinite(s.maxWidth)) s.maxWidth = 1232;
|
|
552
593
|
if (!s.position) s.position = {
|
|
553
594
|
x: 0,
|
|
554
595
|
y: 0,
|
|
@@ -570,6 +611,13 @@ function normalizeWave(s) {
|
|
|
570
611
|
if (!Number.isFinite(s.seed)) s.seed = 0;
|
|
571
612
|
if (s.interaction) normalizeWaveInteraction(s);
|
|
572
613
|
if (s.particles) normalizeParticles(s);
|
|
614
|
+
if (s.dissolve) normalizeDissolve(s);
|
|
615
|
+
if (typeof s.name === "string") {
|
|
616
|
+
const named = s.name.trim().slice(0, 60);
|
|
617
|
+
if (named) s.name = named;
|
|
618
|
+
else delete s.name;
|
|
619
|
+
} else if (s.name !== void 0) delete s.name;
|
|
620
|
+
if (s.path) normalizePath(s);
|
|
573
621
|
}
|
|
574
622
|
/** Backfill scene-level defaults (background/camera/post/lights/quality/mirror). */
|
|
575
623
|
function ensureSceneDefaults(config) {
|
|
@@ -743,9 +791,43 @@ function normalizeParticles(wave) {
|
|
|
743
791
|
if (p.swirl !== void 0) p.swirl = num(p.swirl, 0);
|
|
744
792
|
if (p.wander !== void 0) p.wander = num(p.wander, 0);
|
|
745
793
|
if (p.shape !== void 0 && !PARTICLE_SHAPES.includes(p.shape)) p.shape = "glitter";
|
|
794
|
+
if (p.blend !== void 0 && p.blend !== "additive" && p.blend !== "normal") p.blend = "additive";
|
|
746
795
|
if (p.spriteUrl !== void 0 && typeof p.spriteUrl !== "string") delete p.spriteUrl;
|
|
747
796
|
if (p.pointerShove !== void 0) p.pointerShove = clampNumber(p.pointerShove, 0, 4, 1);
|
|
748
797
|
}
|
|
798
|
+
/** Clamp a present {@link WaveConfig.path}: drop anything that is not a finite point, and drop the
|
|
799
|
+
* whole path if fewer than two survive (one point is not a centreline). Present-only, like the
|
|
800
|
+
* particle and dissolve blocks — absence means "the straight ribbon". */
|
|
801
|
+
function normalizePath(wave) {
|
|
802
|
+
const p = wave.path;
|
|
803
|
+
if (!p) return;
|
|
804
|
+
if (!Array.isArray(p)) {
|
|
805
|
+
delete wave.path;
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const pts = p.filter((q) => !!q && typeof q === "object").map((q) => ({
|
|
809
|
+
x: num(q.x, 0),
|
|
810
|
+
y: num(q.y, 0),
|
|
811
|
+
z: num(q.z, 0),
|
|
812
|
+
width: q.width === void 0 ? void 0 : clampNumber(q.width, 0, 8, 1),
|
|
813
|
+
twist: q.twist === void 0 ? void 0 : num(q.twist, 0)
|
|
814
|
+
})).filter((q) => Number.isFinite(q.x) && Number.isFinite(q.y) && Number.isFinite(q.z));
|
|
815
|
+
if (pts.length < 2) delete wave.path;
|
|
816
|
+
else wave.path = pts;
|
|
817
|
+
}
|
|
818
|
+
/** Clamp a present {@link DissolveConfig} (present-only, like {@link normalizeParticles}: a wave
|
|
819
|
+
* with no `dissolve` block is left exactly as it is). */
|
|
820
|
+
function normalizeDissolve(wave) {
|
|
821
|
+
const d = wave.dissolve;
|
|
822
|
+
if (!d) return;
|
|
823
|
+
d.amount = clampNumber(d.amount, 0, 1, 0);
|
|
824
|
+
if (d.axis !== void 0 && !DISSOLVE_AXES.includes(d.axis)) d.axis = "length";
|
|
825
|
+
if (d.reverse !== void 0) d.reverse = !!d.reverse;
|
|
826
|
+
if (d.band !== void 0) d.band = clampNumber(d.band, .01, 1, .35);
|
|
827
|
+
if (d.scale !== void 0) d.scale = clampNumber(d.scale, 2, 600, 90);
|
|
828
|
+
if (d.blocky !== void 0) d.blocky = clampNumber(d.blocky, 0, 1, .6);
|
|
829
|
+
if (d.dust !== void 0) d.dust = clampNumber(d.dust, 0, 1, 1);
|
|
830
|
+
}
|
|
749
831
|
/** Normalize an ingested config to the wave model: backfill the scene + every wave, and drop in
|
|
750
832
|
* a default wave if none are present. Idempotent, so it is safe on the renderer's own config as
|
|
751
833
|
* well as freshly loaded save-states / share links. */
|
|
@@ -759,6 +841,6 @@ function ensureStudioConfig(input) {
|
|
|
759
841
|
return config;
|
|
760
842
|
}
|
|
761
843
|
//#endregion
|
|
762
|
-
export { CAMERA_FITS, DEFAULT_LIGHT_POSITION, MAX_COLORS, MAX_LIGHTS, MAX_MESH_POINTS, MAX_NOISE_BANDS, MAX_WAVES, PARTICLE_SHAPES, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, normalizeBackground, normalizeParticles, normalizeSceneInteraction, normalizeWave, normalizeWaveInteraction, resizeWaves };
|
|
844
|
+
export { CAMERA_FITS, DEFAULT_LIGHT_POSITION, DISSOLVE_AXES, MAX_COLORS, MAX_LIGHTS, MAX_MESH_POINTS, MAX_NOISE_BANDS, MAX_WAVES, PARTICLE_SHAPES, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, normalizeBackground, normalizeDissolve, normalizeParticles, normalizePath, normalizeSceneInteraction, normalizeWave, normalizeWaveInteraction, resizeWaves };
|
|
763
845
|
|
|
764
846
|
//# sourceMappingURL=model.js.map
|