@wave3d/core 0.5.0 → 0.7.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 +2 -6
- package/dist/config/model.d.ts +97 -9
- package/dist/config/model.js +165 -61
- package/dist/config/model.js.map +1 -1
- package/dist/core-loader.d.ts +0 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/presets.d.ts +8 -3
- package/dist/presets.js +542 -1
- package/dist/presets.js.map +1 -1
- package/dist/renderer/WaveGeometry.d.ts +22 -4
- package/dist/renderer/WaveGeometry.js +22 -3
- package/dist/renderer/WaveGeometry.js.map +1 -1
- package/dist/renderer/WaveRenderer.d.ts +22 -2
- package/dist/renderer/WaveRenderer.js +118 -3
- package/dist/renderer/WaveRenderer.js.map +1 -1
- package/dist/renderer/heroPalette.d.ts +0 -1
- package/dist/renderer/interaction.d.ts +0 -2
- package/dist/renderer/interaction.js +9 -0
- package/dist/renderer/interaction.js.map +1 -1
- package/dist/renderer/palette.d.ts +1 -2
- package/dist/renderer/palette.js +7 -5
- package/dist/renderer/palette.js.map +1 -1
- package/dist/renderer/particleField.d.ts +50 -0
- package/dist/renderer/particleField.js +220 -0
- package/dist/renderer/particleField.js.map +1 -0
- package/dist/renderer/shaders.js +271 -67
- package/dist/renderer/shaders.js.map +1 -1
- package/dist/shell/createWave.d.ts +0 -1
- package/dist/standalone/wave3d.standalone.js +2868 -1950
- package/dist/standalone.d.ts +2 -3
- package/dist/standalone.js +2 -2
- package/dist/studio/StudioWaveRenderer.d.ts +0 -1
- package/dist/studio/randomize.d.ts +0 -1
- package/dist/studio/thumbnail.d.ts +2 -3
- package/dist/studio/thumbnail.js +16 -3
- package/dist/studio/thumbnail.js.map +1 -1
- package/package.json +9 -5
- package/skills/wave3d/SKILL.md +30 -12
package/README.md
CHANGED
|
@@ -40,9 +40,7 @@ const blob = await handle.snapshot(); // resolves null until running. options: {
|
|
|
40
40
|
```html
|
|
41
41
|
<script type="module">
|
|
42
42
|
import { mountWave } from "https://esm.sh/@wave3d/core/standalone";
|
|
43
|
-
mountWave(document.getElementById("wave"), {
|
|
44
|
-
/* your exported config */
|
|
45
|
-
});
|
|
43
|
+
mountWave(document.getElementById("wave"), {/* your exported config */});
|
|
46
44
|
</script>
|
|
47
45
|
```
|
|
48
46
|
|
|
@@ -76,9 +74,7 @@ createWave(el, {
|
|
|
76
74
|
touch: false, // follow touch pointers too
|
|
77
75
|
bindings: [{ source: "scroll", target: "timeOffset", to: 40 }], // scrub the whole wave with scroll
|
|
78
76
|
},
|
|
79
|
-
waves: [
|
|
80
|
-
/* each wave carries its own `interaction` (hover / press / bindings) */
|
|
81
|
-
],
|
|
77
|
+
waves: [/* each wave carries its own `interaction` (hover / press / bindings) */],
|
|
82
78
|
});
|
|
83
79
|
```
|
|
84
80
|
|
package/dist/config/model.d.ts
CHANGED
|
@@ -53,11 +53,14 @@ declare function createLight(position?: Vec3, intensity?: number): LightConfig;
|
|
|
53
53
|
* rig always shows the light — even before one has been explicitly added. */
|
|
54
54
|
declare const DEFAULT_LIGHT_POSITION: Vec3;
|
|
55
55
|
/**
|
|
56
|
-
* A noise band: inside a rectangular uv region
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* colour suppresses them), and the end-weighting parabolaPower. Lets the fibers vary
|
|
56
|
+
* A noise band: inside a rectangular uv region, softened by `feather`, the fiber streaks
|
|
57
|
+
* are overridden — strength, frequency (density), colourAttenuation (how much the local
|
|
58
|
+
* colour suppresses them), and the edge-weighting parabolaPower. Lets the fibers vary
|
|
60
59
|
* per region instead of uniform.
|
|
60
|
+
*
|
|
61
|
+
* Mind the axes, which are the reverse of what the names suggest: the bounds gate on uv,
|
|
62
|
+
* and uv.x is the folded WIDTH while uv.y is the LENGTH (see WaveGeometry's UV AXES note).
|
|
63
|
+
* So startX..endX span the SHORT axis and startY..endY run end to end.
|
|
61
64
|
*/
|
|
62
65
|
interface NoiseBand {
|
|
63
66
|
startX: number;
|
|
@@ -72,7 +75,8 @@ interface NoiseBand {
|
|
|
72
75
|
}
|
|
73
76
|
/** A default band: a strong, coarse streak region over the first half. */
|
|
74
77
|
declare function createNoiseBand(): NoiseBand;
|
|
75
|
-
/** One gradient stop: a colour at a normalized position (0–1)
|
|
78
|
+
/** One gradient stop: a colour at a normalized position (0–1) along the gradient ramp, whose
|
|
79
|
+
* direction on the ribbon is set by `gradientType` / `gradientAngle`. */
|
|
76
80
|
interface ColorStop {
|
|
77
81
|
color: string;
|
|
78
82
|
pos: number;
|
|
@@ -133,8 +137,9 @@ interface WaveConfig {
|
|
|
133
137
|
/** Thin-film / holographic hue response that shifts with view angle (0 = off). */
|
|
134
138
|
iridescence: number;
|
|
135
139
|
edgeFade: number;
|
|
136
|
-
/** Softness of the ribbon's
|
|
137
|
-
* hardcoded value; smaller = razor-crisp graphic ribbons,
|
|
140
|
+
/** Softness of the ribbon's two ENDS — it smoothsteps on uv.y, which is the length, not the
|
|
141
|
+
* long edges. 0.1 = the original hardcoded value; smaller = razor-crisp graphic ribbons,
|
|
142
|
+
* larger = soft vapor. */
|
|
138
143
|
edgeFeather: number;
|
|
139
144
|
/** Depth tint (solid theme): fade far fragments toward depthTintColor for atmospheric
|
|
140
145
|
* separation in multi-wave stacks (0 = off). */
|
|
@@ -148,10 +153,44 @@ interface WaveConfig {
|
|
|
148
153
|
twistFrequency: Vec3;
|
|
149
154
|
twistPower: Vec3;
|
|
150
155
|
twistMotion?: boolean;
|
|
156
|
+
/** Helix: sweep the ribbon around its OWN length axis, `helixTurns` full turns from end to end.
|
|
157
|
+
* The three twists above can't express this — their axes sit 45°/90° off the length, and
|
|
158
|
+
* `expStep` is a monotone falloff, so nothing they do ever repeats. This angle is periodic in the
|
|
159
|
+
* length coordinate instead, which is what makes a helix (and a DNA ladder) reachable.
|
|
160
|
+
* 0 turns still leaves the block inert unless radius or roll is non-zero. */
|
|
161
|
+
helixTurns?: number;
|
|
162
|
+
/** Carries the WHOLE ribbon around the axis at this radius (world units, pre-scale), keeping its
|
|
163
|
+
* orientation. A narrow ribbon then reads as a single strand — two waves half a turn apart in
|
|
164
|
+
* `helixPhase` are the two strands of a double helix. 0 = off. */
|
|
165
|
+
helixRadius?: number;
|
|
166
|
+
/** Rolls the ribbon's own cross-section about the axis as it advances, as a fraction of
|
|
167
|
+
* `helixTurns` (1 = rolls exactly in step, a rigid twisted ribbon). That swings the ribbon's two
|
|
168
|
+
* long edges onto opposite sides of the axis, so ONE wave becomes a ladder whose edges are both
|
|
169
|
+
* strands — pair it with `rungAmount` for the rungs between them. 0 = off. */
|
|
170
|
+
helixRoll?: number;
|
|
171
|
+
/** Phase offset in degrees — where along the turn the ribbon starts. The per-wave knob that puts
|
|
172
|
+
* a second wave on the opposite side of the same helix (180). */
|
|
173
|
+
helixPhase?: number;
|
|
174
|
+
/** Radial fan (optional): sweep the ribbon's length into a plume/peacock spread from the local
|
|
175
|
+
* origin. The three twists and the helix can't reach it — this maps the ribbon to polar so the
|
|
176
|
+
* combed fibers ({@link fiberCount}) read as the individual radial strands. Placement is the wave's
|
|
177
|
+
* `position` transform (there is no separate pivot). Runs AFTER displace/helix/twist, behind
|
|
178
|
+
* `#ifdef RADIAL`, so `radialAmount` 0 leaves the block uncompiled and the wave byte-identical. */
|
|
179
|
+
radialAmount?: number;
|
|
180
|
+
radialArc?: number;
|
|
181
|
+
radialSpread?: number;
|
|
182
|
+
radialRadius?: number;
|
|
183
|
+
radialCenter?: number;
|
|
151
184
|
theme?: "solid" | "wireframe";
|
|
152
185
|
lineAmount?: number;
|
|
153
186
|
lineThickness?: number;
|
|
154
187
|
lineDerivativePower?: number;
|
|
188
|
+
/** Wireframe RUNGS: a second line family carved at constant uv.y, so these run ACROSS the ribbon
|
|
189
|
+
* where `lineAmount`'s run along it — the two cross into a ladder. Frequency, like `lineAmount`
|
|
190
|
+
* (rungs ≈ amount / π). 0 = off, and the cross-wise path isn't compiled. */
|
|
191
|
+
rungAmount?: number;
|
|
192
|
+
/** Rung line width, in pixels (screen-space, so it holds at any zoom). */
|
|
193
|
+
rungThickness?: number;
|
|
155
194
|
maxWidth?: number;
|
|
156
195
|
position: Vec3;
|
|
157
196
|
rotation: Vec3;
|
|
@@ -166,6 +205,9 @@ interface WaveConfig {
|
|
|
166
205
|
/** Optional per-wave interactivity: how THIS wave reacts to the shared pointer + inputs (hover
|
|
167
206
|
* field, click ripples, param bindings). ABSENT = this wave is inert / byte-identical. */
|
|
168
207
|
interaction?: WaveInteractionConfig;
|
|
208
|
+
/** Optional per-wave particle / dust field emitted off THIS wave's deformed surface / edge.
|
|
209
|
+
* ABSENT ⇒ off (no THREE.Points for this wave, byte-identical). See {@link ParticlesConfig}. */
|
|
210
|
+
particles?: ParticlesConfig;
|
|
169
211
|
}
|
|
170
212
|
/**
|
|
171
213
|
* An interaction INPUT: a normalized signal that can smoothly drive config params through an
|
|
@@ -174,7 +216,7 @@ interface WaveConfig {
|
|
|
174
216
|
type InteractionSource = "scroll" | "hover" | "pointerX" | "pointerY" | "pointerSpeed" | "press" | "scrollVelocity" | "appear" | `custom:${string}`;
|
|
175
217
|
/** Per-WAVE params a binding may drive. Single source of truth for WAVE_APPLIERS in
|
|
176
218
|
* renderer/interaction.ts (checked via `satisfies`) and validated by normalizeWaveInteraction. */
|
|
177
|
-
declare const WAVE_TARGET_NAMES: readonly ["displaceAmount", "detailAmount", "twistPowerX", "twistPowerY", "twistPowerZ", "twistFrequencyX", "twistFrequencyY", "twistFrequencyZ", "hueShift", "gradientShift", "colorSaturation", "opacity", "lineThickness", "lineAmount", "fiberStrength", "sheen", "iridescence", "positionX", "positionY"];
|
|
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"];
|
|
178
220
|
/** A per-wave param a {@link WaveInteractionBinding} can drive. */
|
|
179
221
|
type WaveInteractionTarget = (typeof WAVE_TARGET_NAMES)[number];
|
|
180
222
|
/** SCENE params a binding may drive (post / camera / time — shared, not per wave). Single source of
|
|
@@ -259,6 +301,46 @@ interface SceneInteractionConfig {
|
|
|
259
301
|
/** Input→param bindings driving SCENE params (timeOffset, cameraZoom, blur, grain). */
|
|
260
302
|
bindings?: SceneInteractionBinding[];
|
|
261
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* A WAVE's field of additive GPU sprites (dust / sparkle). Lives on {@link WaveConfig} — particles
|
|
306
|
+
* belong to a wave, not the scene. ABSENT ⇒ off: no THREE.Points node is created for that wave and its
|
|
307
|
+
* render is byte-identical; present ⇒ {@link normalizeParticles} clamps it. Every particle's motion is a
|
|
308
|
+
* pure function of `uTime` + a per-particle seed baked from `seed`, so it is deterministic (timeOffset
|
|
309
|
+
* scrub / loopSeconds / paused all hold). Particles spawn on the OWNING wave's DEFORMED surface / edge
|
|
310
|
+
* (via the shared waveShape) and drift outward from the wave centre.
|
|
311
|
+
*/
|
|
312
|
+
/** How each particle sprite is drawn (a per-field render style, not per-particle). */
|
|
313
|
+
type ParticleShape = "glitter" | "soft" | "ring" | "star" | "streak";
|
|
314
|
+
declare const PARTICLE_SHAPES: readonly ParticleShape[];
|
|
315
|
+
interface ParticlesConfig {
|
|
316
|
+
count: number;
|
|
317
|
+
size: number;
|
|
318
|
+
seed: number;
|
|
319
|
+
sizeJitter?: number;
|
|
320
|
+
color?: string;
|
|
321
|
+
/** Second sprite colour — particles interpolate between `color` and this by their seed (two-tone dust). */
|
|
322
|
+
color2?: string;
|
|
323
|
+
life?: number;
|
|
324
|
+
/** Motion-speed multiplier for the dust: how fast particles cycle + drift (1 = default, 0 = frozen).
|
|
325
|
+
* Independent of the wave's own `speed` (which animates the surface the dust rides). Under a seamless
|
|
326
|
+
* `loopSeconds` it snaps to a whole number of cycles so the loop stays seamless. */
|
|
327
|
+
speed?: number;
|
|
328
|
+
twinkle?: number;
|
|
329
|
+
/** Where on the wave particles spawn: 0 = across the whole SURFACE, 1 = the outer rim / EDGE only. */
|
|
330
|
+
edgeBias?: number;
|
|
331
|
+
/** How far particles drift outward from the wave centre as they age (world units). */
|
|
332
|
+
drift?: number;
|
|
333
|
+
/** −1..1 skew of the spawn along the edge width toward one flank (0 = even, −1 → one side, +1 → other). */
|
|
334
|
+
bias?: number;
|
|
335
|
+
/** Screen-vertical buoyancy over a lifetime, world units: + rises (embers), − falls (snow / ash). */
|
|
336
|
+
rise?: number;
|
|
337
|
+
/** Orbit around the wave centre in the screen plane, turns per lifetime (swirls the dust). */
|
|
338
|
+
swirl?: number;
|
|
339
|
+
/** Curl-noise turbulence: particles meander (fireflies / motes) instead of moving in straight lines. */
|
|
340
|
+
wander?: number;
|
|
341
|
+
/** Sprite render style. Default "glitter" (the soft round additive disc). */
|
|
342
|
+
shape?: ParticleShape;
|
|
343
|
+
}
|
|
262
344
|
/**
|
|
263
345
|
* Scene-level settings shared by every wave: output/background/camera/lights, the post-fx
|
|
264
346
|
* pass (grain/blur), playback, quality, and the whole-composition mirror. Everything that
|
|
@@ -395,10 +477,16 @@ declare function normalizeWaveInteraction(wave: WaveConfig): void;
|
|
|
395
477
|
/** Present-only normalizer for the SCENE interaction block: clamp the shared pointer inputs and drop
|
|
396
478
|
* invalid scene bindings. NEVER call when the block is absent. */
|
|
397
479
|
declare function normalizeSceneInteraction(config: StudioConfig): void;
|
|
480
|
+
/**
|
|
481
|
+
* Present-only normalizer for the scene PARTICLES block: clamp the numerics that are present and
|
|
482
|
+
* repair the required fields, leaving absent optionals absent (so the block stays lean). NEVER call
|
|
483
|
+
* when the block is absent — absence is off and byte-identical (ensureStudioConfig gates on presence).
|
|
484
|
+
*/
|
|
485
|
+
declare function normalizeParticles(wave: WaveConfig): void;
|
|
398
486
|
/** Normalize an ingested config to the wave model: backfill the scene + every wave, and drop in
|
|
399
487
|
* a default wave if none are present. Idempotent, so it is safe on the renderer's own config as
|
|
400
488
|
* well as freshly loaded save-states / share links. */
|
|
401
489
|
declare function ensureStudioConfig(input: StudioConfig): StudioConfig;
|
|
402
490
|
//#endregion
|
|
403
|
-
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, PaletteSource, SceneConfig, SceneInteractionBinding, SceneInteractionConfig, SceneInteractionTarget, StudioConfig, Vec2, Vec3, WaveConfig, WaveHoverConfig, WaveInteractionBinding, WaveInteractionConfig, WaveInteractionTarget, WavePressConfig, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, normalizeBackground, normalizeSceneInteraction, normalizeWave, normalizeWaveInteraction, resizeWaves };
|
|
491
|
+
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, Vec2, Vec3, WaveConfig, WaveHoverConfig, WaveInteractionBinding, WaveInteractionConfig, WaveInteractionTarget, WavePressConfig, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, normalizeBackground, normalizeParticles, normalizeSceneInteraction, normalizeWave, normalizeWaveInteraction, resizeWaves };
|
|
404
492
|
//# sourceMappingURL=model.d.ts.map
|
package/dist/config/model.js
CHANGED
|
@@ -119,6 +119,9 @@ const WAVE_TARGET_NAMES = [
|
|
|
119
119
|
"twistFrequencyX",
|
|
120
120
|
"twistFrequencyY",
|
|
121
121
|
"twistFrequencyZ",
|
|
122
|
+
"helixPhase",
|
|
123
|
+
"helixTurns",
|
|
124
|
+
"helixRadius",
|
|
122
125
|
"hueShift",
|
|
123
126
|
"gradientShift",
|
|
124
127
|
"colorSaturation",
|
|
@@ -139,6 +142,13 @@ const SCENE_TARGET_NAMES = [
|
|
|
139
142
|
"blur",
|
|
140
143
|
"grain"
|
|
141
144
|
];
|
|
145
|
+
const PARTICLE_SHAPES = [
|
|
146
|
+
"glitter",
|
|
147
|
+
"soft",
|
|
148
|
+
"ring",
|
|
149
|
+
"star",
|
|
150
|
+
"streak"
|
|
151
|
+
];
|
|
142
152
|
/** Spread a base wave into `count` overlapping waves — each with a slightly varied hue, width,
|
|
143
153
|
* speed, phase, vertical offset and roll so a stack reads as one composition. `count === 1`
|
|
144
154
|
* returns the base unchanged. Used to author multi-wave presets. */
|
|
@@ -263,10 +273,21 @@ function defaultWave() {
|
|
|
263
273
|
z: 6.33
|
|
264
274
|
},
|
|
265
275
|
twistMotion: false,
|
|
276
|
+
helixTurns: 0,
|
|
277
|
+
helixRadius: 0,
|
|
278
|
+
helixRoll: 0,
|
|
279
|
+
helixPhase: 0,
|
|
280
|
+
radialAmount: 0,
|
|
281
|
+
radialArc: 160,
|
|
282
|
+
radialSpread: 1,
|
|
283
|
+
radialRadius: 40,
|
|
284
|
+
radialCenter: 0,
|
|
266
285
|
theme: "solid",
|
|
267
286
|
lineAmount: 425,
|
|
268
287
|
lineThickness: 1,
|
|
269
288
|
lineDerivativePower: .95,
|
|
289
|
+
rungAmount: 0,
|
|
290
|
+
rungThickness: 1,
|
|
270
291
|
maxWidth: 1232,
|
|
271
292
|
position: {
|
|
272
293
|
x: -24.3,
|
|
@@ -377,7 +398,8 @@ function createDefaultConfig() {
|
|
|
377
398
|
* ColorStop[]; mesh points + texture transform are clamped). */
|
|
378
399
|
function normalizeWaveColour(config) {
|
|
379
400
|
const p = config.palette;
|
|
380
|
-
if (p
|
|
401
|
+
if (!Array.isArray(p) || p.length === 0) config.palette = defaultWave().palette;
|
|
402
|
+
else if (typeof p[0] === "string") config.palette = makeStops(p);
|
|
381
403
|
if (config.gradientType !== "radial" && config.gradientType !== "conic" && config.gradientType !== "mesh" && config.gradientType !== "linear") config.gradientType = "linear";
|
|
382
404
|
const rawMeshPoints = config.meshGradientPoints;
|
|
383
405
|
if (!Array.isArray(rawMeshPoints) || rawMeshPoints.length < 2) config.meshGradientPoints = createDefaultMeshPoints();
|
|
@@ -414,6 +436,8 @@ function normalizeWaveColour(config) {
|
|
|
414
436
|
}
|
|
415
437
|
/** Backfill background styling for states saved before gradient/image backgrounds existed. */
|
|
416
438
|
function normalizeBackground(config) {
|
|
439
|
+
if (typeof config.background !== "string") config.background = "#ffffff";
|
|
440
|
+
if (typeof config.transparentBackground !== "boolean") config.transparentBackground = true;
|
|
417
441
|
if (config.backgroundMode !== "gradient" && config.backgroundMode !== "image" && config.backgroundMode !== "color") config.backgroundMode = "color";
|
|
418
442
|
const palette = config.backgroundPalette;
|
|
419
443
|
if (!palette || palette.length < 2) config.backgroundPalette = makeStops([
|
|
@@ -428,11 +452,11 @@ function normalizeBackground(config) {
|
|
|
428
452
|
if (!Array.isArray(bgMesh) || bgMesh.length < 2) config.backgroundMeshPoints = createDefaultMeshPoints();
|
|
429
453
|
if (!Number.isFinite(config.backgroundMeshSoftness)) config.backgroundMeshSoftness = .62;
|
|
430
454
|
config.backgroundMeshSoftness = clamp01(config.backgroundMeshSoftness);
|
|
431
|
-
if (
|
|
455
|
+
if (!Number.isFinite(config.backgroundGradientAngle)) config.backgroundGradientAngle = 135;
|
|
432
456
|
if (typeof config.backgroundGradientSource !== "string") config.backgroundGradientSource = "stops";
|
|
433
457
|
if (typeof config.backgroundImageSource !== "string") config.backgroundImageSource = "vaporwave";
|
|
434
458
|
if (config.backgroundImageFit !== "contain" && config.backgroundImageFit !== "stretch" && config.backgroundImageFit !== "cover") config.backgroundImageFit = "cover";
|
|
435
|
-
if (
|
|
459
|
+
if (!Number.isFinite(config.backgroundImageZoom)) config.backgroundImageZoom = 1;
|
|
436
460
|
config.backgroundImageZoom = clamp(config.backgroundImageZoom, .1, 8);
|
|
437
461
|
if (!config.backgroundImagePosition) config.backgroundImagePosition = {
|
|
438
462
|
x: 0,
|
|
@@ -455,45 +479,46 @@ function ensureCamera(config) {
|
|
|
455
479
|
y: 0,
|
|
456
480
|
z: 0
|
|
457
481
|
};
|
|
458
|
-
if (
|
|
482
|
+
if (!Number.isFinite(config.cameraZoom)) config.cameraZoom = 1;
|
|
459
483
|
if (!CAMERA_FITS.includes(config.cameraFit)) config.cameraFit = "cover";
|
|
460
484
|
config.cameraMinVisibleWidth = typeof config.cameraMinVisibleWidth === "number" ? clamp(config.cameraMinVisibleWidth, 0, 1) : 0;
|
|
461
485
|
}
|
|
462
486
|
/** Backfill/repair a wave so the renderer can consume it (covers partial wave-model JSON). */
|
|
463
487
|
function normalizeWave(s) {
|
|
464
488
|
normalizeWaveColour(s);
|
|
465
|
-
if (
|
|
466
|
-
if (
|
|
489
|
+
if (!Number.isFinite(s.gradientAngle)) s.gradientAngle = 90;
|
|
490
|
+
if (!Number.isFinite(s.gradientShift)) s.gradientShift = .15;
|
|
467
491
|
if (typeof s.usePaletteTexture !== "boolean") s.usePaletteTexture = true;
|
|
468
492
|
if (typeof s.paletteSource !== "string") s.paletteSource = "hero";
|
|
469
493
|
if (typeof s.paletteEdgeColor !== "string") s.paletteEdgeColor = "#8e9dff";
|
|
470
|
-
if (
|
|
471
|
-
if (
|
|
472
|
-
if (
|
|
473
|
-
if (
|
|
474
|
-
if (
|
|
475
|
-
if (
|
|
476
|
-
if (
|
|
477
|
-
if (
|
|
494
|
+
if (!Number.isFinite(s.paletteEdgeAmount)) s.paletteEdgeAmount = .3;
|
|
495
|
+
if (!Number.isFinite(s.paletteDriftX)) s.paletteDriftX = 0;
|
|
496
|
+
if (!Number.isFinite(s.paletteDriftY)) s.paletteDriftY = 0;
|
|
497
|
+
if (!Number.isFinite(s.hueShift)) s.hueShift = 0;
|
|
498
|
+
if (!Number.isFinite(s.colorContrast)) s.colorContrast = 1;
|
|
499
|
+
if (!Number.isFinite(s.colorSaturation)) s.colorSaturation = 1;
|
|
500
|
+
if (!Number.isFinite(s.fiberCount)) s.fiberCount = 600;
|
|
501
|
+
if (!Number.isFinite(s.fiberStrength)) s.fiberStrength = .2;
|
|
478
502
|
if (!Array.isArray(s.noiseBands)) s.noiseBands = [];
|
|
479
|
-
|
|
480
|
-
if (
|
|
481
|
-
if (
|
|
482
|
-
if (
|
|
483
|
-
if (
|
|
484
|
-
if (
|
|
485
|
-
if (
|
|
486
|
-
if (
|
|
487
|
-
if (
|
|
488
|
-
if (
|
|
503
|
+
normalizeNoiseBands(s);
|
|
504
|
+
if (!Number.isFinite(s.texture)) s.texture = 0;
|
|
505
|
+
if (!Number.isFinite(s.creaseLight)) s.creaseLight = .6;
|
|
506
|
+
if (!Number.isFinite(s.creaseSharpness)) s.creaseSharpness = .589;
|
|
507
|
+
if (!Number.isFinite(s.creaseSoftness)) s.creaseSoftness = 1;
|
|
508
|
+
if (!Number.isFinite(s.sheen)) s.sheen = 0;
|
|
509
|
+
if (!Number.isFinite(s.roundness)) s.roundness = 0;
|
|
510
|
+
if (!Number.isFinite(s.iridescence)) s.iridescence = 0;
|
|
511
|
+
if (!Number.isFinite(s.edgeFade)) s.edgeFade = .04;
|
|
512
|
+
if (!Number.isFinite(s.edgeFeather)) s.edgeFeather = .1;
|
|
513
|
+
if (!Number.isFinite(s.depthTint)) s.depthTint = 0;
|
|
489
514
|
if (typeof s.depthTintColor !== "string") s.depthTintColor = "#0a2540";
|
|
490
515
|
if (!s.displaceFrequency) s.displaceFrequency = {
|
|
491
516
|
x: .003234,
|
|
492
517
|
y: .00799
|
|
493
518
|
};
|
|
494
|
-
if (
|
|
495
|
-
if (
|
|
496
|
-
if (
|
|
519
|
+
if (!Number.isFinite(s.displaceAmount)) s.displaceAmount = 6.051;
|
|
520
|
+
if (!Number.isFinite(s.detailFrequency)) s.detailFrequency = .04;
|
|
521
|
+
if (!Number.isFinite(s.detailAmount)) s.detailAmount = 0;
|
|
497
522
|
if (!s.twistFrequency) s.twistFrequency = {
|
|
498
523
|
x: -.055,
|
|
499
524
|
y: .077,
|
|
@@ -504,11 +529,23 @@ function normalizeWave(s) {
|
|
|
504
529
|
y: 5.85,
|
|
505
530
|
z: 6.33
|
|
506
531
|
};
|
|
532
|
+
if (typeof s.twistMotion !== "boolean") s.twistMotion = false;
|
|
533
|
+
if (!Number.isFinite(s.helixTurns)) s.helixTurns = 0;
|
|
534
|
+
if (!Number.isFinite(s.helixRadius)) s.helixRadius = 0;
|
|
535
|
+
if (!Number.isFinite(s.helixRoll)) s.helixRoll = 0;
|
|
536
|
+
if (!Number.isFinite(s.helixPhase)) s.helixPhase = 0;
|
|
537
|
+
if (!Number.isFinite(s.radialAmount)) s.radialAmount = 0;
|
|
538
|
+
if (!Number.isFinite(s.radialArc)) s.radialArc = 160;
|
|
539
|
+
if (!Number.isFinite(s.radialSpread)) s.radialSpread = 1;
|
|
540
|
+
if (!Number.isFinite(s.radialRadius)) s.radialRadius = 40;
|
|
541
|
+
if (!Number.isFinite(s.radialCenter)) s.radialCenter = 0;
|
|
507
542
|
if (typeof s.theme !== "string") s.theme = "solid";
|
|
508
|
-
if (
|
|
509
|
-
if (
|
|
510
|
-
if (
|
|
511
|
-
if (
|
|
543
|
+
if (!Number.isFinite(s.lineAmount)) s.lineAmount = 425;
|
|
544
|
+
if (!Number.isFinite(s.lineThickness)) s.lineThickness = 1;
|
|
545
|
+
if (!Number.isFinite(s.lineDerivativePower)) s.lineDerivativePower = .95;
|
|
546
|
+
if (!Number.isFinite(s.rungAmount)) s.rungAmount = 0;
|
|
547
|
+
if (!Number.isFinite(s.rungThickness)) s.rungThickness = 1;
|
|
548
|
+
if (!Number.isFinite(s.maxWidth)) s.maxWidth = 1232;
|
|
512
549
|
if (!s.position) s.position = {
|
|
513
550
|
x: 0,
|
|
514
551
|
y: 0,
|
|
@@ -525,44 +562,47 @@ function normalizeWave(s) {
|
|
|
525
562
|
z: 7
|
|
526
563
|
};
|
|
527
564
|
if (typeof s.blendMode !== "string") s.blendMode = "squared";
|
|
528
|
-
if (
|
|
529
|
-
if (
|
|
530
|
-
if (
|
|
565
|
+
if (!Number.isFinite(s.speed)) s.speed = .04;
|
|
566
|
+
if (!Number.isFinite(s.opacity)) s.opacity = 1;
|
|
567
|
+
if (!Number.isFinite(s.seed)) s.seed = 0;
|
|
531
568
|
if (s.interaction) normalizeWaveInteraction(s);
|
|
569
|
+
if (s.particles) normalizeParticles(s);
|
|
532
570
|
}
|
|
533
571
|
/** Backfill scene-level defaults (background/camera/post/lights/quality/mirror). */
|
|
534
572
|
function ensureSceneDefaults(config) {
|
|
535
573
|
normalizeBackground(config);
|
|
536
574
|
ensureCamera(config);
|
|
537
|
-
if (
|
|
575
|
+
if (!Number.isFinite(config.ambient)) config.ambient = .45;
|
|
538
576
|
if (!Array.isArray(config.lights)) config.lights = [];
|
|
539
|
-
|
|
540
|
-
if (
|
|
541
|
-
if (
|
|
542
|
-
if (
|
|
543
|
-
if (
|
|
544
|
-
if (
|
|
545
|
-
if (
|
|
546
|
-
if (
|
|
547
|
-
if (
|
|
548
|
-
if (
|
|
549
|
-
if (
|
|
550
|
-
if (
|
|
551
|
-
if (
|
|
552
|
-
if (
|
|
553
|
-
if (
|
|
554
|
-
if (
|
|
555
|
-
if (
|
|
556
|
-
if (
|
|
557
|
-
if (
|
|
558
|
-
if (
|
|
559
|
-
if (
|
|
560
|
-
if (
|
|
561
|
-
if (
|
|
562
|
-
if (
|
|
577
|
+
normalizeLights(config);
|
|
578
|
+
if (!Number.isFinite(config.quality)) config.quality = 1;
|
|
579
|
+
if (!Number.isFinite(config.dprMax)) config.dprMax = 2;
|
|
580
|
+
if (!Number.isFinite(config.grain)) config.grain = 1.1;
|
|
581
|
+
if (!Number.isFinite(config.blur)) config.blur = .02;
|
|
582
|
+
if (!Number.isFinite(config.blurSamples)) config.blurSamples = 6;
|
|
583
|
+
if (!Number.isFinite(config.bloomStrength)) config.bloomStrength = 0;
|
|
584
|
+
if (!Number.isFinite(config.bloomRadius)) config.bloomRadius = .4;
|
|
585
|
+
if (!Number.isFinite(config.bloomThreshold)) config.bloomThreshold = .85;
|
|
586
|
+
if (!Number.isFinite(config.dither)) config.dither = 0;
|
|
587
|
+
if (!Number.isFinite(config.ditherScale)) config.ditherScale = 2;
|
|
588
|
+
if (!Number.isFinite(config.ditherSteps)) config.ditherSteps = 4;
|
|
589
|
+
if (!Number.isFinite(config.innerLight)) config.innerLight = 0;
|
|
590
|
+
if (!Number.isFinite(config.innerLightDensity)) config.innerLightDensity = .5;
|
|
591
|
+
if (!Number.isFinite(config.innerLightDecay)) config.innerLightDecay = .95;
|
|
592
|
+
if (!Number.isFinite(config.innerLightX)) config.innerLightX = .5;
|
|
593
|
+
if (!Number.isFinite(config.innerLightY)) config.innerLightY = .15;
|
|
594
|
+
if (!Number.isFinite(config.halftone)) config.halftone = 0;
|
|
595
|
+
if (!Number.isFinite(config.halftoneCell)) config.halftoneCell = 6;
|
|
596
|
+
if (!Number.isFinite(config.halftoneAngle)) config.halftoneAngle = .4;
|
|
597
|
+
if (!Number.isFinite(config.heatmap)) config.heatmap = 0;
|
|
598
|
+
if (!Number.isFinite(config.paperTexture)) config.paperTexture = 0;
|
|
599
|
+
if (!Number.isFinite(config.paperTextureScale)) config.paperTextureScale = 2;
|
|
600
|
+
if (!Number.isFinite(config.halftoneCmyk)) config.halftoneCmyk = 0;
|
|
601
|
+
if (!Number.isFinite(config.halftoneCmykCell)) config.halftoneCmykCell = 6;
|
|
563
602
|
if (typeof config.showCameraRig !== "boolean") config.showCameraRig = false;
|
|
564
603
|
if (typeof config.paused !== "boolean") config.paused = false;
|
|
565
|
-
if (
|
|
604
|
+
if (!Number.isFinite(config.timeOffset)) config.timeOffset = 0;
|
|
605
|
+
if (!Number.isFinite(config.loopSeconds)) config.loopSeconds = 0;
|
|
566
606
|
if (typeof config.mirrorH !== "boolean") config.mirrorH = false;
|
|
567
607
|
if (typeof config.mirrorV !== "boolean") config.mirrorV = false;
|
|
568
608
|
}
|
|
@@ -571,6 +611,45 @@ function clampNumber(v, min, max, dflt) {
|
|
|
571
611
|
const n = Number(v);
|
|
572
612
|
return Number.isFinite(n) ? clamp(n, min, max) : dflt;
|
|
573
613
|
}
|
|
614
|
+
/** Coerce an untrusted field to a finite number, falling back to `dflt`. Deliberately does NOT
|
|
615
|
+
* clamp — it repairs broken values without reinterpreting out-of-range ones that a saved config
|
|
616
|
+
* may legitimately rely on. */
|
|
617
|
+
function num(v, dflt) {
|
|
618
|
+
const n = Number(v);
|
|
619
|
+
return Number.isFinite(n) ? n : dflt;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Repair the ELEMENTS of the scene's `lights` and a wave's `noiseBands`. Both arrays are
|
|
623
|
+
* type-checked where they're backfilled, but their entries never were — and the studio binds every
|
|
624
|
+
* field below directly, so one hand-authored `"lights": [{}]` was enough to make the whole control
|
|
625
|
+
* panel unbuildable. Entries that aren't objects at all are dropped.
|
|
626
|
+
*/
|
|
627
|
+
function normalizeLights(config) {
|
|
628
|
+
config.lights = config.lights.filter((l) => typeof l === "object" && l !== null);
|
|
629
|
+
for (const l of config.lights) {
|
|
630
|
+
if (typeof l.position !== "object" || l.position === null) l.position = { ...DEFAULT_LIGHT_POSITION };
|
|
631
|
+
l.position.x = num(l.position.x, DEFAULT_LIGHT_POSITION.x);
|
|
632
|
+
l.position.y = num(l.position.y, DEFAULT_LIGHT_POSITION.y);
|
|
633
|
+
l.position.z = num(l.position.z, DEFAULT_LIGHT_POSITION.z);
|
|
634
|
+
if (typeof l.color !== "string") l.color = "#ffffff";
|
|
635
|
+
l.intensity = num(l.intensity, 1);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function normalizeNoiseBands(s) {
|
|
639
|
+
s.noiseBands = s.noiseBands.filter((b) => typeof b === "object" && b !== null);
|
|
640
|
+
const d = createNoiseBand();
|
|
641
|
+
for (const b of s.noiseBands) {
|
|
642
|
+
b.startX = num(b.startX, d.startX);
|
|
643
|
+
b.endX = num(b.endX, d.endX);
|
|
644
|
+
b.startY = num(b.startY, d.startY);
|
|
645
|
+
b.endY = num(b.endY, d.endY);
|
|
646
|
+
b.feather = num(b.feather, d.feather);
|
|
647
|
+
b.strength = num(b.strength, d.strength);
|
|
648
|
+
b.frequency = num(b.frequency, d.frequency);
|
|
649
|
+
b.colorAttenuation = num(b.colorAttenuation, d.colorAttenuation);
|
|
650
|
+
b.parabolaPower = num(b.parabolaPower, d.parabolaPower);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
574
653
|
/** True for a valid interaction source string: a built-in name or a non-empty `custom:<name>`. */
|
|
575
654
|
function isInteractionSource(v) {
|
|
576
655
|
return typeof v === "string" && (INTERACTION_SOURCE_NAMES.includes(v) || v.startsWith("custom:") && v.length > 7);
|
|
@@ -632,6 +711,31 @@ function normalizeSceneInteraction(config) {
|
|
|
632
711
|
if (it.ribbonFlow !== void 0) it.ribbonFlow = clampNumber(it.ribbonFlow, 0, 1, .8);
|
|
633
712
|
if (it.bindings !== void 0) it.bindings = cleanBindings(it.bindings, SCENE_TARGET_NAMES);
|
|
634
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* Present-only normalizer for the scene PARTICLES block: clamp the numerics that are present and
|
|
716
|
+
* repair the required fields, leaving absent optionals absent (so the block stays lean). NEVER call
|
|
717
|
+
* when the block is absent — absence is off and byte-identical (ensureStudioConfig gates on presence).
|
|
718
|
+
*/
|
|
719
|
+
function normalizeParticles(wave) {
|
|
720
|
+
const p = wave.particles;
|
|
721
|
+
if (!p) return;
|
|
722
|
+
p.count = clampNumber(p.count, 0, 4e4, 0);
|
|
723
|
+
p.size = clampNumber(p.size, 0, 200, 2);
|
|
724
|
+
p.seed = num(p.seed, 0);
|
|
725
|
+
if (p.sizeJitter !== void 0) p.sizeJitter = clampNumber(p.sizeJitter, 0, 1, 0);
|
|
726
|
+
if (p.color !== void 0 && typeof p.color !== "string") p.color = "#ffcf8a";
|
|
727
|
+
if (p.life !== void 0) p.life = clampNumber(p.life, .1, 60, 6);
|
|
728
|
+
if (p.speed !== void 0) p.speed = clampNumber(p.speed, 0, 8, 1);
|
|
729
|
+
if (p.twinkle !== void 0) p.twinkle = clampNumber(p.twinkle, 0, 1, 0);
|
|
730
|
+
if (p.color2 !== void 0 && typeof p.color2 !== "string") p.color2 = "#ffcf8a";
|
|
731
|
+
if (p.edgeBias !== void 0) p.edgeBias = clampNumber(p.edgeBias, 0, 1, 1);
|
|
732
|
+
if (p.drift !== void 0) p.drift = num(p.drift, 0);
|
|
733
|
+
if (p.bias !== void 0) p.bias = clampNumber(p.bias, -1, 1, 0);
|
|
734
|
+
if (p.rise !== void 0) p.rise = num(p.rise, 0);
|
|
735
|
+
if (p.swirl !== void 0) p.swirl = num(p.swirl, 0);
|
|
736
|
+
if (p.wander !== void 0) p.wander = num(p.wander, 0);
|
|
737
|
+
if (p.shape !== void 0 && !PARTICLE_SHAPES.includes(p.shape)) p.shape = "glitter";
|
|
738
|
+
}
|
|
635
739
|
/** Normalize an ingested config to the wave model: backfill the scene + every wave, and drop in
|
|
636
740
|
* a default wave if none are present. Idempotent, so it is safe on the renderer's own config as
|
|
637
741
|
* well as freshly loaded save-states / share links. */
|
|
@@ -645,6 +749,6 @@ function ensureStudioConfig(input) {
|
|
|
645
749
|
return config;
|
|
646
750
|
}
|
|
647
751
|
//#endregion
|
|
648
|
-
export { CAMERA_FITS, DEFAULT_LIGHT_POSITION, MAX_COLORS, MAX_LIGHTS, MAX_MESH_POINTS, MAX_NOISE_BANDS, MAX_WAVES, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, normalizeBackground, normalizeSceneInteraction, normalizeWave, normalizeWaveInteraction, resizeWaves };
|
|
752
|
+
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 };
|
|
649
753
|
|
|
650
754
|
//# sourceMappingURL=model.js.map
|