@wave3d/core 0.2.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +47 -0
  2. package/dist/config/model.d.ts +138 -1
  3. package/dist/config/model.js +144 -1
  4. package/dist/config/model.js.map +1 -1
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.js +2 -2
  7. package/dist/presets.js +15 -0
  8. package/dist/presets.js.map +1 -1
  9. package/dist/renderer/WaveRenderer.d.ts +59 -0
  10. package/dist/renderer/WaveRenderer.js +422 -8
  11. package/dist/renderer/WaveRenderer.js.map +1 -1
  12. package/dist/renderer/interaction.d.ts +119 -0
  13. package/dist/renderer/interaction.js +474 -0
  14. package/dist/renderer/interaction.js.map +1 -0
  15. package/dist/renderer/palette.js +14 -0
  16. package/dist/renderer/palette.js.map +1 -1
  17. package/dist/renderer/shaders.js +313 -1
  18. package/dist/renderer/shaders.js.map +1 -1
  19. package/dist/shell/createWave.d.ts +9 -1
  20. package/dist/shell/createWave.js +7 -1
  21. package/dist/shell/createWave.js.map +1 -1
  22. package/dist/shell/poster.d.ts +12 -0
  23. package/dist/shell/poster.js +4 -3
  24. package/dist/shell/poster.js.map +1 -1
  25. package/dist/standalone/wave3d.standalone.js +1016 -223
  26. package/dist/standalone.d.ts +2 -2
  27. package/dist/standalone.js +2 -2
  28. package/dist/studio/StudioWaveRenderer.d.ts +15 -0
  29. package/dist/studio/StudioWaveRenderer.js +32 -1
  30. package/dist/studio/StudioWaveRenderer.js.map +1 -1
  31. package/dist/studio/index.d.ts +2 -2
  32. package/dist/studio/index.js +2 -2
  33. package/dist/studio/randomize.d.ts +7 -4
  34. package/dist/studio/randomize.js +52 -5
  35. package/dist/studio/randomize.js.map +1 -1
  36. package/package.json +1 -1
  37. package/skills/wave3d/SKILL.md +60 -6
@@ -1,7 +1,7 @@
1
1
  import { clamp, roundTo } from "../util/math.js";
2
2
  //#region src/studio/randomize.ts
3
3
  /**
4
- * The "Randomize" helpers: `randomizeConfig` ("Randomize All") plus the per-section randomizers
4
+ * The "Randomize" helpers: `randomizeConfig` ("Tasteful Randomize") plus the per-section randomizers
5
5
  * the studio wires to each folder's 🎲. Studio-facing (not part of the renderer core), exposed via
6
6
  * the `@wave3d/core/studio` entry.
7
7
  */
