@wave3d/core 0.5.0 → 0.6.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 +25 -1
- package/dist/config/model.js +121 -60
- package/dist/config/model.js.map +1 -1
- package/dist/core-loader.d.ts +0 -2
- package/dist/presets.d.ts +0 -1
- package/dist/presets.js +105 -0
- package/dist/presets.js.map +1 -1
- package/dist/renderer/WaveGeometry.d.ts +0 -1
- package/dist/renderer/WaveGeometry.js.map +1 -1
- package/dist/renderer/WaveRenderer.d.ts +2 -2
- package/dist/renderer/WaveRenderer.js +17 -2
- 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 +0 -1
- package/dist/renderer/shaders.js +46 -0
- package/dist/renderer/shaders.js.map +1 -1
- package/dist/shell/createWave.d.ts +0 -1
- package/dist/standalone/wave3d.standalone.js +449 -292
- package/dist/standalone.d.ts +0 -1
- package/dist/studio/StudioWaveRenderer.d.ts +0 -1
- package/dist/studio/randomize.d.ts +0 -1
- package/dist/studio/thumbnail.d.ts +0 -1
- package/dist/studio/thumbnail.js +5 -1
- package/dist/studio/thumbnail.js.map +1 -1
- package/package.json +5 -3
- 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
|
@@ -148,10 +148,34 @@ interface WaveConfig {
|
|
|
148
148
|
twistFrequency: Vec3;
|
|
149
149
|
twistPower: Vec3;
|
|
150
150
|
twistMotion?: boolean;
|
|
151
|
+
/** Helix: sweep the ribbon around its OWN length axis, `helixTurns` full turns from end to end.
|
|
152
|
+
* The three twists above can't express this — their axes sit 45°/90° off the length, and
|
|
153
|
+
* `expStep` is a monotone falloff, so nothing they do ever repeats. This angle is periodic in the
|
|
154
|
+
* length coordinate instead, which is what makes a helix (and a DNA ladder) reachable.
|
|
155
|
+
* 0 turns still leaves the block inert unless radius or roll is non-zero. */
|
|
156
|
+
helixTurns?: number;
|
|
157
|
+
/** Carries the WHOLE ribbon around the axis at this radius (world units, pre-scale), keeping its
|
|
158
|
+
* orientation. A narrow ribbon then reads as a single strand — two waves half a turn apart in
|
|
159
|
+
* `helixPhase` are the two strands of a double helix. 0 = off. */
|
|
160
|
+
helixRadius?: number;
|
|
161
|
+
/** Rolls the ribbon's own cross-section about the axis as it advances, as a fraction of
|
|
162
|
+
* `helixTurns` (1 = rolls exactly in step, a rigid twisted ribbon). That swings the ribbon's two
|
|
163
|
+
* long edges onto opposite sides of the axis, so ONE wave becomes a ladder whose edges are both
|
|
164
|
+
* strands — pair it with `rungAmount` for the rungs between them. 0 = off. */
|
|
165
|
+
helixRoll?: number;
|
|
166
|
+
/** Phase offset in degrees — where along the turn the ribbon starts. The per-wave knob that puts
|
|
167
|
+
* a second wave on the opposite side of the same helix (180). */
|
|
168
|
+
helixPhase?: number;
|
|
151
169
|
theme?: "solid" | "wireframe";
|
|
152
170
|
lineAmount?: number;
|
|
153
171
|
lineThickness?: number;
|
|
154
172
|
lineDerivativePower?: number;
|
|
173
|
+
/** Wireframe RUNGS: a second line family carved at constant uv.y, so these run ACROSS the ribbon
|
|
174
|
+
* where `lineAmount`'s run along it — the two cross into a ladder. Frequency, like `lineAmount`
|
|
175
|
+
* (rungs ≈ amount / π). 0 = off, and the cross-wise path isn't compiled. */
|
|
176
|
+
rungAmount?: number;
|
|
177
|
+
/** Rung line width, in pixels (screen-space, so it holds at any zoom). */
|
|
178
|
+
rungThickness?: number;
|
|
155
179
|
maxWidth?: number;
|
|
156
180
|
position: Vec3;
|
|
157
181
|
rotation: Vec3;
|
|
@@ -174,7 +198,7 @@ interface WaveConfig {
|
|
|
174
198
|
type InteractionSource = "scroll" | "hover" | "pointerX" | "pointerY" | "pointerSpeed" | "press" | "scrollVelocity" | "appear" | `custom:${string}`;
|
|
175
199
|
/** Per-WAVE params a binding may drive. Single source of truth for WAVE_APPLIERS in
|
|
176
200
|
* 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"];
|
|
201
|
+
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
202
|
/** A per-wave param a {@link WaveInteractionBinding} can drive. */
|
|
179
203
|
type WaveInteractionTarget = (typeof WAVE_TARGET_NAMES)[number];
|
|
180
204
|
/** SCENE params a binding may drive (post / camera / time — shared, not per wave). Single source of
|
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",
|
|
@@ -263,10 +266,16 @@ function defaultWave() {
|
|
|
263
266
|
z: 6.33
|
|
264
267
|
},
|
|
265
268
|
twistMotion: false,
|
|
269
|
+
helixTurns: 0,
|
|
270
|
+
helixRadius: 0,
|
|
271
|
+
helixRoll: 0,
|
|
272
|
+
helixPhase: 0,
|
|
266
273
|
theme: "solid",
|
|
267
274
|
lineAmount: 425,
|
|
268
275
|
lineThickness: 1,
|
|
269
276
|
lineDerivativePower: .95,
|
|
277
|
+
rungAmount: 0,
|
|
278
|
+
rungThickness: 1,
|
|
270
279
|
maxWidth: 1232,
|
|
271
280
|
position: {
|
|
272
281
|
x: -24.3,
|
|
@@ -377,7 +386,8 @@ function createDefaultConfig() {
|
|
|
377
386
|
* ColorStop[]; mesh points + texture transform are clamped). */
|
|
378
387
|
function normalizeWaveColour(config) {
|
|
379
388
|
const p = config.palette;
|
|
380
|
-
if (p
|
|
389
|
+
if (!Array.isArray(p) || p.length === 0) config.palette = defaultWave().palette;
|
|
390
|
+
else if (typeof p[0] === "string") config.palette = makeStops(p);
|
|
381
391
|
if (config.gradientType !== "radial" && config.gradientType !== "conic" && config.gradientType !== "mesh" && config.gradientType !== "linear") config.gradientType = "linear";
|
|
382
392
|
const rawMeshPoints = config.meshGradientPoints;
|
|
383
393
|
if (!Array.isArray(rawMeshPoints) || rawMeshPoints.length < 2) config.meshGradientPoints = createDefaultMeshPoints();
|
|
@@ -414,6 +424,8 @@ function normalizeWaveColour(config) {
|
|
|
414
424
|
}
|
|
415
425
|
/** Backfill background styling for states saved before gradient/image backgrounds existed. */
|
|
416
426
|
function normalizeBackground(config) {
|
|
427
|
+
if (typeof config.background !== "string") config.background = "#ffffff";
|
|
428
|
+
if (typeof config.transparentBackground !== "boolean") config.transparentBackground = true;
|
|
417
429
|
if (config.backgroundMode !== "gradient" && config.backgroundMode !== "image" && config.backgroundMode !== "color") config.backgroundMode = "color";
|
|
418
430
|
const palette = config.backgroundPalette;
|
|
419
431
|
if (!palette || palette.length < 2) config.backgroundPalette = makeStops([
|
|
@@ -428,11 +440,11 @@ function normalizeBackground(config) {
|
|
|
428
440
|
if (!Array.isArray(bgMesh) || bgMesh.length < 2) config.backgroundMeshPoints = createDefaultMeshPoints();
|
|
429
441
|
if (!Number.isFinite(config.backgroundMeshSoftness)) config.backgroundMeshSoftness = .62;
|
|
430
442
|
config.backgroundMeshSoftness = clamp01(config.backgroundMeshSoftness);
|
|
431
|
-
if (
|
|
443
|
+
if (!Number.isFinite(config.backgroundGradientAngle)) config.backgroundGradientAngle = 135;
|
|
432
444
|
if (typeof config.backgroundGradientSource !== "string") config.backgroundGradientSource = "stops";
|
|
433
445
|
if (typeof config.backgroundImageSource !== "string") config.backgroundImageSource = "vaporwave";
|
|
434
446
|
if (config.backgroundImageFit !== "contain" && config.backgroundImageFit !== "stretch" && config.backgroundImageFit !== "cover") config.backgroundImageFit = "cover";
|
|
435
|
-
if (
|
|
447
|
+
if (!Number.isFinite(config.backgroundImageZoom)) config.backgroundImageZoom = 1;
|
|
436
448
|
config.backgroundImageZoom = clamp(config.backgroundImageZoom, .1, 8);
|
|
437
449
|
if (!config.backgroundImagePosition) config.backgroundImagePosition = {
|
|
438
450
|
x: 0,
|
|
@@ -455,45 +467,46 @@ function ensureCamera(config) {
|
|
|
455
467
|
y: 0,
|
|
456
468
|
z: 0
|
|
457
469
|
};
|
|
458
|
-
if (
|
|
470
|
+
if (!Number.isFinite(config.cameraZoom)) config.cameraZoom = 1;
|
|
459
471
|
if (!CAMERA_FITS.includes(config.cameraFit)) config.cameraFit = "cover";
|
|
460
472
|
config.cameraMinVisibleWidth = typeof config.cameraMinVisibleWidth === "number" ? clamp(config.cameraMinVisibleWidth, 0, 1) : 0;
|
|
461
473
|
}
|
|
462
474
|
/** Backfill/repair a wave so the renderer can consume it (covers partial wave-model JSON). */
|
|
463
475
|
function normalizeWave(s) {
|
|
464
476
|
normalizeWaveColour(s);
|
|
465
|
-
if (
|
|
466
|
-
if (
|
|
477
|
+
if (!Number.isFinite(s.gradientAngle)) s.gradientAngle = 90;
|
|
478
|
+
if (!Number.isFinite(s.gradientShift)) s.gradientShift = .15;
|
|
467
479
|
if (typeof s.usePaletteTexture !== "boolean") s.usePaletteTexture = true;
|
|
468
480
|
if (typeof s.paletteSource !== "string") s.paletteSource = "hero";
|
|
469
481
|
if (typeof s.paletteEdgeColor !== "string") s.paletteEdgeColor = "#8e9dff";
|
|
470
|
-
if (
|
|
471
|
-
if (
|
|
472
|
-
if (
|
|
473
|
-
if (
|
|
474
|
-
if (
|
|
475
|
-
if (
|
|
476
|
-
if (
|
|
477
|
-
if (
|
|
482
|
+
if (!Number.isFinite(s.paletteEdgeAmount)) s.paletteEdgeAmount = .3;
|
|
483
|
+
if (!Number.isFinite(s.paletteDriftX)) s.paletteDriftX = 0;
|
|
484
|
+
if (!Number.isFinite(s.paletteDriftY)) s.paletteDriftY = 0;
|
|
485
|
+
if (!Number.isFinite(s.hueShift)) s.hueShift = 0;
|
|
486
|
+
if (!Number.isFinite(s.colorContrast)) s.colorContrast = 1;
|
|
487
|
+
if (!Number.isFinite(s.colorSaturation)) s.colorSaturation = 1;
|
|
488
|
+
if (!Number.isFinite(s.fiberCount)) s.fiberCount = 600;
|
|
489
|
+
if (!Number.isFinite(s.fiberStrength)) s.fiberStrength = .2;
|
|
478
490
|
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 (
|
|
491
|
+
normalizeNoiseBands(s);
|
|
492
|
+
if (!Number.isFinite(s.texture)) s.texture = 0;
|
|
493
|
+
if (!Number.isFinite(s.creaseLight)) s.creaseLight = .6;
|
|
494
|
+
if (!Number.isFinite(s.creaseSharpness)) s.creaseSharpness = .589;
|
|
495
|
+
if (!Number.isFinite(s.creaseSoftness)) s.creaseSoftness = 1;
|
|
496
|
+
if (!Number.isFinite(s.sheen)) s.sheen = 0;
|
|
497
|
+
if (!Number.isFinite(s.roundness)) s.roundness = 0;
|
|
498
|
+
if (!Number.isFinite(s.iridescence)) s.iridescence = 0;
|
|
499
|
+
if (!Number.isFinite(s.edgeFade)) s.edgeFade = .04;
|
|
500
|
+
if (!Number.isFinite(s.edgeFeather)) s.edgeFeather = .1;
|
|
501
|
+
if (!Number.isFinite(s.depthTint)) s.depthTint = 0;
|
|
489
502
|
if (typeof s.depthTintColor !== "string") s.depthTintColor = "#0a2540";
|
|
490
503
|
if (!s.displaceFrequency) s.displaceFrequency = {
|
|
491
504
|
x: .003234,
|
|
492
505
|
y: .00799
|
|
493
506
|
};
|
|
494
|
-
if (
|
|
495
|
-
if (
|
|
496
|
-
if (
|
|
507
|
+
if (!Number.isFinite(s.displaceAmount)) s.displaceAmount = 6.051;
|
|
508
|
+
if (!Number.isFinite(s.detailFrequency)) s.detailFrequency = .04;
|
|
509
|
+
if (!Number.isFinite(s.detailAmount)) s.detailAmount = 0;
|
|
497
510
|
if (!s.twistFrequency) s.twistFrequency = {
|
|
498
511
|
x: -.055,
|
|
499
512
|
y: .077,
|
|
@@ -504,11 +517,18 @@ function normalizeWave(s) {
|
|
|
504
517
|
y: 5.85,
|
|
505
518
|
z: 6.33
|
|
506
519
|
};
|
|
520
|
+
if (typeof s.twistMotion !== "boolean") s.twistMotion = false;
|
|
521
|
+
if (!Number.isFinite(s.helixTurns)) s.helixTurns = 0;
|
|
522
|
+
if (!Number.isFinite(s.helixRadius)) s.helixRadius = 0;
|
|
523
|
+
if (!Number.isFinite(s.helixRoll)) s.helixRoll = 0;
|
|
524
|
+
if (!Number.isFinite(s.helixPhase)) s.helixPhase = 0;
|
|
507
525
|
if (typeof s.theme !== "string") s.theme = "solid";
|
|
508
|
-
if (
|
|
509
|
-
if (
|
|
510
|
-
if (
|
|
511
|
-
if (
|
|
526
|
+
if (!Number.isFinite(s.lineAmount)) s.lineAmount = 425;
|
|
527
|
+
if (!Number.isFinite(s.lineThickness)) s.lineThickness = 1;
|
|
528
|
+
if (!Number.isFinite(s.lineDerivativePower)) s.lineDerivativePower = .95;
|
|
529
|
+
if (!Number.isFinite(s.rungAmount)) s.rungAmount = 0;
|
|
530
|
+
if (!Number.isFinite(s.rungThickness)) s.rungThickness = 1;
|
|
531
|
+
if (!Number.isFinite(s.maxWidth)) s.maxWidth = 1232;
|
|
512
532
|
if (!s.position) s.position = {
|
|
513
533
|
x: 0,
|
|
514
534
|
y: 0,
|
|
@@ -525,44 +545,46 @@ function normalizeWave(s) {
|
|
|
525
545
|
z: 7
|
|
526
546
|
};
|
|
527
547
|
if (typeof s.blendMode !== "string") s.blendMode = "squared";
|
|
528
|
-
if (
|
|
529
|
-
if (
|
|
530
|
-
if (
|
|
548
|
+
if (!Number.isFinite(s.speed)) s.speed = .04;
|
|
549
|
+
if (!Number.isFinite(s.opacity)) s.opacity = 1;
|
|
550
|
+
if (!Number.isFinite(s.seed)) s.seed = 0;
|
|
531
551
|
if (s.interaction) normalizeWaveInteraction(s);
|
|
532
552
|
}
|
|
533
553
|
/** Backfill scene-level defaults (background/camera/post/lights/quality/mirror). */
|
|
534
554
|
function ensureSceneDefaults(config) {
|
|
535
555
|
normalizeBackground(config);
|
|
536
556
|
ensureCamera(config);
|
|
537
|
-
if (
|
|
557
|
+
if (!Number.isFinite(config.ambient)) config.ambient = .45;
|
|
538
558
|
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 (
|
|
559
|
+
normalizeLights(config);
|
|
560
|
+
if (!Number.isFinite(config.quality)) config.quality = 1;
|
|
561
|
+
if (!Number.isFinite(config.dprMax)) config.dprMax = 2;
|
|
562
|
+
if (!Number.isFinite(config.grain)) config.grain = 1.1;
|
|
563
|
+
if (!Number.isFinite(config.blur)) config.blur = .02;
|
|
564
|
+
if (!Number.isFinite(config.blurSamples)) config.blurSamples = 6;
|
|
565
|
+
if (!Number.isFinite(config.bloomStrength)) config.bloomStrength = 0;
|
|
566
|
+
if (!Number.isFinite(config.bloomRadius)) config.bloomRadius = .4;
|
|
567
|
+
if (!Number.isFinite(config.bloomThreshold)) config.bloomThreshold = .85;
|
|
568
|
+
if (!Number.isFinite(config.dither)) config.dither = 0;
|
|
569
|
+
if (!Number.isFinite(config.ditherScale)) config.ditherScale = 2;
|
|
570
|
+
if (!Number.isFinite(config.ditherSteps)) config.ditherSteps = 4;
|
|
571
|
+
if (!Number.isFinite(config.innerLight)) config.innerLight = 0;
|
|
572
|
+
if (!Number.isFinite(config.innerLightDensity)) config.innerLightDensity = .5;
|
|
573
|
+
if (!Number.isFinite(config.innerLightDecay)) config.innerLightDecay = .95;
|
|
574
|
+
if (!Number.isFinite(config.innerLightX)) config.innerLightX = .5;
|
|
575
|
+
if (!Number.isFinite(config.innerLightY)) config.innerLightY = .15;
|
|
576
|
+
if (!Number.isFinite(config.halftone)) config.halftone = 0;
|
|
577
|
+
if (!Number.isFinite(config.halftoneCell)) config.halftoneCell = 6;
|
|
578
|
+
if (!Number.isFinite(config.halftoneAngle)) config.halftoneAngle = .4;
|
|
579
|
+
if (!Number.isFinite(config.heatmap)) config.heatmap = 0;
|
|
580
|
+
if (!Number.isFinite(config.paperTexture)) config.paperTexture = 0;
|
|
581
|
+
if (!Number.isFinite(config.paperTextureScale)) config.paperTextureScale = 2;
|
|
582
|
+
if (!Number.isFinite(config.halftoneCmyk)) config.halftoneCmyk = 0;
|
|
583
|
+
if (!Number.isFinite(config.halftoneCmykCell)) config.halftoneCmykCell = 6;
|
|
563
584
|
if (typeof config.showCameraRig !== "boolean") config.showCameraRig = false;
|
|
564
585
|
if (typeof config.paused !== "boolean") config.paused = false;
|
|
565
|
-
if (
|
|
586
|
+
if (!Number.isFinite(config.timeOffset)) config.timeOffset = 0;
|
|
587
|
+
if (!Number.isFinite(config.loopSeconds)) config.loopSeconds = 0;
|
|
566
588
|
if (typeof config.mirrorH !== "boolean") config.mirrorH = false;
|
|
567
589
|
if (typeof config.mirrorV !== "boolean") config.mirrorV = false;
|
|
568
590
|
}
|
|
@@ -571,6 +593,45 @@ function clampNumber(v, min, max, dflt) {
|
|
|
571
593
|
const n = Number(v);
|
|
572
594
|
return Number.isFinite(n) ? clamp(n, min, max) : dflt;
|
|
573
595
|
}
|
|
596
|
+
/** Coerce an untrusted field to a finite number, falling back to `dflt`. Deliberately does NOT
|
|
597
|
+
* clamp — it repairs broken values without reinterpreting out-of-range ones that a saved config
|
|
598
|
+
* may legitimately rely on. */
|
|
599
|
+
function num(v, dflt) {
|
|
600
|
+
const n = Number(v);
|
|
601
|
+
return Number.isFinite(n) ? n : dflt;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Repair the ELEMENTS of the scene's `lights` and a wave's `noiseBands`. Both arrays are
|
|
605
|
+
* type-checked where they're backfilled, but their entries never were — and the studio binds every
|
|
606
|
+
* field below directly, so one hand-authored `"lights": [{}]` was enough to make the whole control
|
|
607
|
+
* panel unbuildable. Entries that aren't objects at all are dropped.
|
|
608
|
+
*/
|
|
609
|
+
function normalizeLights(config) {
|
|
610
|
+
config.lights = config.lights.filter((l) => typeof l === "object" && l !== null);
|
|
611
|
+
for (const l of config.lights) {
|
|
612
|
+
if (typeof l.position !== "object" || l.position === null) l.position = { ...DEFAULT_LIGHT_POSITION };
|
|
613
|
+
l.position.x = num(l.position.x, DEFAULT_LIGHT_POSITION.x);
|
|
614
|
+
l.position.y = num(l.position.y, DEFAULT_LIGHT_POSITION.y);
|
|
615
|
+
l.position.z = num(l.position.z, DEFAULT_LIGHT_POSITION.z);
|
|
616
|
+
if (typeof l.color !== "string") l.color = "#ffffff";
|
|
617
|
+
l.intensity = num(l.intensity, 1);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function normalizeNoiseBands(s) {
|
|
621
|
+
s.noiseBands = s.noiseBands.filter((b) => typeof b === "object" && b !== null);
|
|
622
|
+
const d = createNoiseBand();
|
|
623
|
+
for (const b of s.noiseBands) {
|
|
624
|
+
b.startX = num(b.startX, d.startX);
|
|
625
|
+
b.endX = num(b.endX, d.endX);
|
|
626
|
+
b.startY = num(b.startY, d.startY);
|
|
627
|
+
b.endY = num(b.endY, d.endY);
|
|
628
|
+
b.feather = num(b.feather, d.feather);
|
|
629
|
+
b.strength = num(b.strength, d.strength);
|
|
630
|
+
b.frequency = num(b.frequency, d.frequency);
|
|
631
|
+
b.colorAttenuation = num(b.colorAttenuation, d.colorAttenuation);
|
|
632
|
+
b.parabolaPower = num(b.parabolaPower, d.parabolaPower);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
574
635
|
/** True for a valid interaction source string: a built-in name or a non-empty `custom:<name>`. */
|
|
575
636
|
function isInteractionSource(v) {
|
|
576
637
|
return typeof v === "string" && (INTERACTION_SOURCE_NAMES.includes(v) || v.startsWith("custom:") && v.length > 7);
|