@@ -48,7 +48,7 @@ function rand(min, max) {
48
48
  function pick(arr) {
49
49
  return arr[Math.floor(Math.random() * arr.length)];
50
50
  }
51
- /** "Randomize All": keep the scene (background, camera, lights, quality) and randomize the
51
+ /** "Tasteful Randomize": keep the scene (background, camera, lights, quality) and randomize the
52
52
  * post-fx plus every wave independently — so a multi-wave stack becomes visibly varied.
53
53
  * The camera is deliberately left alone so the result always lands in view. */
54
54
  function randomizeConfig(base) {
@@ -59,7 +59,7 @@ function randomizeConfig(base) {
59
59
  return cfg;
60
60
  }
61
61
  /** Randomize a whole wave: colour, shape (displacement/twist), transform, finish + the
62
- * compositing knobs (speed/opacity/seed/blend). Used by each wave's 🎲 and by "Randomize All". */
62
+ * compositing knobs (speed/opacity/seed/blend). Used by each wave's 🎲 and by "Tasteful Randomize". */
63
63
  function randomizeWave(s) {
64
64
  randomizeGradient(s);
65
65
  randomizeColor(s);
@@ -150,7 +150,7 @@ function randomizeGradient(c) {
150
150
  c.paletteDriftY = 0;
151
151
  }
152
152
  /** "Background" folder: a fresh random gradient (linear/radial/conic) or mesh backdrop. Left out
153
- * of "Randomize All", which deliberately preserves the background. */
153
+ * of "Tasteful Randomize", which deliberately preserves the background. */
154
154
  function randomizeBackground(c) {
155
155
  const colors = pick(RANDOM_PALETTES);
156
156
  c.transparentBackground = false;
@@ -171,6 +171,53 @@ function randomizeBackground(c) {
171
171
  c.backgroundPalette = randomStops(colors);
172
172
  }
173
173
  }
174
+ /** "Post FX" folder: enable a single random effect (the rest off) with random shape params — one
175
+ * post effect reads cleaner than a stack, so this stays tasteful. */
176
+ function randomizePostFx(c) {
177
+ c.dither = 0;
178
+ c.halftone = 0;
179
+ c.halftoneCmyk = 0;
180
+ c.heatmap = 0;
181
+ c.paperTexture = 0;
182
+ c.innerLight = 0;
183
+ switch (pick([
184
+ "dither",
185
+ "halftone",
186
+ "cmyk",
187
+ "heatmap",
188
+ "paper",
189
+ "light"
190
+ ])) {
191
+ case "dither":
192
+ c.dither = r2(rand(.6, 1));
193
+ c.ditherScale = Math.round(rand(1, 5));
194
+ c.ditherSteps = Math.round(rand(2, 6));
195
+ break;
196
+ case "halftone":
197
+ c.halftone = r2(rand(.7, 1));
198
+ c.halftoneCell = r2(rand(4, 12));
199
+ c.halftoneAngle = r2(rand(0, 1.2));
200
+ break;
201
+ case "cmyk":
202
+ c.halftoneCmyk = r2(rand(.7, 1));
203
+ c.halftoneCmykCell = r2(rand(4, 10));
204
+ break;
205
+ case "heatmap":
206
+ c.heatmap = r2(rand(.6, 1));
207
+ break;
208
+ case "paper":
209
+ c.paperTexture = r2(rand(.4, .9));
210
+ c.paperTextureScale = r2(rand(1, 4));
211
+ break;
212
+ case "light":
213
+ c.innerLight = r2(rand(.4, .9));
214
+ c.innerLightDensity = r2(rand(.3, .8));
215
+ c.innerLightDecay = r3(rand(.9, .98));
216
+ c.innerLightX = r2(rand(.2, .8));
217
+ c.innerLightY = r2(rand(0, .4));
218
+ break;
219
+ }
220
+ }
174
221
  /** "Color" folder: hue / contrast / saturation grading. */
175
222
  function randomizeColor(c) {
176
223
  c.hueShift = Math.round(rand(0, 360));
@@ -259,6 +306,6 @@ function randomizeGlobal(c) {
259
306
  c.cameraZoom = r2(rand(1.2, 2.6));
260
307
  }
261
308
  //#endregion
262
- export { randomizeBackground, randomizeColor, randomizeConfig, randomizeFinish, randomizeGlobal, randomizeGradient, randomizeLights, randomizeSpine, randomizeTransform, randomizeTwist, randomizeWave };
309
+ export { randomizeBackground, randomizeColor, randomizeConfig, randomizeFinish, randomizeGlobal, randomizeGradient, randomizeLights, randomizePostFx, randomizeSpine, randomizeTransform, randomizeTwist, randomizeWave };
263
310
 
264
311
  //# sourceMappingURL=randomize.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"randomize.js","names":[],"sources":["../../src/studio/randomize.ts"],"sourcesContent":["/**\n * The \"Randomize\" helpers: `randomizeConfig` (\"Randomize All\") plus the per-section randomizers\n * the studio wires to each folder's 🎲. Studio-facing (not part of the renderer core), exposed via\n * the `@wave3d/core/studio` entry.\n */\nimport { clamp, roundTo } from \"../util/math\";\nimport type {\n StudioConfig,\n WaveConfig,\n MeshGradientPoint,\n ColorStop,\n GradientType,\n} from \"../config/model\";\n\nconst RANDOM_PALETTES: string[][] = [\n [\"#9bb0ff\", \"#ffb14e\", \"#ff6aa8\", \"#b15cff\", \"#6f7bff\"],\n [\"#ffd166\", \"#ff6b6b\", \"#c44dff\", \"#4d79ff\", \"#22d3ee\"],\n [\"#f97316\", \"#ec4899\", \"#8b5cf6\", \"#3b82f6\", \"#06b6d4\"],\n [\"#00f5d4\", \"#00bbf9\", \"#9b5de5\", \"#f15bb5\", \"#fee440\"],\n [\"#7c3aed\", \"#db2777\", \"#f59e0b\", \"#10b981\", \"#06b6d4\"],\n];\n\nfunction rand(min: number, max: number): number {\n return min + Math.random() * (max - min);\n}\n\nfunction pick<T>(arr: T[]): T {\n return arr[Math.floor(Math.random() * arr.length)];\n}\n\n/** \"Randomize All\": keep the scene (background, camera, lights, quality) and randomize the\n * post-fx plus every wave independently — so a multi-wave stack becomes visibly varied.\n * The camera is deliberately left alone so the result always lands in view. */\nexport function randomizeConfig(base: StudioConfig): StudioConfig {\n const cfg = structuredClone(base);\n cfg.grain = r2(rand(0, 1.5));\n cfg.blur = r3(rand(0, 0.02));\n for (const s of cfg.waves) randomizeWave(s);\n return cfg;\n}\n\n/** Randomize a whole wave: colour, shape (displacement/twist), transform, finish + the\n * compositing knobs (speed/opacity/seed/blend). Used by each wave's 🎲 and by \"Randomize All\". */\nexport function randomizeWave(s: WaveConfig): void {\n randomizeGradient(s);\n randomizeColor(s);\n randomizeSpine(s);\n randomizeTransform(s);\n randomizeTwist(s);\n randomizeFinish(s);\n s.speed = r2(rand(0.02, 0.15));\n s.opacity = r2(rand(0.6, 1));\n s.seed = r2(rand(0, 12));\n // Bias to the vivid squared blend; occasionally normal/additive (skip multiply — it muddies\n // the wave on light/transparent backgrounds).\n s.blendMode = pick([\"squared\", \"squared\", \"normal\", \"additive\"]);\n}\n\n// ---- Per-section randomizers (each mutates only its own fields, in place) ----\n\nfunction r2(x: number): number {\n return roundTo(x, 2);\n}\nfunction r3(x: number): number {\n return roundTo(x, 3);\n}\n\nconst LIGHT_TINTS = [\"#ffffff\", \"#ffffff\", \"#fff0e0\", \"#e2e8ff\", \"#ffe2f0\", \"#e2fff4\", \"#fff6cc\"];\n\n/** Random sorted positions in [0,1] with the ends pinned to 0 and 1. */\nfunction randomSortedPositions(n: number): number[] {\n if (n <= 1) return [0];\n if (n === 2) return [0, 1];\n const inner = Array.from({ length: n - 2 }, () => r2(rand(0.06, 0.94))).sort((a, b) => a - b);\n return [0, ...inner, 1];\n}\n\nfunction randomMeshPoints(colors: string[]): MeshGradientPoint[] {\n return colors.map((color) => ({\n color,\n x: r2(rand(0.08, 0.92)),\n y: r2(rand(0.08, 0.92)),\n influence: r2(rand(0.5, 0.95)),\n }));\n}\n\nfunction randomStops(colors: string[]): ColorStop[] {\n const positions = randomSortedPositions(colors.length);\n return colors.map((color, i) => ({ color, pos: positions[i] }));\n}\n\nexport function randomizeGradient(c: WaveConfig): void {\n const colors = pick(RANDOM_PALETTES);\n const count = clamp(Math.round(rand(3, colors.length)), 3, colors.length);\n const chosen = colors.slice(0, count);\n c.palette = randomStops(chosen);\n // Bias toward linear, occasionally radial/conic/mesh for variety.\n c.gradientType = pick([\n \"linear\",\n \"linear\",\n \"linear\",\n \"radial\",\n \"conic\",\n \"mesh\",\n ] as GradientType[]);\n c.gradientAngle = Math.round(rand(0, 180));\n c.gradientShift = r2(rand(0, 0.4)); // 2D warp\n // Also refresh the mesh field + edge tint so the whole colour section changes regardless of the\n // active source (both are inert unless gradientType is \"mesh\" / the source is \"stops\").\n c.meshGradientPoints = randomMeshPoints(colors);\n c.meshGradientSoftness = r2(rand(0.45, 0.85));\n c.paletteEdgeColor = pick(chosen);\n c.paletteEdgeAmount = r2(rand(0, 0.5));\n // Colour engine: mostly the editable stops (as a 2D texture or procedurally), occasionally the\n // baked hero LUT. Clear any loaded custom image/video so the picked source actually shows.\n c.paletteImageUrl = undefined;\n c.paletteVideoUrl = undefined;\n const engine = pick([\"stops-tex\", \"stops-tex\", \"procedural\", \"hero\"]);\n c.usePaletteTexture = engine !== \"procedural\";\n c.paletteSource = engine === \"hero\" ? \"hero\" : \"stops\";\n // Occasional slow colour flow along the ribbon (inert on mesh / procedural palettes).\n c.paletteDriftX = rand(0, 1) < 0.25 ? r2(rand(-0.25, 0.25)) : 0;\n c.paletteDriftY = 0;\n}\n\n/** \"Background\" folder: a fresh random gradient (linear/radial/conic) or mesh backdrop. Left out\n * of \"Randomize All\", which deliberately preserves the background. */\nexport function randomizeBackground(c: StudioConfig): void {\n const colors = pick(RANDOM_PALETTES);\n c.transparentBackground = false;\n c.backgroundMode = \"gradient\";\n c.backgroundGradientType = pick([\"linear\", \"radial\", \"conic\", \"mesh\"] as GradientType[]);\n c.backgroundGradientAngle = Math.round(rand(0, 360));\n c.background = colors[0]; // matte fallback (shown only if a gradient ever fails to cover)\n if (c.backgroundGradientType === \"mesh\") {\n c.backgroundMeshPoints = randomMeshPoints(colors);\n c.backgroundMeshSoftness = r2(rand(0.45, 0.85));\n } else {\n c.backgroundGradientSource = \"stops\";\n c.backgroundPalette = randomStops(colors);\n }\n}\n\n/** \"Color\" folder: hue / contrast / saturation grading. */\nexport function randomizeColor(c: WaveConfig): void {\n c.hueShift = Math.round(rand(0, 360));\n c.colorContrast = r2(rand(0.9, 1.3));\n c.colorSaturation = r2(rand(0.8, 1.35));\n}\n\nexport function randomizeSpine(c: WaveConfig): void {\n // Wider frequency spread than before so a re-roll is actually visible (the old range barely\n // moved). Amount takes either sign so the ribbon folds either way.\n c.displaceFrequency = { x: r3(rand(0.002, 0.016)), y: r3(rand(0.004, 0.02)) };\n c.displaceAmount = r2(rand(3, 10)) * pick([1, -1]);\n // Occasionally layer a finer second octave for a richer, two-scale shape.\n if (rand(0, 1) < 0.35) {\n c.detailFrequency = r3(rand(0.03, 0.08));\n c.detailAmount = r2(rand(0.8, 2.5)) * pick([1, -1]);\n } else {\n c.detailAmount = 0;\n }\n}\n\nexport function randomizeTransform(c: WaveConfig): void {\n c.rotation = { x: r2(rand(-20, 5)), y: r2(rand(-25, 10)), z: r2(rand(-170, -150)) };\n // Full scale (×10) — the mesh lives in the tens, not fractions.\n const s = r2(rand(6, 14));\n c.scale = { x: s, y: s, z: r2(s * rand(0.6, 0.8)) };\n // z is modest — the ortho camera barely shows depth, and a big z can clip the near/far planes.\n c.position = { x: r2(rand(-60, 60)), y: r2(rand(-60, 60)), z: r2(rand(-20, 20)) };\n}\n\nexport function randomizeTwist(c: WaveConfig): void {\n c.twistFrequency = { x: r3(rand(-0.5, 0.5)), y: r3(rand(-0.3, 0.5)), z: r3(rand(-1.6, -0.6)) };\n c.twistPower = { x: r2(rand(2, 6)), y: r2(rand(2, 6)), z: r2(rand(2, 7)) };\n c.twistMotion = rand(0, 1) < 0.25; // occasionally enable the breathing wobble\n}\n\n/** A wave's surface finish: fibers/texture + roundness/crease/edge. Grain & blur are scene post-fx\n * (see randomizeGlobal), so they're not touched here. */\nexport function randomizeFinish(c: WaveConfig): void {\n c.fiberCount = Math.round(rand(200, 900));\n c.fiberStrength = r2(rand(0.1, 0.35));\n c.texture = r2(rand(0, 0.35));\n c.roundness = r2(rand(0.3, 0.8)); // rounded-solid shading\n c.sheen = r2(rand(0.2, 0.9));\n c.iridescence = rand(0, 1) < 0.35 ? r2(rand(0.15, 0.6)) : 0; // occasional thin-film sheen\n c.creaseLight = r2(rand(0.4, 1.0)); // crease strength (where streaks appear)\n c.creaseSharpness = r2(rand(0.45, 0.8));\n c.creaseSoftness = r2(rand(0.8, 1.2));\n c.edgeFade = r2(rand(0, 0.08));\n c.edgeFeather = r2(rand(0.03, 0.22)); // crisp graphic ↔ soft vapor edges\n c.depthTint = rand(0, 1) < 0.3 ? r2(rand(0.2, 0.6)) : 0; // occasional atmospheric depth fade\n // Wireframe line params (inert unless theme is \"wireframe\") — randomized too so a wireframe\n // wave's Finish 🎲 refreshes its whole look, not just the solid-shader knobs.\n c.lineAmount = Math.round(rand(200, 900));\n c.lineThickness = r2(rand(0.5, 2));\n c.lineDerivativePower = r2(rand(0.4, 1.2));\n c.maxWidth = Math.round(rand(400, 1600));\n c.theme = rand(0, 1) < 0.2 ? \"wireframe\" : \"solid\"; // occasionally flip the material\n}\n\nexport function randomizeLights(c: StudioConfig): void {\n c.ambient = r2(rand(0.25, 0.6));\n for (const l of c.lights) {\n l.position = { x: r2(rand(-1000, 1000)), y: r2(rand(-500, 1000)), z: r2(rand(-500, 1200)) };\n l.intensity = r2(rand(0.5, 1.6));\n l.color = pick(LIGHT_TINTS);\n }\n}\n\n/** Scene knobs: the post-fx (grain/blur) + camera framing (zoom). */\nexport function randomizeGlobal(c: StudioConfig): void {\n c.grain = r2(rand(0, 1.5));\n c.blur = r3(rand(0, 0.02));\n // Ortho: vary the zoom (framing), keep the camera pose/target — distance doesn't size it.\n c.cameraZoom = r2(rand(1.2, 2.6));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"randomize.js","names":[],"sources":["../../src/studio/randomize.ts"],"sourcesContent":["/**\n * The \"Randomize\" helpers: `randomizeConfig` (\"Tasteful Randomize\") plus the per-section randomizers\n * the studio wires to each folder's 🎲. Studio-facing (not part of the renderer core), exposed via\n * the `@wave3d/core/studio` entry.\n */\nimport { clamp, roundTo } from \"../util/math\";\nimport type {\n StudioConfig,\n WaveConfig,\n MeshGradientPoint,\n ColorStop,\n GradientType,\n} from \"../config/model\";\n\nconst RANDOM_PALETTES: string[][] = [\n [\"#9bb0ff\", \"#ffb14e\", \"#ff6aa8\", \"#b15cff\", \"#6f7bff\"],\n [\"#ffd166\", \"#ff6b6b\", \"#c44dff\", \"#4d79ff\", \"#22d3ee\"],\n [\"#f97316\", \"#ec4899\", \"#8b5cf6\", \"#3b82f6\", \"#06b6d4\"],\n [\"#00f5d4\", \"#00bbf9\", \"#9b5de5\", \"#f15bb5\", \"#fee440\"],\n [\"#7c3aed\", \"#db2777\", \"#f59e0b\", \"#10b981\", \"#06b6d4\"],\n];\n\nfunction rand(min: number, max: number): number {\n return min + Math.random() * (max - min);\n}\n\nfunction pick<T>(arr: T[]): T {\n return arr[Math.floor(Math.random() * arr.length)];\n}\n\n/** \"Tasteful Randomize\": keep the scene (background, camera, lights, quality) and randomize the\n * post-fx plus every wave independently — so a multi-wave stack becomes visibly varied.\n * The camera is deliberately left alone so the result always lands in view. */\nexport function randomizeConfig(base: StudioConfig): StudioConfig {\n const cfg = structuredClone(base);\n cfg.grain = r2(rand(0, 1.5));\n cfg.blur = r3(rand(0, 0.02));\n for (const s of cfg.waves) randomizeWave(s);\n return cfg;\n}\n\n/** Randomize a whole wave: colour, shape (displacement/twist), transform, finish + the\n * compositing knobs (speed/opacity/seed/blend). Used by each wave's 🎲 and by \"Tasteful Randomize\". */\nexport function randomizeWave(s: WaveConfig): void {\n randomizeGradient(s);\n randomizeColor(s);\n randomizeSpine(s);\n randomizeTransform(s);\n randomizeTwist(s);\n randomizeFinish(s);\n s.speed = r2(rand(0.02, 0.15));\n s.opacity = r2(rand(0.6, 1));\n s.seed = r2(rand(0, 12));\n // Bias to the vivid squared blend; occasionally normal/additive (skip multiply — it muddies\n // the wave on light/transparent backgrounds).\n s.blendMode = pick([\"squared\", \"squared\", \"normal\", \"additive\"]);\n}\n\n// ---- Per-section randomizers (each mutates only its own fields, in place) ----\n\nfunction r2(x: number): number {\n return roundTo(x, 2);\n}\nfunction r3(x: number): number {\n return roundTo(x, 3);\n}\n\nconst LIGHT_TINTS = [\"#ffffff\", \"#ffffff\", \"#fff0e0\", \"#e2e8ff\", \"#ffe2f0\", \"#e2fff4\", \"#fff6cc\"];\n\n/** Random sorted positions in [0,1] with the ends pinned to 0 and 1. */\nfunction randomSortedPositions(n: number): number[] {\n if (n <= 1) return [0];\n if (n === 2) return [0, 1];\n const inner = Array.from({ length: n - 2 }, () => r2(rand(0.06, 0.94))).sort((a, b) => a - b);\n return [0, ...inner, 1];\n}\n\nfunction randomMeshPoints(colors: string[]): MeshGradientPoint[] {\n return colors.map((color) => ({\n color,\n x: r2(rand(0.08, 0.92)),\n y: r2(rand(0.08, 0.92)),\n influence: r2(rand(0.5, 0.95)),\n }));\n}\n\nfunction randomStops(colors: string[]): ColorStop[] {\n const positions = randomSortedPositions(colors.length);\n return colors.map((color, i) => ({ color, pos: positions[i] }));\n}\n\nexport function randomizeGradient(c: WaveConfig): void {\n const colors = pick(RANDOM_PALETTES);\n const count = clamp(Math.round(rand(3, colors.length)), 3, colors.length);\n const chosen = colors.slice(0, count);\n c.palette = randomStops(chosen);\n // Bias toward linear, occasionally radial/conic/mesh for variety.\n c.gradientType = pick([\n \"linear\",\n \"linear\",\n \"linear\",\n \"radial\",\n \"conic\",\n \"mesh\",\n ] as GradientType[]);\n c.gradientAngle = Math.round(rand(0, 180));\n c.gradientShift = r2(rand(0, 0.4)); // 2D warp\n // Also refresh the mesh field + edge tint so the whole colour section changes regardless of the\n // active source (both are inert unless gradientType is \"mesh\" / the source is \"stops\").\n c.meshGradientPoints = randomMeshPoints(colors);\n c.meshGradientSoftness = r2(rand(0.45, 0.85));\n c.paletteEdgeColor = pick(chosen);\n c.paletteEdgeAmount = r2(rand(0, 0.5));\n // Colour engine: mostly the editable stops (as a 2D texture or procedurally), occasionally the\n // baked hero LUT. Clear any loaded custom image/video so the picked source actually shows.\n c.paletteImageUrl = undefined;\n c.paletteVideoUrl = undefined;\n const engine = pick([\"stops-tex\", \"stops-tex\", \"procedural\", \"hero\"]);\n c.usePaletteTexture = engine !== \"procedural\";\n c.paletteSource = engine === \"hero\" ? \"hero\" : \"stops\";\n // Occasional slow colour flow along the ribbon (inert on mesh / procedural palettes).\n c.paletteDriftX = rand(0, 1) < 0.25 ? r2(rand(-0.25, 0.25)) : 0;\n c.paletteDriftY = 0;\n}\n\n/** \"Background\" folder: a fresh random gradient (linear/radial/conic) or mesh backdrop. Left out\n * of \"Tasteful Randomize\", which deliberately preserves the background. */\nexport function randomizeBackground(c: StudioConfig): void {\n const colors = pick(RANDOM_PALETTES);\n c.transparentBackground = false;\n c.backgroundMode = \"gradient\";\n c.backgroundGradientType = pick([\"linear\", \"radial\", \"conic\", \"mesh\"] as GradientType[]);\n c.backgroundGradientAngle = Math.round(rand(0, 360));\n c.background = colors[0]; // matte fallback (shown only if a gradient ever fails to cover)\n if (c.backgroundGradientType === \"mesh\") {\n c.backgroundMeshPoints = randomMeshPoints(colors);\n c.backgroundMeshSoftness = r2(rand(0.45, 0.85));\n } else {\n c.backgroundGradientSource = \"stops\";\n c.backgroundPalette = randomStops(colors);\n }\n}\n\n/** \"Post FX\" folder: enable a single random effect (the rest off) with random shape params — one\n * post effect reads cleaner than a stack, so this stays tasteful. */\nexport function randomizePostFx(c: StudioConfig): void {\n c.dither = 0;\n c.halftone = 0;\n c.halftoneCmyk = 0;\n c.heatmap = 0;\n c.paperTexture = 0;\n c.innerLight = 0;\n switch (pick([\"dither\", \"halftone\", \"cmyk\", \"heatmap\", \"paper\", \"light\"])) {\n case \"dither\":\n c.dither = r2(rand(0.6, 1));\n c.ditherScale = Math.round(rand(1, 5));\n c.ditherSteps = Math.round(rand(2, 6));\n break;\n case \"halftone\":\n c.halftone = r2(rand(0.7, 1));\n c.halftoneCell = r2(rand(4, 12));\n c.halftoneAngle = r2(rand(0, 1.2));\n break;\n case \"cmyk\":\n c.halftoneCmyk = r2(rand(0.7, 1));\n c.halftoneCmykCell = r2(rand(4, 10));\n break;\n case \"heatmap\":\n c.heatmap = r2(rand(0.6, 1));\n break;\n case \"paper\":\n c.paperTexture = r2(rand(0.4, 0.9));\n c.paperTextureScale = r2(rand(1, 4));\n break;\n case \"light\":\n c.innerLight = r2(rand(0.4, 0.9));\n c.innerLightDensity = r2(rand(0.3, 0.8));\n c.innerLightDecay = r3(rand(0.9, 0.98));\n c.innerLightX = r2(rand(0.2, 0.8));\n c.innerLightY = r2(rand(0, 0.4));\n break;\n }\n}\n\n/** \"Color\" folder: hue / contrast / saturation grading. */\nexport function randomizeColor(c: WaveConfig): void {\n c.hueShift = Math.round(rand(0, 360));\n c.colorContrast = r2(rand(0.9, 1.3));\n c.colorSaturation = r2(rand(0.8, 1.35));\n}\n\nexport function randomizeSpine(c: WaveConfig): void {\n // Wider frequency spread than before so a re-roll is actually visible (the old range barely\n // moved). Amount takes either sign so the ribbon folds either way.\n c.displaceFrequency = { x: r3(rand(0.002, 0.016)), y: r3(rand(0.004, 0.02)) };\n c.displaceAmount = r2(rand(3, 10)) * pick([1, -1]);\n // Occasionally layer a finer second octave for a richer, two-scale shape.\n if (rand(0, 1) < 0.35) {\n c.detailFrequency = r3(rand(0.03, 0.08));\n c.detailAmount = r2(rand(0.8, 2.5)) * pick([1, -1]);\n } else {\n c.detailAmount = 0;\n }\n}\n\nexport function randomizeTransform(c: WaveConfig): void {\n c.rotation = { x: r2(rand(-20, 5)), y: r2(rand(-25, 10)), z: r2(rand(-170, -150)) };\n // Full scale (×10) — the mesh lives in the tens, not fractions.\n const s = r2(rand(6, 14));\n c.scale = { x: s, y: s, z: r2(s * rand(0.6, 0.8)) };\n // z is modest — the ortho camera barely shows depth, and a big z can clip the near/far planes.\n c.position = { x: r2(rand(-60, 60)), y: r2(rand(-60, 60)), z: r2(rand(-20, 20)) };\n}\n\nexport function randomizeTwist(c: WaveConfig): void {\n c.twistFrequency = { x: r3(rand(-0.5, 0.5)), y: r3(rand(-0.3, 0.5)), z: r3(rand(-1.6, -0.6)) };\n c.twistPower = { x: r2(rand(2, 6)), y: r2(rand(2, 6)), z: r2(rand(2, 7)) };\n c.twistMotion = rand(0, 1) < 0.25; // occasionally enable the breathing wobble\n}\n\n/** A wave's surface finish: fibers/texture + roundness/crease/edge. Grain & blur are scene post-fx\n * (see randomizeGlobal), so they're not touched here. */\nexport function randomizeFinish(c: WaveConfig): void {\n c.fiberCount = Math.round(rand(200, 900));\n c.fiberStrength = r2(rand(0.1, 0.35));\n c.texture = r2(rand(0, 0.35));\n c.roundness = r2(rand(0.3, 0.8)); // rounded-solid shading\n c.sheen = r2(rand(0.2, 0.9));\n c.iridescence = rand(0, 1) < 0.35 ? r2(rand(0.15, 0.6)) : 0; // occasional thin-film sheen\n c.creaseLight = r2(rand(0.4, 1.0)); // crease strength (where streaks appear)\n c.creaseSharpness = r2(rand(0.45, 0.8));\n c.creaseSoftness = r2(rand(0.8, 1.2));\n c.edgeFade = r2(rand(0, 0.08));\n c.edgeFeather = r2(rand(0.03, 0.22)); // crisp graphic ↔ soft vapor edges\n c.depthTint = rand(0, 1) < 0.3 ? r2(rand(0.2, 0.6)) : 0; // occasional atmospheric depth fade\n // Wireframe line params (inert unless theme is \"wireframe\") — randomized too so a wireframe\n // wave's Finish 🎲 refreshes its whole look, not just the solid-shader knobs.\n c.lineAmount = Math.round(rand(200, 900));\n c.lineThickness = r2(rand(0.5, 2));\n c.lineDerivativePower = r2(rand(0.4, 1.2));\n c.maxWidth = Math.round(rand(400, 1600));\n c.theme = rand(0, 1) < 0.2 ? \"wireframe\" : \"solid\"; // occasionally flip the material\n}\n\nexport function randomizeLights(c: StudioConfig): void {\n c.ambient = r2(rand(0.25, 0.6));\n for (const l of c.lights) {\n l.position = { x: r2(rand(-1000, 1000)), y: r2(rand(-500, 1000)), z: r2(rand(-500, 1200)) };\n l.intensity = r2(rand(0.5, 1.6));\n l.color = pick(LIGHT_TINTS);\n }\n}\n\n/** Scene knobs: the post-fx (grain/blur) + camera framing (zoom). */\nexport function randomizeGlobal(c: StudioConfig): void {\n c.grain = r2(rand(0, 1.5));\n c.blur = r3(rand(0, 0.02));\n // Ortho: vary the zoom (framing), keep the camera pose/target — distance doesn't size it.\n c.cameraZoom = r2(rand(1.2, 2.6));\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wave3d/core",
3
- "version": "0.2.2",
3
+ "version": "0.4.1",
4
4
  "description": "Framework-agnostic 3D gradient-wave renderer + config model (the engine behind Wave Studio)",
5
5
  "keywords": [
6
6
  "animation",
@@ -3,18 +3,20 @@ name: wave3d
3
3
  description: >
4
4
  Add an animated 3D gradient "wave of light" (the glossy twisting ribbon seen across Stripe's
5
5
  designs) to a website as a drop-in component, powered by three.js/WebGL. Load this when a user
6
- wants a @wave3d package — @wave3d/react (<Wave3D>), @wave3d/element (<wave-3d>), or @wave3d/core
7
- (createWave / mountWave) — or asks for an animated gradient hero background, a poster-first lazy
8
- WebGL wave, a CDN <script> wave, or how to reproduce a wave exported from Wave Studio.
6
+ wants a @wave3d package — @wave3d/react (<Wave3D>), @wave3d/element (<wave-3d>), @wave3d/core
7
+ (createWave / mountWave) or @wave3d/vite (build-time posters) — or asks for an animated gradient
8
+ hero background, a poster-first lazy WebGL wave, a CDN <script> wave, film-grain / bloom / dither
9
+ / halftone post effects on a wave, or how to reproduce a wave exported from Wave Studio.
9
10
  metadata:
10
11
  type: core
11
12
  library: "@wave3d/core"
12
- library_version: "0.1.0"
13
+ library_version: "0.4.1"
13
14
  sources:
14
15
  - "wave3d/wave3d:README.md"
15
16
  - "wave3d/wave3d:packages/core/src/config/model.ts"
16
17
  - "wave3d/wave3d:packages/core/src/shell/createWave.ts"
17
18
  - "wave3d/wave3d:packages/core/src/presets.ts"
19
+ - "wave3d/wave3d:packages/vite/README.md"
18
20
  ---
19
21
 
20
22
  # @wave3d — drop-in animated 3D gradient waves
@@ -36,6 +38,7 @@ load**. Framework-agnostic core, with React and web-component adapters.
36
38
  pnpm add @wave3d/react three # React
37
39
  pnpm add @wave3d/element three # <wave-3d> for Vue / Svelte / plain HTML
38
40
  pnpm add @wave3d/core three # framework-agnostic createWave
41
+ pnpm add -D @wave3d/vite # optional: capture posters at dev time
39
42
  ```
40
43
 
41
44
  `three` is a **peer dependency** of `@wave3d/core` (`>=0.180 <1`). For TypeScript, also add
@@ -51,6 +54,7 @@ pnpm add @wave3d/core three # framework-agnostic createWave
51
54
  | Direct renderer (no shell/poster) | `import { WaveRenderer } from "@wave3d/core/renderer"` |
52
55
  | One `<script>` from a CDN (three in) | `import { mountWave } from "@wave3d/core/standalone"` (via esm.sh) |
53
56
  | Built-in presets | `import { PRESETS } from "@wave3d/core/presets"` |
57
+ | Poster written to disk at dev time | `import { wave3dPoster } from "@wave3d/vite"` (Vite plugin) |
54
58
 
55
59
  The `.` entry (`@wave3d/core`) has **no static three import** — the engine arrives via a dynamic
56
60
  import, so bundlers keep three out of your initial chunk until a wave actually upgrades.
@@ -109,11 +113,51 @@ Omitted fields fall back to `createDefaultConfig()`.
109
113
  The studio's **Export code** button generates a ready-made snippet (a minimal config diff + a
110
114
  poster) for every entry — the fastest way to hand a designed wave to a developer.
111
115
 
116
+ ## Interactivity (optional)
117
+
118
+ Interactivity is **per wave** and **off by default / additive** — omit it and the wave renders
119
+ byte-for-byte as before. Each `WaveConfig.interaction` has three parts: **`hover`** (the cursor-follow
120
+ field — `agitate` churn, `push` ± repel/attract dome, `wake` drag-trough, `thin`, `hueShift`,
121
+ `lighten`), **`press`** (`ripple` rings from a click/tap), and **`bindings`** that smoothly drive
122
+ that wave's params from an input.
123
+ Sources: `scroll`, `hover`, `pointerX` / `pointerY`, `pointerSpeed`, `press`, `scrollVelocity`,
124
+ `appear`, or a developer-fed `custom:*` (via `handle.setInteractionInput(name, value)` /
125
+ `renderer.setInteractionInput`). Each binding rests at the authored value and moves toward `to` as
126
+ its input rises 0→1 — `{ source: "hover", target: "displaceAmount", to: 12 }` grows the folds on
127
+ hover. Each wave's `hover.smoothing` sets its own cursor-follow lag (vary it across a stack for a
128
+ parallax drag). **Shared inputs** (one cursor + scroll: `radius`, `touch`) and **scene-param
129
+ bindings** (`timeOffset`, `cameraZoom`, `blur`, `grain` — e.g. `scroll → timeOffset` scrubs the whole
130
+ wave with the page) live on `SceneConfig.interaction`. In React the flat `interaction` prop targets
131
+ the first wave; the studio authors it per wave (Hover / Click & touch / Bindings) plus a global
132
+ Interaction folder for the shared inputs and a scroll preview.
133
+
134
+ ## Post effects (optional)
135
+
136
+ Passes over the finished composite. Each is a plain **scene-level** `SceneConfig` field (a sibling of
137
+ `background` / `quality`, **not** per wave) that defaults to off, and setting it to `0` removes the
138
+ pass entirely — cost and pixels identical to never having set it.
139
+
140
+ | Field | Extra knobs | Effect |
141
+ | --------------- | ------------------------------------------------------------ | ----------------------------------------------------------- |
142
+ | `grain` | — | Static film-grain speckle. |
143
+ | `blur` | `blurSamples` | Soft-focus spin blur, smeared toward the top/bottom edges. |
144
+ | `bloomStrength` | `bloomRadius`, `bloomThreshold` | Glow bleed off the bright ribbon (three's UnrealBloomPass). |
145
+ | `innerLight` | `innerLightDensity`, `innerLightDecay`, `innerLightX`/`Y` | Volumetric light streaks from a light point (X/Y in UV). |
146
+ | `dither` | `ditherScale` (cell px), `ditherSteps` (levels/channel, >=2) | Ordered (Bayer) dithering + posterization. |
147
+ | `halftone` | `halftoneCell`, `halftoneAngle` | Rotated dot screen; dot size tracks local brightness. |
148
+ | `halftoneCmyk` | `halftoneCmykCell` | Four rotated screens — subtractive CMYK print look. |
149
+ | `heatmap` | — | Luminance → thermal-palette recolour. |
150
+ | `paperTexture` | `paperTextureScale` | Fibrous printed-paper substrate overlay. |
151
+
152
+ `bloomStrength` and `innerLight` are **scene-zone** — they scatter the raw wave, so they read as
153
+ light. The rest are **finish-zone** stylization, applied after tone-map + sRGB (`dither` runs last).
154
+ Only `blur` and `grain` are bindable from an interaction input; the others are authored values.
155
+
112
156
  ## Presets
113
157
 
114
- 13 built-in presets (`@wave3d/core/presets`): **Hero**, **Wave 2**, **Wave 3**, **Wave 4**,
158
+ 14 built-in presets (`@wave3d/core/presets`): **Hero**, **Wave 2**, **Wave 3**, **Wave 4**,
115
159
  **Wireframe**, **Neon Dark Multistrand**, **Mesh Gradient**, **Solar Bloom**, **Holographic**,
116
- **Aurora**, **Palestine**, **Vaporwave Sunset**, **Kaleidoscope**.
160
+ **Aurora**, **Palestine**, **Spain**, **Vaporwave Sunset**, **Kaleidoscope**.
117
161
 
118
162
  - React: `preset="Hero"` (a **string** lazy-imports the presets chunk) or
119
163
  `preset={() => PRESETS["Hero"]()}` (a **function** is tree-shakeable — bundles only that preset).
@@ -126,9 +170,17 @@ The shell shows a poster immediately, then crossfades to the live wave.
126
170
  - **Poster source:** the `poster` option/prop (URL or data-URI), or — for SSR — an
127
171
  `<img data-wave3d-poster>` you render inside the container; the shell **adopts** it (no hydration
128
172
  flash). React: pass it as a child.
173
+ - **`posterFit`** (`"fill"` default | `"cover"` | `"contain"`) sets how the poster maps into the box.
174
+ The `fill` default matters: it makes the poster match the canvas exactly, so the handoff doesn't
175
+ visibly jump.
129
176
  - **Make a poster:** the studio's Export dialog, or capture the live frame at runtime — once
130
177
  running, `handle.snapshot()` (core/element) or `onReady(renderer)` → `renderer.captureImage()`
131
178
  (React) resolves an image Blob you can host and feed back as the `poster`.
179
+ - **`@wave3d/vite`** automates that in a Vite app: `wave3dPoster()` snapshots the wave from the
180
+ browser already rendering it and writes the file to `public/` — mark a `<wave-3d>` with
181
+ `data-wave3d-poster-out="hero.webp"`, or call `registerPoster(handle, out)` from
182
+ `@wave3d/vite/client` for React / `createWave`. Re-snapshots over HMR; `vite build` just uses the
183
+ committed file.
132
184
  - **`onFallback(reason)`** fires when the shell keeps the poster instead of upgrading; reasons:
133
185
  `"no-webgl" | "reduced-motion" | "save-data" | "context-lost" | "load-error"`.
134
186
  - **`onStateChange(state)`**: `"poster" → "loading" → "running"`, or `"fallback"`.
@@ -142,6 +194,8 @@ The shell shows a poster immediately, then crossfades to the live wave.
142
194
  - `dprMax` clamps device-pixel-ratio (default 2). `quality` and `waves.length` changing forces a
143
195
  geometry **rebuild** (costlier than a uniform refresh); `fiberCount`, `loopSeconds` (0 = off),
144
196
  and `paused` are cheap. The renderer already pauses offscreen and when the tab is hidden.
197
+ - Every enabled **post effect** is one more full-screen pass — leave the ones you aren't using at
198
+ `0` (that removes the pass, rather than running it as a no-op).
145
199
 
146
200
  ## SSR / Next.js
147
201