@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/dist/presets.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presets.js","names":[],"sources":["../src/presets.ts"],"sourcesContent":["/**\n * Built-in presets: each a complete studio config (scene + one or more waves) in the wave model.\n * IP-clean — no copyrighted assets. The studio layers its own extra presets (and its historical\n * \"Stripe *\" display names) on top; see apps/studio/src/presets.ts.\n */\nimport { createDefaultConfig, makeStops, makeWaveSpread } from \"./config/model\";\nimport type { StudioConfig, NoiseBand } from \"./config/model\";\n\nconst RAD = 180 / Math.PI;\n\n/** Build a preset from a set of wave parameters. rotation/hue are given in RADIANS and\n * converted to degrees. All presets are solid-theme, so they reuse the hero palette +\n * surfaceColor fibers (600/0.2) and sheen 0, like the hero. camTarget/zoom frame the\n * wave (we pan the look-at to centre each one). */\nfunction buildPreset(p: {\n speed: number;\n contrast: number;\n sat: number;\n hueRad: number;\n dispX: number;\n dispZ: number;\n dispAmt: number;\n pos: [number, number, number];\n rotRad: [number, number, number];\n scale: [number, number, number];\n twF: [number, number, number];\n twP: [number, number, number];\n glow: [number, number, number];\n grain: number;\n blur: number;\n zoom: number;\n camTarget: [number, number];\n noiseBands?: NoiseBand[];\n twistMotion?: boolean;\n}): StudioConfig {\n const c = createDefaultConfig();\n const w = c.waves[0];\n w.speed = p.speed;\n w.colorContrast = p.contrast;\n w.colorSaturation = p.sat;\n w.hueShift = p.hueRad * RAD;\n w.displaceFrequency = { x: p.dispX, y: p.dispZ };\n w.displaceAmount = p.dispAmt;\n w.position = { x: p.pos[0], y: p.pos[1], z: p.pos[2] };\n w.rotation = { x: p.rotRad[0] * RAD, y: p.rotRad[1] * RAD, z: p.rotRad[2] * RAD };\n w.scale = { x: p.scale[0], y: p.scale[1], z: p.scale[2] };\n w.twistFrequency = { x: p.twF[0], y: p.twF[1], z: p.twF[2] };\n w.twistPower = { x: p.twP[0], y: p.twP[1], z: p.twP[2] };\n w.creaseLight = p.glow[0];\n w.creaseSharpness = p.glow[1];\n w.creaseSoftness = p.glow[2];\n if (p.noiseBands) w.noiseBands = p.noiseBands;\n if (p.twistMotion) w.twistMotion = true;\n c.grain = p.grain;\n c.blur = p.blur;\n c.cameraPosition = { x: 100, y: 0, z: 5000 };\n c.cameraTarget = { x: p.camTarget[0], y: p.camTarget[1], z: 0 };\n c.cameraZoom = p.zoom;\n return c;\n}\n\n/** Presets: each a complete studio config (scene + one or more waves) in the wave model. */\nexport const PRESETS: Record<string, () => StudioConfig> = {\n // The app's default wave: a centred, full-frame ribbon (window-independent framing).\n // Shown first and named \"Hero\"; several presets below derive from it.\n Hero: () =>\n buildPreset({\n speed: 0.04,\n contrast: 1,\n sat: 1,\n hueRad: -0.00159265,\n dispX: 0.005831,\n dispZ: 0.016001,\n dispAmt: -7.821,\n pos: [380, -301.7, -11.1],\n rotRad: [-0.44959, -0.11759, 1.874407],\n scale: [9, 8, 5],\n twF: [-0.65, 0.41, -0.58],\n twP: [3.63, 0.7, 3.95],\n glow: [1.98, 0.806, 0.834],\n grain: 1.1,\n blur: 0.02,\n zoom: 0.55,\n camTarget: [-420, -200], // user-tuned default framing\n }),\n // Stripe's real hero, recreated faithfully: an orthographic ×10 scene that overflows the\n // frame, so only the twisted crop shows. This is the model's plain default config.\n \"Wave 2\": () => createDefaultConfig(),\n // camTarget on the waves below is a first-pass centring; tune per-wave. NOTE: Wave 4 also\n // uses a variant vertex shader (animated twist-X wobble) we don't fully replicate — its\n // STATIC frame is close, the motion differs.\n \"Wave 3\": () =>\n buildPreset({\n speed: 0.08,\n contrast: 1,\n sat: 1,\n hueRad: -0.00159265,\n dispX: 0.005831,\n dispZ: 0.016001,\n dispAmt: -7.821,\n pos: [-200.7, -65.4, -11.1],\n rotRad: [-2.875593, 3.095927, -2.925927],\n scale: [3, 3, 3],\n twF: [0.059, 0.32, -0.397],\n twP: [3.63, 0.44, 5.99],\n glow: [3.86, 0.923, 1],\n grain: 1.2,\n blur: 0.02,\n zoom: 1.3,\n camTarget: [-104, 13], // centred; zoomed in (wide/flat wave)\n }),\n \"Wave 4\": () =>\n buildPreset({\n speed: 0.0525,\n contrast: 0.969,\n sat: 1.383,\n hueRad: 0.0376991,\n dispX: 0.005,\n dispZ: 0.0212,\n dispAmt: 6.68,\n pos: [206.1, -438, -11.1],\n rotRad: [-0.666018, -0.031416, 0.779115],\n scale: [6.0501, 8.3983, 6.9854],\n twF: [-0.424, 0.024, -1.312],\n twP: [1.81, 0.94, 4.76],\n glow: [1.55, 1.174, 0.972],\n grain: 0.576,\n blur: 0,\n zoom: 0.9316,\n camTarget: [194, -402], // centred on the wave\n twistMotion: true, // variant vertex shader — animated twist-X wobble\n noiseBands: [\n {\n startX: 0.856,\n endX: 1,\n startY: 0,\n endY: 0.913,\n feather: 0.5,\n strength: 0.346,\n frequency: 1018,\n colorAttenuation: 1,\n parabolaPower: 0,\n },\n {\n startX: 0.038,\n endX: 0.538,\n startY: 0.105,\n endY: 1,\n feather: 0.3315,\n strength: 1,\n frequency: 190,\n colorAttenuation: 0,\n parabolaPower: 2.11,\n },\n ],\n }),\n // The dark-background hero: identical geometry/camera to the default hero, but theme\n // \"wireframe\" → the line shader on a dark page background, with grain 1.2. Same palette.\n Wireframe: () => {\n const c = createDefaultConfig();\n c.waves[0].theme = \"wireframe\";\n c.grain = 1.2;\n c.background = \"#0a2540\"; // dark navy page background\n c.transparentBackground = false;\n return c;\n },\n \"Neon Dark Multistrand\": () => {\n const c = createDefaultConfig();\n const w = c.waves[0];\n w.theme = \"wireframe\"; // line shader on the near-black background — neon wireframe look\n w.blendMode = \"additive\";\n w.palette = makeStops([\"#00f5d4\", \"#00bbf9\", \"#9b5de5\", \"#f15bb5\", \"#fee440\"]);\n w.creaseLight = 1.0;\n c.background = \"#05060c\";\n c.transparentBackground = false; // fill the dark bg so the neon lines read on black (not the page)\n c.waves = makeWaveSpread(w, 3); // three overlapping neon waves\n c.waveCount = 3;\n return c;\n },\n \"Mesh Gradient\": () => {\n const c = PRESETS[\"Hero\"](); // the centred default \"Hero\" wave\n const w = c.waves[0];\n w.gradientType = \"mesh\";\n w.meshGradientPoints = [\n { color: \"#0a84ff\", x: 0.06, y: 0.9, influence: 0.68 },\n { color: \"#64d2ff\", x: 0.88, y: 0.92, influence: 0.72 },\n { color: \"#bf5af2\", x: 0.5, y: 0.64, influence: 0.58 },\n { color: \"#ff375f\", x: 0.1, y: 0.14, influence: 0.7 },\n { color: \"#ff9f0a\", x: 0.84, y: 0.12, influence: 0.74 },\n { color: \"#30d158\", x: 0.94, y: 0.5, influence: 0.54 },\n ];\n w.meshGradientSoftness = 0.68;\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.06;\n w.colorSaturation = 1.12;\n w.fiberStrength = 0.14;\n c.grain = 0.3;\n c.blur = 0.008;\n c.background = \"#070914\";\n c.backgroundMode = \"color\";\n c.transparentBackground = false;\n return c;\n },\n \"Solar Bloom\": () => {\n // Radial gradient: a warm core blooming out to a deep-indigo edge. usePaletteTexture off so\n // our own stops map along the radial gradCoord instead of sampling the baked hero LUT.\n const c = PRESETS[\"Hero\"]();\n const w = c.waves[0];\n w.usePaletteTexture = false;\n w.gradientType = \"radial\";\n w.gradientShift = 0.14;\n w.palette = [\n { color: \"#fff3c4\", pos: 0 }, // warm-white core\n { color: \"#ffd166\", pos: 0.22 }, // gold\n { color: \"#ff8c42\", pos: 0.42 }, // orange\n { color: \"#ff5d8f\", pos: 0.62 }, // coral-pink\n { color: \"#a64dff\", pos: 0.82 }, // violet\n { color: \"#241246\", pos: 1 }, // deep indigo edge\n ];\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.05;\n w.colorSaturation = 1.18;\n w.fiberStrength = 0.12;\n c.grain = 0.3;\n c.blur = 0.01;\n // Deep warm radial vignette behind the bloom.\n c.background = \"#0a0714\";\n c.backgroundMode = \"gradient\";\n c.backgroundGradientType = \"radial\";\n c.backgroundGradientSource = \"stops\";\n c.backgroundPalette = makeStops([\"#2a1330\", \"#08040f\"]);\n c.transparentBackground = false;\n return c;\n },\n Holographic: () => {\n // Conic gradient: an iridescent oil-slick sweep. The palette wraps (first ≈ last stop) so\n // the conic seam is invisible.\n const c = PRESETS[\"Hero\"]();\n const w = c.waves[0];\n w.usePaletteTexture = false;\n w.gradientType = \"conic\";\n w.gradientShift = 0.08;\n w.palette = [\n { color: \"#8ef6e4\", pos: 0 }, // mint (seam)\n { color: \"#6ec3ff\", pos: 0.18 }, // sky\n { color: \"#9b8cff\", pos: 0.36 }, // periwinkle\n { color: \"#ff8ad8\", pos: 0.54 }, // pink\n { color: \"#ffd98e\", pos: 0.72 }, // peach\n { color: \"#a0f0c8\", pos: 0.88 }, // seafoam\n { color: \"#8ef6e4\", pos: 1 }, // mint again (seamless wrap)\n ];\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.04;\n w.colorSaturation = 1.12;\n w.fiberStrength = 0.12;\n c.grain = 0.28;\n c.blur = 0.01;\n // Subtle deep teal → violet wash behind the iridescence.\n c.background = \"#05060c\";\n c.backgroundMode = \"gradient\";\n c.backgroundGradientType = \"linear\";\n c.backgroundGradientAngle = 135;\n c.backgroundGradientSource = \"stops\";\n c.backgroundPalette = makeStops([\"#04121a\", \"#0a0518\"]);\n c.transparentBackground = false;\n return c;\n },\n Aurora: () => {\n // Mesh gradient: a moody aurora — teals/greens drifting into violet over a night-sky base\n // (distinct from the brighter iOS-style \"Mesh Gradient\").\n const c = PRESETS[\"Hero\"]();\n const w = c.waves[0];\n w.gradientType = \"mesh\";\n w.meshGradientPoints = [\n { color: \"#0a1f3c\", x: 0.08, y: 0.12, influence: 0.62 },\n { color: \"#1fddb0\", x: 0.3, y: 0.7, influence: 0.78 },\n { color: \"#57f5a3\", x: 0.58, y: 0.86, influence: 0.7 },\n { color: \"#3a86ff\", x: 0.82, y: 0.55, influence: 0.62 },\n { color: \"#a15cff\", x: 0.5, y: 0.32, influence: 0.7 },\n { color: \"#071433\", x: 0.92, y: 0.08, influence: 0.6 },\n ];\n w.meshGradientSoftness = 0.72;\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.05;\n w.colorSaturation = 1.18;\n w.fiberStrength = 0.12;\n c.grain = 0.3;\n c.blur = 0.008;\n // Dark night-sky MESH backdrop (also shows off the mesh background type).\n c.background = \"#03060f\";\n c.backgroundMode = \"gradient\";\n c.backgroundGradientType = \"mesh\";\n c.backgroundMeshPoints = [\n { color: \"#02040c\", x: 0.15, y: 0.85, influence: 0.7 },\n { color: \"#08243a\", x: 0.5, y: 0.5, influence: 0.75 },\n { color: \"#0a0f2e\", x: 0.85, y: 0.7, influence: 0.7 },\n { color: \"#04121a\", x: 0.7, y: 0.2, influence: 0.6 },\n { color: \"#000208\", x: 0.12, y: 0.12, influence: 0.6 },\n ];\n c.backgroundMeshSoftness = 0.75;\n c.transparentBackground = false;\n return c;\n },\n Palestine: () => {\n const c = PRESETS[\"Hero\"](); // the centred default \"Hero\" wave\n const w = c.waves[0];\n w.paletteSource = \"palestine\";\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1;\n w.colorSaturation = 1;\n c.grain = 0.35;\n c.background = \"#f2efe8\";\n c.transparentBackground = true;\n return c;\n },\n Spain: () => {\n const c = PRESETS[\"Hero\"](); // the centred default \"Hero\" wave\n const w = c.waves[0];\n w.paletteSource = \"spain\";\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.18;\n w.colorSaturation = 1.25;\n w.creaseLight = 1.6; // moderate crease-light: rich crimson without washing to salmon (Hero's is 1.98)\n c.grain = 0.3;\n c.background = \"#1a0608\"; // deep oxblood stage\n c.backgroundMode = \"color\";\n c.transparentBackground = false; // opaque, so the dark stage makes the flag pop\n return c;\n },\n \"Vaporwave Sunset\": () => {\n // The Hero wave re-posed/re-framed, plus the vaporwave palette.\n const c = PRESETS[\"Hero\"](); // the centred default \"Hero\" wave\n const w = c.waves[0];\n w.position.x = 525;\n w.rotation.x = -0.64 * RAD;\n w.rotation.z = 1.68 * RAD;\n w.paletteSource = \"vaporwave\";\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.08;\n w.colorSaturation = 1.15;\n w.creaseLight = 1.25;\n c.cameraZoom = 1.1;\n c.cameraTarget = { x: 150, y: 360, z: 0 };\n c.background = \"#09051f\";\n c.transparentBackground = false;\n return c;\n },\n Kaleidoscope: () => {\n const c = PRESETS[\"Wave 3\"]();\n const w = c.waves[0];\n w.paletteSource = \"kaleidoscope\";\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.05;\n w.colorSaturation = 1.12;\n c.grain = 0.5;\n return c;\n },\n};\n"],"mappings":";;;;;;;AAQA,MAAM,MAAM,MAAM,KAAK;;;;;AAMvB,SAAS,YAAY,GAoBJ;CACf,MAAM,IAAI,oBAAoB;CAC9B,MAAM,IAAI,EAAE,MAAM;CAClB,EAAE,QAAQ,EAAE;CACZ,EAAE,gBAAgB,EAAE;CACpB,EAAE,kBAAkB,EAAE;CACtB,EAAE,WAAW,EAAE,SAAS;CACxB,EAAE,oBAAoB;EAAE,GAAG,EAAE;EAAO,GAAG,EAAE;CAAM;CAC/C,EAAE,iBAAiB,EAAE;CACrB,EAAE,WAAW;EAAE,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;CAAG;CACrD,EAAE,WAAW;EAAE,GAAG,EAAE,OAAO,KAAK;EAAK,GAAG,EAAE,OAAO,KAAK;EAAK,GAAG,EAAE,OAAO,KAAK;CAAI;CAChF,EAAE,QAAQ;EAAE,GAAG,EAAE,MAAM;EAAI,GAAG,EAAE,MAAM;EAAI,GAAG,EAAE,MAAM;CAAG;CACxD,EAAE,iBAAiB;EAAE,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;CAAG;CAC3D,EAAE,aAAa;EAAE,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;CAAG;CACvD,EAAE,cAAc,EAAE,KAAK;CACvB,EAAE,kBAAkB,EAAE,KAAK;CAC3B,EAAE,iBAAiB,EAAE,KAAK;CAC1B,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE;CACnC,IAAI,EAAE,aAAa,EAAE,cAAc;CACnC,EAAE,QAAQ,EAAE;CACZ,EAAE,OAAO,EAAE;CACX,EAAE,iBAAiB;EAAE,GAAG;EAAK,GAAG;EAAG,GAAG;CAAK;CAC3C,EAAE,eAAe;EAAE,GAAG,EAAE,UAAU;EAAI,GAAG,EAAE,UAAU;EAAI,GAAG;CAAE;CAC9D,EAAE,aAAa,EAAE;CACjB,OAAO;AACT;;AAGA,MAAa,UAA8C;CAGzD,YACE,YAAY;EACV,OAAO;EACP,UAAU;EACV,KAAK;EACL,QAAQ;EACR,OAAO;EACP,OAAO;EACP,SAAS;EACT,KAAK;GAAC;GAAK;GAAQ;EAAK;EACxB,QAAQ;GAAC;GAAU;GAAU;EAAQ;EACrC,OAAO;GAAC;GAAG;GAAG;EAAC;EACf,KAAK;GAAC;GAAO;GAAM;EAAK;EACxB,KAAK;GAAC;GAAM;GAAK;EAAI;EACrB,MAAM;GAAC;GAAM;GAAO;EAAK;EACzB,OAAO;EACP,MAAM;EACN,MAAM;EACN,WAAW,CAAC,MAAM,IAAI;CACxB,CAAC;CAGH,gBAAgB,oBAAoB;CAIpC,gBACE,YAAY;EACV,OAAO;EACP,UAAU;EACV,KAAK;EACL,QAAQ;EACR,OAAO;EACP,OAAO;EACP,SAAS;EACT,KAAK;GAAC;GAAQ;GAAO;EAAK;EAC1B,QAAQ;GAAC;GAAW;GAAU;EAAS;EACvC,OAAO;GAAC;GAAG;GAAG;EAAC;EACf,KAAK;GAAC;GAAO;GAAM;EAAM;EACzB,KAAK;GAAC;GAAM;GAAM;EAAI;EACtB,MAAM;GAAC;GAAM;GAAO;EAAC;EACrB,OAAO;EACP,MAAM;EACN,MAAM;EACN,WAAW,CAAC,MAAM,EAAE;CACtB,CAAC;CACH,gBACE,YAAY;EACV,OAAO;EACP,UAAU;EACV,KAAK;EACL,QAAQ;EACR,OAAO;EACP,OAAO;EACP,SAAS;EACT,KAAK;GAAC;GAAO;GAAM;EAAK;EACxB,QAAQ;GAAC;GAAW;GAAW;EAAQ;EACvC,OAAO;GAAC;GAAQ;GAAQ;EAAM;EAC9B,KAAK;GAAC;GAAQ;GAAO;EAAM;EAC3B,KAAK;GAAC;GAAM;GAAM;EAAI;EACtB,MAAM;GAAC;GAAM;GAAO;EAAK;EACzB,OAAO;EACP,MAAM;EACN,MAAM;EACN,WAAW,CAAC,KAAK,IAAI;EACrB,aAAa;EACb,YAAY,CACV;GACE,QAAQ;GACR,MAAM;GACN,QAAQ;GACR,MAAM;GACN,SAAS;GACT,UAAU;GACV,WAAW;GACX,kBAAkB;GAClB,eAAe;EACjB,GACA;GACE,QAAQ;GACR,MAAM;GACN,QAAQ;GACR,MAAM;GACN,SAAS;GACT,UAAU;GACV,WAAW;GACX,kBAAkB;GAClB,eAAe;EACjB,CACF;CACF,CAAC;CAGH,iBAAiB;EACf,MAAM,IAAI,oBAAoB;EAC9B,EAAE,MAAM,EAAE,CAAC,QAAQ;EACnB,EAAE,QAAQ;EACV,EAAE,aAAa;EACf,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,+BAA+B;EAC7B,MAAM,IAAI,oBAAoB;EAC9B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,QAAQ;EACV,EAAE,YAAY;EACd,EAAE,UAAU,UAAU;GAAC;GAAW;GAAW;GAAW;GAAW;EAAS,CAAC;EAC7E,EAAE,cAAc;EAChB,EAAE,aAAa;EACf,EAAE,wBAAwB;EAC1B,EAAE,QAAQ,eAAe,GAAG,CAAC;EAC7B,EAAE,YAAY;EACd,OAAO;CACT;CACA,uBAAuB;EACrB,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,eAAe;EACjB,EAAE,qBAAqB;GACrB;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAK,WAAW;GAAK;GACrD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAM,WAAW;GAAK;GACrD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAM,WAAW;GAAI;GACpD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAK,WAAW;GAAK;EACvD;EACA,EAAE,uBAAuB;EACzB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,gBAAgB;EAClB,EAAE,QAAQ;EACV,EAAE,OAAO;EACT,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,qBAAqB;EAGnB,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,oBAAoB;EACtB,EAAE,eAAe;EACjB,EAAE,gBAAgB;EAClB,EAAE,UAAU;GACV;IAAE,OAAO;IAAW,KAAK;GAAE;GAC3B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAE;EAC7B;EACA,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,gBAAgB;EAClB,EAAE,QAAQ;EACV,EAAE,OAAO;EAET,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,yBAAyB;EAC3B,EAAE,2BAA2B;EAC7B,EAAE,oBAAoB,UAAU,CAAC,WAAW,SAAS,CAAC;EACtD,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,mBAAmB;EAGjB,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,oBAAoB;EACtB,EAAE,eAAe;EACjB,EAAE,gBAAgB;EAClB,EAAE,UAAU;GACV;IAAE,OAAO;IAAW,KAAK;GAAE;GAC3B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAE;EAC7B;EACA,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,gBAAgB;EAClB,EAAE,QAAQ;EACV,EAAE,OAAO;EAET,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,yBAAyB;EAC3B,EAAE,0BAA0B;EAC5B,EAAE,2BAA2B;EAC7B,EAAE,oBAAoB,UAAU,CAAC,WAAW,SAAS,CAAC;EACtD,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,cAAc;EAGZ,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,eAAe;EACjB,EAAE,qBAAqB;GACrB;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAK,WAAW;GAAK;GACpD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAI;GACrD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAM,WAAW;GAAI;GACpD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAI;EACvD;EACA,EAAE,uBAAuB;EACzB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,gBAAgB;EAClB,EAAE,QAAQ;EACV,EAAE,OAAO;EAET,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,yBAAyB;EAC3B,EAAE,uBAAuB;GACvB;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAI;GACrD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAK,WAAW;GAAK;GACpD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAK,WAAW;GAAI;GACpD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAK,WAAW;GAAI;GACnD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAI;EACvD;EACA,EAAE,yBAAyB;EAC3B,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,iBAAiB;EACf,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,gBAAgB;EAClB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,QAAQ;EACV,EAAE,aAAa;EACf,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,aAAa;EACX,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,gBAAgB;EAClB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,cAAc;EAChB,EAAE,QAAQ;EACV,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,0BAA0B;EAExB,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,SAAS,IAAI;EACf,EAAE,SAAS,IAAI,OAAQ;EACvB,EAAE,SAAS,IAAI,OAAO;EACtB,EAAE,gBAAgB;EAClB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,cAAc;EAChB,EAAE,aAAa;EACf,EAAE,eAAe;GAAE,GAAG;GAAK,GAAG;GAAK,GAAG;EAAE;EACxC,EAAE,aAAa;EACf,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,oBAAoB;EAClB,MAAM,IAAI,QAAQ,SAAS,CAAC;EAC5B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,gBAAgB;EAClB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,QAAQ;EACV,OAAO;CACT;AACF"}
|
|
1
|
+
{"version":3,"file":"presets.js","names":[],"sources":["../src/presets.ts"],"sourcesContent":["/**\n * Built-in presets: each a complete studio config (scene + one or more waves) in the wave model.\n * IP-clean — no copyrighted assets. The studio layers its own extra presets (and its historical\n * \"Stripe *\" display names) on top; see apps/studio/src/presets.ts.\n */\nimport { createDefaultConfig, makeStops, makeWaveSpread } from \"./config/model\";\nimport type { ParticlesConfig, StudioConfig, NoiseBand } from \"./config/model\";\n\nconst RAD = 180 / Math.PI;\n\n/**\n * Reusable particle STYLES applied to a wave's `particles` block. Surfaced in the studio's per-wave\n * Particles folder (the \"style\" picker) and used by the \"Particle Zoo\" showcase preset. Each is a dust\n * look independent of the wave it rides — clone before mutating (`structuredClone`).\n */\nexport const PARTICLE_PRESETS: Record<string, ParticlesConfig> = {\n Glitter: {\n count: 16000,\n size: 2.6,\n seed: 7,\n color: \"#ffdca8\",\n shape: \"glitter\",\n edgeBias: 1,\n drift: 300,\n bias: -0.6,\n twinkle: 0.8,\n },\n Embers: {\n count: 16000,\n size: 3.6,\n seed: 7,\n color: \"#ff8a2c\",\n color2: \"#ffe19a\",\n shape: \"soft\",\n edgeBias: 0.15,\n drift: 40,\n rise: 320,\n wander: 120,\n twinkle: 0.85,\n life: 6,\n },\n Snow: {\n count: 12000,\n size: 3,\n seed: 4,\n color: \"#eef4ff\",\n color2: \"#bcd0ee\",\n shape: \"soft\",\n edgeBias: 0.1,\n drift: 20,\n rise: -260,\n wander: 80,\n twinkle: 0.5,\n life: 8,\n },\n Sparks: {\n count: 9000,\n size: 5,\n seed: 9,\n color: \"#ffd27a\",\n color2: \"#fff4d0\",\n shape: \"streak\",\n edgeBias: 1,\n drift: 620,\n rise: 90,\n wander: 20,\n bias: -0.4,\n twinkle: 0.9,\n life: 2.5,\n },\n Fireflies: {\n count: 2200,\n size: 5,\n seed: 11,\n color: \"#dfff9a\",\n color2: \"#8fd94a\",\n shape: \"star\",\n edgeBias: 0.1,\n drift: 25,\n swirl: 0.3,\n wander: 170,\n twinkle: 1,\n life: 5,\n },\n Bubbles: {\n count: 3000,\n size: 7,\n sizeJitter: 0.9,\n seed: 6,\n color: \"#cdeefb\",\n color2: \"#8fd0e6\",\n shape: \"ring\",\n edgeBias: 0.2,\n drift: 30,\n rise: 240,\n wander: 60,\n twinkle: 0.3,\n life: 7,\n },\n};\n\n/** Degrees → radians (for the Particle Zoo's per-wave rotation, authored in degrees). */\nconst rad = (deg: number): number => (deg * Math.PI) / 180;\n\n/** Clamp a signed magnitude to ±m (used to cap the Zoo's long-range particle motion terms). */\nconst capMag = (v: number | undefined, m: number): number | undefined =>\n v == null ? v : Math.sign(v) * Math.min(Math.abs(v), m);\n\n/** Build a preset from a set of wave parameters. rotation/hue are given in RADIANS and\n * converted to degrees. All presets are solid-theme, so they reuse the hero palette +\n * surfaceColor fibers (600/0.2) and sheen 0, like the hero. camTarget/zoom frame the\n * wave (we pan the look-at to centre each one). */\nfunction buildPreset(p: {\n speed: number;\n contrast: number;\n sat: number;\n hueRad: number;\n dispX: number;\n dispZ: number;\n dispAmt: number;\n pos: [number, number, number];\n rotRad: [number, number, number];\n scale: [number, number, number];\n twF: [number, number, number];\n twP: [number, number, number];\n glow: [number, number, number];\n grain: number;\n blur: number;\n zoom: number;\n camTarget: [number, number];\n noiseBands?: NoiseBand[];\n twistMotion?: boolean;\n}): StudioConfig {\n const c = createDefaultConfig();\n const w = c.waves[0];\n w.speed = p.speed;\n w.colorContrast = p.contrast;\n w.colorSaturation = p.sat;\n w.hueShift = p.hueRad * RAD;\n w.displaceFrequency = { x: p.dispX, y: p.dispZ };\n w.displaceAmount = p.dispAmt;\n w.position = { x: p.pos[0], y: p.pos[1], z: p.pos[2] };\n w.rotation = { x: p.rotRad[0] * RAD, y: p.rotRad[1] * RAD, z: p.rotRad[2] * RAD };\n w.scale = { x: p.scale[0], y: p.scale[1], z: p.scale[2] };\n w.twistFrequency = { x: p.twF[0], y: p.twF[1], z: p.twF[2] };\n w.twistPower = { x: p.twP[0], y: p.twP[1], z: p.twP[2] };\n w.creaseLight = p.glow[0];\n w.creaseSharpness = p.glow[1];\n w.creaseSoftness = p.glow[2];\n if (p.noiseBands) w.noiseBands = p.noiseBands;\n if (p.twistMotion) w.twistMotion = true;\n c.grain = p.grain;\n c.blur = p.blur;\n c.cameraPosition = { x: 100, y: 0, z: 5000 };\n c.cameraTarget = { x: p.camTarget[0], y: p.camTarget[1], z: 0 };\n c.cameraZoom = p.zoom;\n return c;\n}\n\n/** Presets: each a complete studio config (scene + one or more waves) in the wave model. */\nexport const PRESETS: Record<string, () => StudioConfig> = {\n // The app's default wave: a centred, full-frame ribbon (window-independent framing).\n // Shown first and named \"Hero\"; several presets below derive from it.\n Hero: () =>\n buildPreset({\n speed: 0.04,\n contrast: 1,\n sat: 1,\n hueRad: -0.00159265,\n dispX: 0.005831,\n dispZ: 0.016001,\n dispAmt: -7.821,\n pos: [380, -301.7, -11.1],\n rotRad: [-0.44959, -0.11759, 1.874407],\n scale: [9, 8, 5],\n twF: [-0.65, 0.41, -0.58],\n twP: [3.63, 0.7, 3.95],\n glow: [1.98, 0.806, 0.834],\n grain: 1.1,\n blur: 0.02,\n zoom: 0.55,\n camTarget: [-420, -200], // user-tuned default framing\n }),\n // Stripe's real hero, recreated faithfully: an orthographic ×10 scene that overflows the\n // frame, so only the twisted crop shows. This is the model's plain default config.\n \"Wave 2\": () => createDefaultConfig(),\n // camTarget on the waves below is a first-pass centring; tune per-wave. NOTE: Wave 4 also\n // uses a variant vertex shader (animated twist-X wobble) we don't fully replicate — its\n // STATIC frame is close, the motion differs.\n \"Wave 3\": () =>\n buildPreset({\n speed: 0.08,\n contrast: 1,\n sat: 1,\n hueRad: -0.00159265,\n dispX: 0.005831,\n dispZ: 0.016001,\n dispAmt: -7.821,\n pos: [-200.7, -65.4, -11.1],\n rotRad: [-2.875593, 3.095927, -2.925927],\n scale: [3, 3, 3],\n twF: [0.059, 0.32, -0.397],\n twP: [3.63, 0.44, 5.99],\n glow: [3.86, 0.923, 1],\n grain: 1.2,\n blur: 0.02,\n zoom: 1.3,\n camTarget: [-104, 13], // centred; zoomed in (wide/flat wave)\n }),\n \"Wave 4\": () =>\n buildPreset({\n speed: 0.0525,\n contrast: 0.969,\n sat: 1.383,\n hueRad: 0.0376991,\n dispX: 0.005,\n dispZ: 0.0212,\n dispAmt: 6.68,\n pos: [206.1, -438, -11.1],\n rotRad: [-0.666018, -0.031416, 0.779115],\n scale: [6.0501, 8.3983, 6.9854],\n twF: [-0.424, 0.024, -1.312],\n twP: [1.81, 0.94, 4.76],\n glow: [1.55, 1.174, 0.972],\n grain: 0.576,\n blur: 0,\n zoom: 0.9316,\n camTarget: [194, -402], // centred on the wave\n twistMotion: true, // variant vertex shader — animated twist-X wobble\n noiseBands: [\n {\n startX: 0.856,\n endX: 1,\n startY: 0,\n endY: 0.913,\n feather: 0.5,\n strength: 0.346,\n frequency: 1018,\n colorAttenuation: 1,\n parabolaPower: 0,\n },\n {\n startX: 0.038,\n endX: 0.538,\n startY: 0.105,\n endY: 1,\n feather: 0.3315,\n strength: 1,\n frequency: 190,\n colorAttenuation: 0,\n parabolaPower: 2.11,\n },\n ],\n }),\n // The dark-background hero: identical geometry/camera to the default hero, but theme\n // \"wireframe\" → the line shader on a dark page background, with grain 1.2. Same palette.\n Wireframe: () => {\n const c = createDefaultConfig();\n c.waves[0].theme = \"wireframe\";\n c.grain = 1.2;\n c.background = \"#0a2540\"; // dark navy page background\n c.transparentBackground = false;\n return c;\n },\n \"Neon Dark Multistrand\": () => {\n const c = createDefaultConfig();\n const w = c.waves[0];\n w.theme = \"wireframe\"; // line shader on the near-black background — neon wireframe look\n w.blendMode = \"additive\";\n w.palette = makeStops([\"#00f5d4\", \"#00bbf9\", \"#9b5de5\", \"#f15bb5\", \"#fee440\"]);\n w.creaseLight = 1.0;\n c.background = \"#05060c\";\n c.transparentBackground = false; // fill the dark bg so the neon lines read on black (not the page)\n c.waves = makeWaveSpread(w, 3); // three overlapping neon waves\n c.waveCount = 3;\n return c;\n },\n \"Mesh Gradient\": () => {\n const c = PRESETS[\"Hero\"](); // the centred default \"Hero\" wave\n const w = c.waves[0];\n w.gradientType = \"mesh\";\n w.meshGradientPoints = [\n { color: \"#0a84ff\", x: 0.06, y: 0.9, influence: 0.68 },\n { color: \"#64d2ff\", x: 0.88, y: 0.92, influence: 0.72 },\n { color: \"#bf5af2\", x: 0.5, y: 0.64, influence: 0.58 },\n { color: \"#ff375f\", x: 0.1, y: 0.14, influence: 0.7 },\n { color: \"#ff9f0a\", x: 0.84, y: 0.12, influence: 0.74 },\n { color: \"#30d158\", x: 0.94, y: 0.5, influence: 0.54 },\n ];\n w.meshGradientSoftness = 0.68;\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.06;\n w.colorSaturation = 1.12;\n w.fiberStrength = 0.14;\n c.grain = 0.3;\n c.blur = 0.008;\n c.background = \"#070914\";\n c.backgroundMode = \"color\";\n c.transparentBackground = false;\n return c;\n },\n \"Solar Bloom\": () => {\n // Radial gradient: a warm core blooming out to a deep-indigo edge. usePaletteTexture off so\n // our own stops map along the radial gradCoord instead of sampling the baked hero LUT.\n const c = PRESETS[\"Hero\"]();\n const w = c.waves[0];\n w.usePaletteTexture = false;\n w.gradientType = \"radial\";\n w.gradientShift = 0.14;\n w.palette = [\n { color: \"#fff3c4\", pos: 0 }, // warm-white core\n { color: \"#ffd166\", pos: 0.22 }, // gold\n { color: \"#ff8c42\", pos: 0.42 }, // orange\n { color: \"#ff5d8f\", pos: 0.62 }, // coral-pink\n { color: \"#a64dff\", pos: 0.82 }, // violet\n { color: \"#241246\", pos: 1 }, // deep indigo edge\n ];\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.05;\n w.colorSaturation = 1.18;\n w.fiberStrength = 0.12;\n c.grain = 0.3;\n c.blur = 0.01;\n // Deep warm radial vignette behind the bloom.\n c.background = \"#0a0714\";\n c.backgroundMode = \"gradient\";\n c.backgroundGradientType = \"radial\";\n c.backgroundGradientSource = \"stops\";\n c.backgroundPalette = makeStops([\"#2a1330\", \"#08040f\"]);\n c.transparentBackground = false;\n return c;\n },\n \"Latte Ring\": () => {\n // A warm cream-and-gold ring of combed silk curling around a dark void — the crema swirl on a\n // latte. The camera ORBITS a wide radial fan (arc 286°) so its combed length sweeps across the\n // frame as a flowing arc instead of a head-on fan; a mesh gradient warms it gold→cream with an\n // orange edge, and a noise band frays the fibers finer toward the tips. Exercises the radial wave\n // mode + the particle layer (ambient field + shed-from-edge dust into the void).\n const c = PRESETS[\"Hero\"]();\n const w = c.waves[0];\n // A wide radial fan (fans the ribbon's length); the orbiting camera below crops it to a ring.\n w.radialAmount = 0.72;\n w.radialArc = 286;\n w.radialCenter = -160;\n w.radialRadius = 300;\n w.radialSpread = 1.47;\n w.rotation = { x: 0, y: 0, z: 0 };\n w.position = { x: -280, y: -320, z: 0 };\n w.scale = { x: 1.12, y: 1.12, z: 1.12 };\n w.opacity = 0.5;\n // A big broad swell (amount 101.73) with a fine second octave riding on it, so the ring's silk\n // ripples and folds as it turns; `speed` sets the swell's drift rate.\n w.displaceFrequency = { x: 0.0026, y: 0.0048 };\n w.displaceAmount = 101.73;\n w.detailAmount = 6;\n w.detailFrequency = 0.1;\n w.speed = 0.21;\n // Fine combed fibers; the noise band overrides them finer + wispier over the OUTER half (uv.y>0.5)\n // for organic variation instead of a uniform comb. (Bands override fiber params per uv region.)\n w.fiberCount = 110;\n w.fiberStrength = 0.95;\n w.creaseLight = 0.55;\n w.edgeFeather = 0.34;\n w.noiseBands = [\n {\n startX: 0,\n endX: 1,\n startY: 0.5,\n endY: 1,\n feather: 0.4,\n strength: 1,\n frequency: 300,\n colorAttenuation: 0.85,\n parabolaPower: 2.5,\n },\n ];\n // Mesh gradient: gold + cream dominant with an orange accent — the latte's crema tones. A 2D\n // colour field (not a length-wise ramp) so the warmth pools within the fibers.\n w.usePaletteTexture = false;\n w.gradientType = \"mesh\";\n w.meshGradientSoftness = 0.7;\n w.meshGradientPoints = [\n { color: \"#f3c06a\", x: 0.5, y: 0.1, influence: 0.7 }, // warm gold\n { color: \"#f9edd4\", x: 0.34, y: 0.36, influence: 0.62 }, // hot cream\n { color: \"#e6923a\", x: 0.15, y: 0.55, influence: 0.5 }, // orange accent\n { color: \"#f1d49a\", x: 0.82, y: 0.42, influence: 0.55 }, // gold\n { color: \"#ece7d8\", x: 0.58, y: 0.88, influence: 0.72 }, // cool cream\n ];\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.05;\n w.colorSaturation = 1.15;\n // Orbit the camera around the fan so its length reads as a sweeping arc wrapping the void.\n c.cameraDistance = 600;\n c.cameraPosition = { x: -854.327, y: 135.331, z: 478.048 };\n c.cameraTarget = { x: -720.043, y: 10.575, z: -93.27 };\n c.cameraZoom = 1.222;\n // Scene: a near-black void with gentle bloom on the silk + dust.\n c.background = \"#050404\";\n c.backgroundMode = \"color\";\n c.transparentBackground = false;\n c.grain = 0.25;\n c.blur = 0;\n c.bloomStrength = 0.18;\n c.bloomRadius = 0.55;\n c.bloomThreshold = 0.72;\n // Golden dust shed off the ring's own edge into the void (biased to one flank). Per-wave: it rides\n // this wave's deform and drifts outward from it. edgeBias 1 = spawn on the rim (the shed look).\n w.particles = {\n count: 20000,\n size: 2.9,\n color: \"#ffdca8\",\n seed: 7,\n twinkle: 0.8,\n edgeBias: 1,\n drift: 490,\n bias: -0.6,\n };\n return c;\n },\n \"Particle Zoo\": () => {\n // One scene demoing every particle STYLE — each wave sheds a different kind of dust off its own\n // surface (embers / snow / sparks / fireflies / bubbles). Every wave is a DIFFERENT shape family\n // too (dome / flat sheet / radial fan / helix coil / twist curl), spread apart so none overlap and\n // the frame stays full. The showcase for the per-wave particle system; styles come from\n // PARTICLE_PRESETS (the studio \"preset style\" picker).\n const c = createDefaultConfig();\n c.background = \"#05070d\";\n c.backgroundMode = \"color\";\n c.transparentBackground = false;\n c.grain = 0.26;\n c.blur = 0;\n c.bloomStrength = 0.3;\n c.bloomThreshold = 0.66; // high, so the central sparks fan glows without blowing the middle out\n c.cameraPosition = { x: 100, y: 0, z: 5000 };\n c.cameraTarget = { x: 0, y: 0, z: 0 };\n c.cameraZoom = 0.6;\n // Five well-separated waves, each a DIFFERENT shape family so the silhouettes read as distinct,\n // not five copies of one ribbon. Frame at cameraZoom 0.6 spans ~2222×1250 world units centred on\n // the origin (x ∈ [-1111, 1111], y ∈ [-625, 625], orthographic → screen-xy = world-xy), so the\n // positions/scales below keep the meshes from overlapping. `shape` carries the per-family knobs\n // (displace / twist / helix / radial); `rot` is in degrees. style, palette, opacity as before.\n const zoo = [\n {\n // Embers rising off a broad DOME — low-frequency, high-amplitude displacement mound.\n style: \"Embers\",\n palette: [\"#ff7a1e\", \"#ffd27a\", \"#c23a12\"],\n pos: [-720, 285],\n scale: [1.0, 1.0, 1.0],\n rot: [-20, 0, -14],\n opacity: 0.72,\n shape: {\n displaceFrequency: { x: 0.0024, y: 0.0065 },\n displaceAmount: 118,\n twistPower: { x: 2.4, y: 2.4, z: 3.2 },\n },\n },\n {\n // Snow settling on a near-flat drifting SHEET — tilted toward the camera, barely rippled.\n style: \"Snow\",\n palette: [\"#8fb8e8\", \"#e8f1fb\", \"#aeb9d6\"],\n pos: [700, 320],\n scale: [1.1, 1.0, 0.9],\n rot: [-62, 0, 12],\n opacity: 0.72,\n shape: {\n displaceFrequency: { x: 0.006, y: 0.013 },\n displaceAmount: 22,\n twistPower: { x: 0.8, y: 0.8, z: 1.0 },\n },\n },\n {\n // Sparks bursting off a RADIAL FAN — the one spiky, fanned silhouette; dimmed so it glows\n // without blowing the centre out.\n style: \"Sparks\",\n palette: [\"#ffd27a\", \"#fff4d0\", \"#c8853a\"],\n pos: [0, -30],\n scale: [0.85, 0.85, 0.85],\n rot: [0, 0, 0],\n opacity: 0.32,\n shape: {\n radialAmount: 0.82,\n radialArc: 118,\n radialCenter: 90, // fan points up\n radialRadius: 40,\n radialSpread: 1.0,\n displaceAmount: 30,\n },\n },\n {\n // Fireflies wandering around a HELIX coil — a screw-thread column of light.\n style: \"Fireflies\",\n palette: [\"#3d5a24\", \"#7bd23a\", \"#40521f\"],\n pos: [-560, -300],\n scale: [1.25, 1.25, 1.25],\n rot: [0, 0, 8],\n opacity: 0.72,\n shape: {\n helixTurns: 3,\n helixRadius: 44,\n helixRoll: 1,\n displaceAmount: 8,\n twistPower: { x: 1.0, y: 1.0, z: 1.2 },\n },\n },\n {\n // Bubbles rising off a strongly TWISTED curl — an S-ribbon coiling on its length.\n style: \"Bubbles\",\n palette: [\"#2e8fb0\", \"#7fd4e8\", \"#1a5a6e\"],\n pos: [660, -270],\n scale: [1.0, 1.15, 1.0],\n rot: [-16, 0, -20],\n opacity: 0.72,\n shape: {\n displaceFrequency: { x: 0.005, y: 0.011 },\n displaceAmount: 46,\n twistFrequency: { x: -0.06, y: 0.09, z: -0.5 },\n twistPower: { x: 4.0, y: 6.0, z: 9.0 },\n },\n },\n ];\n const base = c.waves[0];\n c.waves = zoo.map((z, i) => {\n const w = structuredClone(base);\n w.usePaletteTexture = false;\n w.gradientType = \"linear\";\n w.gradientAngle = 0;\n w.palette = makeStops(z.palette);\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1;\n w.colorSaturation = 1.1;\n w.opacity = z.opacity;\n w.scale = { x: z.scale[0], y: z.scale[1], z: z.scale[2] };\n w.rotation = { x: rad(z.rot[0]), y: rad(z.rot[1]), z: rad(z.rot[2]) };\n w.position = { x: z.pos[0], y: z.pos[1], z: 0 };\n w.seed = i * 7;\n w.speed = 0.05;\n Object.assign(w, z.shape); // the wave's distinct shape family (displace / twist / helix / radial)\n // Each PARTICLE_PRESET is tuned as a standalone hero cloud whose dust flings far; here every\n // specimen should sit ON the wave that emits it, reading as dust clinging to the surface rather\n // than a cloud floating nearby. Pull the motion terms right down (a small fraction of the hero\n // reach + tight clamps) so the dust overlaps its own wave; the styles still read apart by sprite\n // shape / colour / wave shape.\n const p = structuredClone(PARTICLE_PRESETS[z.style]);\n const PULL = 0.13; // fraction of each preset's hero-scale reach kept here\n if (p.drift != null) p.drift = capMag(p.drift * PULL, 24);\n if (p.rise != null) p.rise = capMag(p.rise * PULL, 28);\n if (p.wander != null) p.wander = capMag(p.wander * PULL, 15);\n if (p.swirl != null) p.swirl = capMag(p.swirl * PULL, 0.05);\n w.particles = p;\n return w;\n });\n c.waveCount = c.waves.length;\n return c;\n },\n Holographic: () => {\n // Conic gradient: an iridescent oil-slick sweep. The palette wraps (first ≈ last stop) so\n // the conic seam is invisible.\n const c = PRESETS[\"Hero\"]();\n const w = c.waves[0];\n w.usePaletteTexture = false;\n w.gradientType = \"conic\";\n w.gradientShift = 0.08;\n w.palette = [\n { color: \"#8ef6e4\", pos: 0 }, // mint (seam)\n { color: \"#6ec3ff\", pos: 0.18 }, // sky\n { color: \"#9b8cff\", pos: 0.36 }, // periwinkle\n { color: \"#ff8ad8\", pos: 0.54 }, // pink\n { color: \"#ffd98e\", pos: 0.72 }, // peach\n { color: \"#a0f0c8\", pos: 0.88 }, // seafoam\n { color: \"#8ef6e4\", pos: 1 }, // mint again (seamless wrap)\n ];\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.04;\n w.colorSaturation = 1.12;\n w.fiberStrength = 0.12;\n c.grain = 0.28;\n c.blur = 0.01;\n // Subtle deep teal → violet wash behind the iridescence.\n c.background = \"#05060c\";\n c.backgroundMode = \"gradient\";\n c.backgroundGradientType = \"linear\";\n c.backgroundGradientAngle = 135;\n c.backgroundGradientSource = \"stops\";\n c.backgroundPalette = makeStops([\"#04121a\", \"#0a0518\"]);\n c.transparentBackground = false;\n return c;\n },\n Aurora: () => {\n // Mesh gradient: a moody aurora — teals/greens drifting into violet over a night-sky base\n // (distinct from the brighter iOS-style \"Mesh Gradient\").\n const c = PRESETS[\"Hero\"]();\n const w = c.waves[0];\n w.gradientType = \"mesh\";\n w.meshGradientPoints = [\n { color: \"#0a1f3c\", x: 0.08, y: 0.12, influence: 0.62 },\n { color: \"#1fddb0\", x: 0.3, y: 0.7, influence: 0.78 },\n { color: \"#57f5a3\", x: 0.58, y: 0.86, influence: 0.7 },\n { color: \"#3a86ff\", x: 0.82, y: 0.55, influence: 0.62 },\n { color: \"#a15cff\", x: 0.5, y: 0.32, influence: 0.7 },\n { color: \"#071433\", x: 0.92, y: 0.08, influence: 0.6 },\n ];\n w.meshGradientSoftness = 0.72;\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.05;\n w.colorSaturation = 1.18;\n w.fiberStrength = 0.12;\n c.grain = 0.3;\n c.blur = 0.008;\n // Dark night-sky MESH backdrop (also shows off the mesh background type).\n c.background = \"#03060f\";\n c.backgroundMode = \"gradient\";\n c.backgroundGradientType = \"mesh\";\n c.backgroundMeshPoints = [\n { color: \"#02040c\", x: 0.15, y: 0.85, influence: 0.7 },\n { color: \"#08243a\", x: 0.5, y: 0.5, influence: 0.75 },\n { color: \"#0a0f2e\", x: 0.85, y: 0.7, influence: 0.7 },\n { color: \"#04121a\", x: 0.7, y: 0.2, influence: 0.6 },\n { color: \"#000208\", x: 0.12, y: 0.12, influence: 0.6 },\n ];\n c.backgroundMeshSoftness = 0.75;\n c.transparentBackground = false;\n return c;\n },\n Palestine: () => {\n const c = PRESETS[\"Hero\"](); // the centred default \"Hero\" wave\n const w = c.waves[0];\n w.paletteSource = \"palestine\";\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1;\n w.colorSaturation = 1;\n c.grain = 0.35;\n c.background = \"#f2efe8\";\n c.transparentBackground = true;\n return c;\n },\n Spain: () => {\n const c = PRESETS[\"Hero\"](); // the centred default \"Hero\" wave\n const w = c.waves[0];\n w.paletteSource = \"spain\";\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.18;\n w.colorSaturation = 1.25;\n w.creaseLight = 1.6; // moderate crease-light: rich crimson without washing to salmon (Hero's is 1.98)\n c.grain = 0.3;\n c.background = \"#1a0608\"; // deep oxblood stage\n c.backgroundMode = \"color\";\n c.transparentBackground = false; // opaque, so the dark stage makes the flag pop\n return c;\n },\n \"Vaporwave Sunset\": () => {\n // The Hero wave re-posed/re-framed, plus the vaporwave palette.\n const c = PRESETS[\"Hero\"](); // the centred default \"Hero\" wave\n const w = c.waves[0];\n w.position.x = 525;\n w.rotation.x = -0.64 * RAD;\n w.rotation.z = 1.68 * RAD;\n w.paletteSource = \"vaporwave\";\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.08;\n w.colorSaturation = 1.15;\n w.creaseLight = 1.25;\n c.cameraZoom = 1.1;\n c.cameraTarget = { x: 150, y: 360, z: 0 };\n c.background = \"#09051f\";\n c.transparentBackground = false;\n return c;\n },\n Corkscrew: () => {\n // The helix mode, shown off on its own: `helixRoll` at 1 rolls the ribbon's cross-section in\n // step with the sweep, so the flat strip becomes an auger blade winding around its own length\n // axis, and `helixRadius` lifts that blade off the axis so the turns read as a screw thread\n // rather than a flat twist. No twist at all — this shape is unreachable with twistFrequency,\n // whose expStep angle is monotone and can only ramp once (see the helix docs in config/model).\n const c = PRESETS[\"Hero\"]();\n const w = c.waves[0];\n w.helixTurns = 5;\n w.helixRadius = 45;\n w.helixRoll = 1;\n w.helixPhase = 0;\n w.twistFrequency = { x: 0, y: 0, z: 0 };\n w.twistPower = { x: 4, y: 4, z: 2 };\n // A slow swell along the blade so it breathes; the corkscrew itself is static geometry.\n w.displaceAmount = 16;\n w.displaceFrequency = { x: 0.006, y: 0.0008 };\n w.speed = 0.1;\n w.position = { x: 0, y: 0, z: 0 };\n w.rotation = { x: 0, y: 0, z: 12 }; // tilt so it climbs across the frame\n w.scale = { x: 3, y: 3, z: 1.5 };\n // Mesh gradient: the colour field runs along the blade, so each turn picks up a different part\n // of the spectrum instead of the one hue a linear stop ramp would give.\n w.gradientType = \"mesh\";\n w.meshGradientPoints = [\n { color: \"#0a84ff\", x: 0.06, y: 0.9, influence: 0.68 },\n { color: \"#64d2ff\", x: 0.88, y: 0.92, influence: 0.72 },\n { color: \"#bf5af2\", x: 0.5, y: 0.64, influence: 0.58 },\n { color: \"#ff375f\", x: 0.1, y: 0.14, influence: 0.7 },\n { color: \"#ff9f0a\", x: 0.84, y: 0.12, influence: 0.74 },\n { color: \"#30d158\", x: 0.94, y: 0.5, influence: 0.54 },\n ];\n w.meshGradientSoftness = 0.68;\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.06;\n w.colorSaturation = 1.12;\n w.fiberStrength = 0.14;\n // Framed down the axis rather than side-on: the coil reads as a screw receding into the frame,\n // and each turn shows its blade face instead of an edge. cameraDistance is the orbit radius\n // (= |position − target|); the camera is orthographic, so it's the rig's dolly, not the scale —\n // cameraZoom sets that.\n c.cameraPosition = { x: -526.009, y: -285.284, z: -425.489 };\n c.cameraTarget = { x: -95.046, y: -17.053, z: -105.608 };\n c.cameraDistance = 600;\n c.cameraZoom = 1.176;\n c.grain = 0.3;\n c.blur = 0.008;\n c.bloomStrength = 0.35;\n c.bloomRadius = 0.6;\n c.bloomThreshold = 0.55;\n c.background = \"#070914\";\n c.backgroundMode = \"color\";\n c.transparentBackground = false;\n return c;\n },\n Kaleidoscope: () => {\n const c = PRESETS[\"Wave 3\"]();\n const w = c.waves[0];\n w.paletteSource = \"kaleidoscope\";\n w.blendMode = \"normal\";\n w.hueShift = 0;\n w.colorContrast = 1.05;\n w.colorSaturation = 1.12;\n c.grain = 0.5;\n return c;\n },\n};\n"],"mappings":";;;;;;;AAQA,MAAM,MAAM,MAAM,KAAK;;;;;;AAOvB,MAAa,mBAAoD;CAC/D,SAAS;EACP,OAAO;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACP,OAAO;EACP,UAAU;EACV,OAAO;EACP,MAAM;EACN,SAAS;CACX;CACA,QAAQ;EACN,OAAO;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,OAAO;EACP,MAAM;EACN,QAAQ;EACR,SAAS;EACT,MAAM;CACR;CACA,MAAM;EACJ,OAAO;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,OAAO;EACP,MAAM;EACN,QAAQ;EACR,SAAS;EACT,MAAM;CACR;CACA,QAAQ;EACN,OAAO;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,OAAO;EACP,MAAM;EACN,QAAQ;EACR,MAAM;EACN,SAAS;EACT,MAAM;CACR;CACA,WAAW;EACT,OAAO;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,OAAO;EACP,OAAO;EACP,QAAQ;EACR,SAAS;EACT,MAAM;CACR;CACA,SAAS;EACP,OAAO;EACP,MAAM;EACN,YAAY;EACZ,MAAM;EACN,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,OAAO;EACP,MAAM;EACN,QAAQ;EACR,SAAS;EACT,MAAM;CACR;AACF;;AAGA,MAAM,OAAO,QAAyB,MAAM,KAAK,KAAM;;AAGvD,MAAM,UAAU,GAAuB,MACrC,KAAK,OAAO,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC;;;;;AAMxD,SAAS,YAAY,GAoBJ;CACf,MAAM,IAAI,oBAAoB;CAC9B,MAAM,IAAI,EAAE,MAAM;CAClB,EAAE,QAAQ,EAAE;CACZ,EAAE,gBAAgB,EAAE;CACpB,EAAE,kBAAkB,EAAE;CACtB,EAAE,WAAW,EAAE,SAAS;CACxB,EAAE,oBAAoB;EAAE,GAAG,EAAE;EAAO,GAAG,EAAE;CAAM;CAC/C,EAAE,iBAAiB,EAAE;CACrB,EAAE,WAAW;EAAE,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;CAAG;CACrD,EAAE,WAAW;EAAE,GAAG,EAAE,OAAO,KAAK;EAAK,GAAG,EAAE,OAAO,KAAK;EAAK,GAAG,EAAE,OAAO,KAAK;CAAI;CAChF,EAAE,QAAQ;EAAE,GAAG,EAAE,MAAM;EAAI,GAAG,EAAE,MAAM;EAAI,GAAG,EAAE,MAAM;CAAG;CACxD,EAAE,iBAAiB;EAAE,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;CAAG;CAC3D,EAAE,aAAa;EAAE,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;EAAI,GAAG,EAAE,IAAI;CAAG;CACvD,EAAE,cAAc,EAAE,KAAK;CACvB,EAAE,kBAAkB,EAAE,KAAK;CAC3B,EAAE,iBAAiB,EAAE,KAAK;CAC1B,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE;CACnC,IAAI,EAAE,aAAa,EAAE,cAAc;CACnC,EAAE,QAAQ,EAAE;CACZ,EAAE,OAAO,EAAE;CACX,EAAE,iBAAiB;EAAE,GAAG;EAAK,GAAG;EAAG,GAAG;CAAK;CAC3C,EAAE,eAAe;EAAE,GAAG,EAAE,UAAU;EAAI,GAAG,EAAE,UAAU;EAAI,GAAG;CAAE;CAC9D,EAAE,aAAa,EAAE;CACjB,OAAO;AACT;;AAGA,MAAa,UAA8C;CAGzD,YACE,YAAY;EACV,OAAO;EACP,UAAU;EACV,KAAK;EACL,QAAQ;EACR,OAAO;EACP,OAAO;EACP,SAAS;EACT,KAAK;GAAC;GAAK;GAAQ;EAAK;EACxB,QAAQ;GAAC;GAAU;GAAU;EAAQ;EACrC,OAAO;GAAC;GAAG;GAAG;EAAC;EACf,KAAK;GAAC;GAAO;GAAM;EAAK;EACxB,KAAK;GAAC;GAAM;GAAK;EAAI;EACrB,MAAM;GAAC;GAAM;GAAO;EAAK;EACzB,OAAO;EACP,MAAM;EACN,MAAM;EACN,WAAW,CAAC,MAAM,IAAI;CACxB,CAAC;CAGH,gBAAgB,oBAAoB;CAIpC,gBACE,YAAY;EACV,OAAO;EACP,UAAU;EACV,KAAK;EACL,QAAQ;EACR,OAAO;EACP,OAAO;EACP,SAAS;EACT,KAAK;GAAC;GAAQ;GAAO;EAAK;EAC1B,QAAQ;GAAC;GAAW;GAAU;EAAS;EACvC,OAAO;GAAC;GAAG;GAAG;EAAC;EACf,KAAK;GAAC;GAAO;GAAM;EAAM;EACzB,KAAK;GAAC;GAAM;GAAM;EAAI;EACtB,MAAM;GAAC;GAAM;GAAO;EAAC;EACrB,OAAO;EACP,MAAM;EACN,MAAM;EACN,WAAW,CAAC,MAAM,EAAE;CACtB,CAAC;CACH,gBACE,YAAY;EACV,OAAO;EACP,UAAU;EACV,KAAK;EACL,QAAQ;EACR,OAAO;EACP,OAAO;EACP,SAAS;EACT,KAAK;GAAC;GAAO;GAAM;EAAK;EACxB,QAAQ;GAAC;GAAW;GAAW;EAAQ;EACvC,OAAO;GAAC;GAAQ;GAAQ;EAAM;EAC9B,KAAK;GAAC;GAAQ;GAAO;EAAM;EAC3B,KAAK;GAAC;GAAM;GAAM;EAAI;EACtB,MAAM;GAAC;GAAM;GAAO;EAAK;EACzB,OAAO;EACP,MAAM;EACN,MAAM;EACN,WAAW,CAAC,KAAK,IAAI;EACrB,aAAa;EACb,YAAY,CACV;GACE,QAAQ;GACR,MAAM;GACN,QAAQ;GACR,MAAM;GACN,SAAS;GACT,UAAU;GACV,WAAW;GACX,kBAAkB;GAClB,eAAe;EACjB,GACA;GACE,QAAQ;GACR,MAAM;GACN,QAAQ;GACR,MAAM;GACN,SAAS;GACT,UAAU;GACV,WAAW;GACX,kBAAkB;GAClB,eAAe;EACjB,CACF;CACF,CAAC;CAGH,iBAAiB;EACf,MAAM,IAAI,oBAAoB;EAC9B,EAAE,MAAM,EAAE,CAAC,QAAQ;EACnB,EAAE,QAAQ;EACV,EAAE,aAAa;EACf,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,+BAA+B;EAC7B,MAAM,IAAI,oBAAoB;EAC9B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,QAAQ;EACV,EAAE,YAAY;EACd,EAAE,UAAU,UAAU;GAAC;GAAW;GAAW;GAAW;GAAW;EAAS,CAAC;EAC7E,EAAE,cAAc;EAChB,EAAE,aAAa;EACf,EAAE,wBAAwB;EAC1B,EAAE,QAAQ,eAAe,GAAG,CAAC;EAC7B,EAAE,YAAY;EACd,OAAO;CACT;CACA,uBAAuB;EACrB,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,eAAe;EACjB,EAAE,qBAAqB;GACrB;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAK,WAAW;GAAK;GACrD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAM,WAAW;GAAK;GACrD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAM,WAAW;GAAI;GACpD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAK,WAAW;GAAK;EACvD;EACA,EAAE,uBAAuB;EACzB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,gBAAgB;EAClB,EAAE,QAAQ;EACV,EAAE,OAAO;EACT,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,qBAAqB;EAGnB,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,oBAAoB;EACtB,EAAE,eAAe;EACjB,EAAE,gBAAgB;EAClB,EAAE,UAAU;GACV;IAAE,OAAO;IAAW,KAAK;GAAE;GAC3B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAE;EAC7B;EACA,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,gBAAgB;EAClB,EAAE,QAAQ;EACV,EAAE,OAAO;EAET,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,yBAAyB;EAC3B,EAAE,2BAA2B;EAC7B,EAAE,oBAAoB,UAAU,CAAC,WAAW,SAAS,CAAC;EACtD,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,oBAAoB;EAMlB,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAElB,EAAE,eAAe;EACjB,EAAE,YAAY;EACd,EAAE,eAAe;EACjB,EAAE,eAAe;EACjB,EAAE,eAAe;EACjB,EAAE,WAAW;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EAChC,EAAE,WAAW;GAAE,GAAG;GAAM,GAAG;GAAM,GAAG;EAAE;EACtC,EAAE,QAAQ;GAAE,GAAG;GAAM,GAAG;GAAM,GAAG;EAAK;EACtC,EAAE,UAAU;EAGZ,EAAE,oBAAoB;GAAE,GAAG;GAAQ,GAAG;EAAO;EAC7C,EAAE,iBAAiB;EACnB,EAAE,eAAe;EACjB,EAAE,kBAAkB;EACpB,EAAE,QAAQ;EAGV,EAAE,aAAa;EACf,EAAE,gBAAgB;EAClB,EAAE,cAAc;EAChB,EAAE,cAAc;EAChB,EAAE,aAAa,CACb;GACE,QAAQ;GACR,MAAM;GACN,QAAQ;GACR,MAAM;GACN,SAAS;GACT,UAAU;GACV,WAAW;GACX,kBAAkB;GAClB,eAAe;EACjB,CACF;EAGA,EAAE,oBAAoB;EACtB,EAAE,eAAe;EACjB,EAAE,uBAAuB;EACzB,EAAE,qBAAqB;GACrB;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAK,WAAW;GAAI;GACnD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAI;GACrD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;EACxD;EACA,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EAEpB,EAAE,iBAAiB;EACnB,EAAE,iBAAiB;GAAE,GAAG;GAAU,GAAG;GAAS,GAAG;EAAQ;EACzD,EAAE,eAAe;GAAE,GAAG;GAAU,GAAG;GAAQ,GAAG;EAAO;EACrD,EAAE,aAAa;EAEf,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,wBAAwB;EAC1B,EAAE,QAAQ;EACV,EAAE,OAAO;EACT,EAAE,gBAAgB;EAClB,EAAE,cAAc;EAChB,EAAE,iBAAiB;EAGnB,EAAE,YAAY;GACZ,OAAO;GACP,MAAM;GACN,OAAO;GACP,MAAM;GACN,SAAS;GACT,UAAU;GACV,OAAO;GACP,MAAM;EACR;EACA,OAAO;CACT;CACA,sBAAsB;EAMpB,MAAM,IAAI,oBAAoB;EAC9B,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,wBAAwB;EAC1B,EAAE,QAAQ;EACV,EAAE,OAAO;EACT,EAAE,gBAAgB;EAClB,EAAE,iBAAiB;EACnB,EAAE,iBAAiB;GAAE,GAAG;GAAK,GAAG;GAAG,GAAG;EAAK;EAC3C,EAAE,eAAe;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EACpC,EAAE,aAAa;EAMf,MAAM,MAAM;GACV;IAEE,OAAO;IACP,SAAS;KAAC;KAAW;KAAW;IAAS;IACzC,KAAK,CAAC,MAAM,GAAG;IACf,OAAO;KAAC;KAAK;KAAK;IAAG;IACrB,KAAK;KAAC;KAAK;KAAG;IAAG;IACjB,SAAS;IACT,OAAO;KACL,mBAAmB;MAAE,GAAG;MAAQ,GAAG;KAAO;KAC1C,gBAAgB;KAChB,YAAY;MAAE,GAAG;MAAK,GAAG;MAAK,GAAG;KAAI;IACvC;GACF;GACA;IAEE,OAAO;IACP,SAAS;KAAC;KAAW;KAAW;IAAS;IACzC,KAAK,CAAC,KAAK,GAAG;IACd,OAAO;KAAC;KAAK;KAAK;IAAG;IACrB,KAAK;KAAC;KAAK;KAAG;IAAE;IAChB,SAAS;IACT,OAAO;KACL,mBAAmB;MAAE,GAAG;MAAO,GAAG;KAAM;KACxC,gBAAgB;KAChB,YAAY;MAAE,GAAG;MAAK,GAAG;MAAK,GAAG;KAAI;IACvC;GACF;GACA;IAGE,OAAO;IACP,SAAS;KAAC;KAAW;KAAW;IAAS;IACzC,KAAK,CAAC,GAAG,GAAG;IACZ,OAAO;KAAC;KAAM;KAAM;IAAI;IACxB,KAAK;KAAC;KAAG;KAAG;IAAC;IACb,SAAS;IACT,OAAO;KACL,cAAc;KACd,WAAW;KACX,cAAc;KACd,cAAc;KACd,cAAc;KACd,gBAAgB;IAClB;GACF;GACA;IAEE,OAAO;IACP,SAAS;KAAC;KAAW;KAAW;IAAS;IACzC,KAAK,CAAC,MAAM,IAAI;IAChB,OAAO;KAAC;KAAM;KAAM;IAAI;IACxB,KAAK;KAAC;KAAG;KAAG;IAAC;IACb,SAAS;IACT,OAAO;KACL,YAAY;KACZ,aAAa;KACb,WAAW;KACX,gBAAgB;KAChB,YAAY;MAAE,GAAG;MAAK,GAAG;MAAK,GAAG;KAAI;IACvC;GACF;GACA;IAEE,OAAO;IACP,SAAS;KAAC;KAAW;KAAW;IAAS;IACzC,KAAK,CAAC,KAAK,IAAI;IACf,OAAO;KAAC;KAAK;KAAM;IAAG;IACtB,KAAK;KAAC;KAAK;KAAG;IAAG;IACjB,SAAS;IACT,OAAO;KACL,mBAAmB;MAAE,GAAG;MAAO,GAAG;KAAM;KACxC,gBAAgB;KAChB,gBAAgB;MAAE,GAAG;MAAO,GAAG;MAAM,GAAG;KAAK;KAC7C,YAAY;MAAE,GAAG;MAAK,GAAG;MAAK,GAAG;KAAI;IACvC;GACF;EACF;EACA,MAAM,OAAO,EAAE,MAAM;EACrB,EAAE,QAAQ,IAAI,KAAK,GAAG,MAAM;GAC1B,MAAM,IAAI,gBAAgB,IAAI;GAC9B,EAAE,oBAAoB;GACtB,EAAE,eAAe;GACjB,EAAE,gBAAgB;GAClB,EAAE,UAAU,UAAU,EAAE,OAAO;GAC/B,EAAE,YAAY;GACd,EAAE,WAAW;GACb,EAAE,gBAAgB;GAClB,EAAE,kBAAkB;GACpB,EAAE,UAAU,EAAE;GACd,EAAE,QAAQ;IAAE,GAAG,EAAE,MAAM;IAAI,GAAG,EAAE,MAAM;IAAI,GAAG,EAAE,MAAM;GAAG;GACxD,EAAE,WAAW;IAAE,GAAG,IAAI,EAAE,IAAI,EAAE;IAAG,GAAG,IAAI,EAAE,IAAI,EAAE;IAAG,GAAG,IAAI,EAAE,IAAI,EAAE;GAAE;GACpE,EAAE,WAAW;IAAE,GAAG,EAAE,IAAI;IAAI,GAAG,EAAE,IAAI;IAAI,GAAG;GAAE;GAC9C,EAAE,OAAO,IAAI;GACb,EAAE,QAAQ;GACV,OAAO,OAAO,GAAG,EAAE,KAAK;GAMxB,MAAM,IAAI,gBAAgB,iBAAiB,EAAE,MAAM;GACnD,MAAM,OAAO;GACb,IAAI,EAAE,SAAS,MAAM,EAAE,QAAQ,OAAO,EAAE,QAAQ,MAAM,EAAE;GACxD,IAAI,EAAE,QAAQ,MAAM,EAAE,OAAO,OAAO,EAAE,OAAO,MAAM,EAAE;GACrD,IAAI,EAAE,UAAU,MAAM,EAAE,SAAS,OAAO,EAAE,SAAS,MAAM,EAAE;GAC3D,IAAI,EAAE,SAAS,MAAM,EAAE,QAAQ,OAAO,EAAE,QAAQ,MAAM,GAAI;GAC1D,EAAE,YAAY;GACd,OAAO;EACT,CAAC;EACD,EAAE,YAAY,EAAE,MAAM;EACtB,OAAO;CACT;CACA,mBAAmB;EAGjB,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,oBAAoB;EACtB,EAAE,eAAe;EACjB,EAAE,gBAAgB;EAClB,EAAE,UAAU;GACV;IAAE,OAAO;IAAW,KAAK;GAAE;GAC3B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAK;GAC9B;IAAE,OAAO;IAAW,KAAK;GAAE;EAC7B;EACA,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,gBAAgB;EAClB,EAAE,QAAQ;EACV,EAAE,OAAO;EAET,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,yBAAyB;EAC3B,EAAE,0BAA0B;EAC5B,EAAE,2BAA2B;EAC7B,EAAE,oBAAoB,UAAU,CAAC,WAAW,SAAS,CAAC;EACtD,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,cAAc;EAGZ,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,eAAe;EACjB,EAAE,qBAAqB;GACrB;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAK,WAAW;GAAK;GACpD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAI;GACrD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAM,WAAW;GAAI;GACpD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAI;EACvD;EACA,EAAE,uBAAuB;EACzB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,gBAAgB;EAClB,EAAE,QAAQ;EACV,EAAE,OAAO;EAET,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,yBAAyB;EAC3B,EAAE,uBAAuB;GACvB;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAI;GACrD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAK,WAAW;GAAK;GACpD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAK,WAAW;GAAI;GACpD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAK,WAAW;GAAI;GACnD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAI;EACvD;EACA,EAAE,yBAAyB;EAC3B,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,iBAAiB;EACf,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,gBAAgB;EAClB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,QAAQ;EACV,EAAE,aAAa;EACf,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,aAAa;EACX,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,gBAAgB;EAClB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,cAAc;EAChB,EAAE,QAAQ;EACV,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,0BAA0B;EAExB,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,SAAS,IAAI;EACf,EAAE,SAAS,IAAI,OAAQ;EACvB,EAAE,SAAS,IAAI,OAAO;EACtB,EAAE,gBAAgB;EAClB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,cAAc;EAChB,EAAE,aAAa;EACf,EAAE,eAAe;GAAE,GAAG;GAAK,GAAG;GAAK,GAAG;EAAE;EACxC,EAAE,aAAa;EACf,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,iBAAiB;EAMf,MAAM,IAAI,QAAQ,OAAO,CAAC;EAC1B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,aAAa;EACf,EAAE,cAAc;EAChB,EAAE,YAAY;EACd,EAAE,aAAa;EACf,EAAE,iBAAiB;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EACtC,EAAE,aAAa;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EAElC,EAAE,iBAAiB;EACnB,EAAE,oBAAoB;GAAE,GAAG;GAAO,GAAG;EAAO;EAC5C,EAAE,QAAQ;EACV,EAAE,WAAW;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EAChC,EAAE,WAAW;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAG;EACjC,EAAE,QAAQ;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAI;EAG/B,EAAE,eAAe;EACjB,EAAE,qBAAqB;GACrB;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAK,WAAW;GAAK;GACrD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAM,WAAW;GAAK;GACrD;IAAE,OAAO;IAAW,GAAG;IAAK,GAAG;IAAM,WAAW;GAAI;GACpD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAM,WAAW;GAAK;GACtD;IAAE,OAAO;IAAW,GAAG;IAAM,GAAG;IAAK,WAAW;GAAK;EACvD;EACA,EAAE,uBAAuB;EACzB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,gBAAgB;EAKlB,EAAE,iBAAiB;GAAE,GAAG;GAAU,GAAG;GAAU,GAAG;EAAS;EAC3D,EAAE,eAAe;GAAE,GAAG;GAAS,GAAG;GAAS,GAAG;EAAS;EACvD,EAAE,iBAAiB;EACnB,EAAE,aAAa;EACf,EAAE,QAAQ;EACV,EAAE,OAAO;EACT,EAAE,gBAAgB;EAClB,EAAE,cAAc;EAChB,EAAE,iBAAiB;EACnB,EAAE,aAAa;EACf,EAAE,iBAAiB;EACnB,EAAE,wBAAwB;EAC1B,OAAO;CACT;CACA,oBAAoB;EAClB,MAAM,IAAI,QAAQ,SAAS,CAAC;EAC5B,MAAM,IAAI,EAAE,MAAM;EAClB,EAAE,gBAAgB;EAClB,EAAE,YAAY;EACd,EAAE,WAAW;EACb,EAAE,gBAAgB;EAClB,EAAE,kBAAkB;EACpB,EAAE,QAAQ;EACV,OAAO;CACT;AACF"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
|
-
|
|
3
2
|
//#region src/renderer/WaveGeometry.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Base wave geometry — `folded()`: a flat PlaneGeometry folded into a hairpin
|
|
6
5
|
* (sideways-U) cross-section, then stood up so the fold runs along the wave's length.
|
|
7
6
|
*
|
|
8
|
-
* - Each vertex gets a half-thickness `r` (per-vertex math below): tight
|
|
9
|
-
*
|
|
7
|
+
* - Each vertex gets a half-thickness `r` (per-vertex math below): tight at the middle
|
|
8
|
+
* of the wave's length, flaring toward both ends.
|
|
10
9
|
* - The strip |x| < FOLD_X becomes a semicircular hinge; the plane's two halves bend
|
|
11
10
|
* around it into parallel arms offset to +r and -r.
|
|
12
11
|
* - Two −90° rotations (about X then Y) orient the U upright and down its length.
|
|
@@ -17,7 +16,26 @@ import * as THREE from "three";
|
|
|
17
16
|
* faces only, no vertex positions move.
|
|
18
17
|
*
|
|
19
18
|
* All further deformation (displacement, twist, transform) happens in the vertex shader
|
|
20
|
-
* on top of this base.
|
|
19
|
+
* on top of this base.
|
|
20
|
+
*
|
|
21
|
+
* UV AXES — the canonical statement, because this is easy to get backwards and the rest of
|
|
22
|
+
* the codebase reasons in uv. The plane is folded along its local x, which is the COLUMN
|
|
23
|
+
* direction (uv.x), and the two −90° rotations land world = (plane.y, plane.z, plane.x):
|
|
24
|
+
*
|
|
25
|
+
* uv.y → the ribbon's 400-unit LENGTH (world X — the axis `displaceFrequency.x` drives)
|
|
26
|
+
* uv.x → the folded ~188-unit WIDTH, wrapping the hairpin cross-section (world Z)
|
|
27
|
+
*
|
|
28
|
+
* So u runs ACROSS the fold and v runs ALONG it. The welding below corroborates it twice:
|
|
29
|
+
* the end-caps fan across columns at rows v=0 / v=subX, and the seam joins col 0 to col
|
|
30
|
+
* subX down every row — a join that necessarily runs the full length. Measured on a built
|
|
31
|
+
* mesh, the correlation of uv.y with world X is exactly 1.0, and of uv.x with world X, 0.
|
|
32
|
+
* (uv.x vs world Z also reads 0 — the fold's own signature: a monotone mapping would give
|
|
33
|
+
* ±1, but the hairpin runs out along one arm and back along the other.)
|
|
34
|
+
*
|
|
35
|
+
* Consequences that read backwards if you assume otherwise: a `NoiseBand`'s startX/endX
|
|
36
|
+
* are the SHORT axis; `edgeFeather` softens the two ends, not the long edges; the palette
|
|
37
|
+
* texture's "edge tint" lands on the ends; `parabolaPower` bunches streaks toward the long
|
|
38
|
+
* edges; and the twist X/Z falloffs run lengthwise while Y runs across the width.
|
|
21
39
|
*/
|
|
22
40
|
declare class WaveGeometry {
|
|
23
41
|
readonly geometry: THREE.BufferGeometry;
|
|
@@ -11,8 +11,8 @@ const Y_AXIS = new THREE.Vector3(0, 1, 0);
|
|
|
11
11
|
* Base wave geometry — `folded()`: a flat PlaneGeometry folded into a hairpin
|
|
12
12
|
* (sideways-U) cross-section, then stood up so the fold runs along the wave's length.
|
|
13
13
|
*
|
|
14
|
-
* - Each vertex gets a half-thickness `r` (per-vertex math below): tight
|
|
15
|
-
*
|
|
14
|
+
* - Each vertex gets a half-thickness `r` (per-vertex math below): tight at the middle
|
|
15
|
+
* of the wave's length, flaring toward both ends.
|
|
16
16
|
* - The strip |x| < FOLD_X becomes a semicircular hinge; the plane's two halves bend
|
|
17
17
|
* around it into parallel arms offset to +r and -r.
|
|
18
18
|
* - Two −90° rotations (about X then Y) orient the U upright and down its length.
|
|
@@ -23,7 +23,26 @@ const Y_AXIS = new THREE.Vector3(0, 1, 0);
|
|
|
23
23
|
* faces only, no vertex positions move.
|
|
24
24
|
*
|
|
25
25
|
* All further deformation (displacement, twist, transform) happens in the vertex shader
|
|
26
|
-
* on top of this base.
|
|
26
|
+
* on top of this base.
|
|
27
|
+
*
|
|
28
|
+
* UV AXES — the canonical statement, because this is easy to get backwards and the rest of
|
|
29
|
+
* the codebase reasons in uv. The plane is folded along its local x, which is the COLUMN
|
|
30
|
+
* direction (uv.x), and the two −90° rotations land world = (plane.y, plane.z, plane.x):
|
|
31
|
+
*
|
|
32
|
+
* uv.y → the ribbon's 400-unit LENGTH (world X — the axis `displaceFrequency.x` drives)
|
|
33
|
+
* uv.x → the folded ~188-unit WIDTH, wrapping the hairpin cross-section (world Z)
|
|
34
|
+
*
|
|
35
|
+
* So u runs ACROSS the fold and v runs ALONG it. The welding below corroborates it twice:
|
|
36
|
+
* the end-caps fan across columns at rows v=0 / v=subX, and the seam joins col 0 to col
|
|
37
|
+
* subX down every row — a join that necessarily runs the full length. Measured on a built
|
|
38
|
+
* mesh, the correlation of uv.y with world X is exactly 1.0, and of uv.x with world X, 0.
|
|
39
|
+
* (uv.x vs world Z also reads 0 — the fold's own signature: a monotone mapping would give
|
|
40
|
+
* ±1, but the hairpin runs out along one arm and back along the other.)
|
|
41
|
+
*
|
|
42
|
+
* Consequences that read backwards if you assume otherwise: a `NoiseBand`'s startX/endX
|
|
43
|
+
* are the SHORT axis; `edgeFeather` softens the two ends, not the long edges; the palette
|
|
44
|
+
* texture's "edge tint" lands on the ends; `parabolaPower` bunches streaks toward the long
|
|
45
|
+
* edges; and the twist X/Z falloffs run lengthwise while Y runs across the width.
|
|
27
46
|
*/
|
|
28
47
|
var WaveGeometry = class {
|
|
29
48
|
geometry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WaveGeometry.js","names":[],"sources":["../../src/renderer/WaveGeometry.ts"],"sourcesContent":["import * as THREE from \"three\";\n\n/** Native plane size for folded() — keep this exact (400) so the vertex\n * shader's displace/twist frequencies (calibrated to this scale) stay faithful. */\nconst NATIVE = 400;\nconst FOLD_X = 16; // |x| < 16 is the semicircular hinge; outside it the two flat arms\nconst SHIFT = NATIVE / 4; // recentre the folded cross-section along x\n\nconst X_AXIS = new THREE.Vector3(1, 0, 0);\nconst Y_AXIS = new THREE.Vector3(0, 1, 0);\n\n/**\n * Base wave geometry — `folded()`: a flat PlaneGeometry folded into a hairpin\n * (sideways-U) cross-section, then stood up so the fold runs along the wave's length.\n *\n * - Each vertex gets a half-thickness `r` (per-vertex math below): tight
|
|
1
|
+
{"version":3,"file":"WaveGeometry.js","names":[],"sources":["../../src/renderer/WaveGeometry.ts"],"sourcesContent":["import * as THREE from \"three\";\n\n/** Native plane size for folded() — keep this exact (400) so the vertex\n * shader's displace/twist frequencies (calibrated to this scale) stay faithful. */\nconst NATIVE = 400;\nconst FOLD_X = 16; // |x| < 16 is the semicircular hinge; outside it the two flat arms\nconst SHIFT = NATIVE / 4; // recentre the folded cross-section along x\n\nconst X_AXIS = new THREE.Vector3(1, 0, 0);\nconst Y_AXIS = new THREE.Vector3(0, 1, 0);\n\n/**\n * Local-Z centre of the folded ribbon's width. The fold collapses x ∈ [-NATIVE/2, NATIVE/2] onto a\n * single arm and SHIFT recentres it, which lands the width at [-100, 84] rather than symmetric\n * about 0 — so a rotation about local X through the ORIGIN would swing the ribbon's two long edges\n * to radii 100 and 84 (a visibly lopsided helix). The vertex shader's helix roll rotates about this\n * line instead, so both edges come out at equal radius.\n */\nexport const RIBBON_Z_CENTER = (SHIFT - NATIVE / 2 + (SHIFT - FOLD_X)) / 2;\n\n/**\n * Base wave geometry — `folded()`: a flat PlaneGeometry folded into a hairpin\n * (sideways-U) cross-section, then stood up so the fold runs along the wave's length.\n *\n * - Each vertex gets a half-thickness `r` (per-vertex math below): tight at the middle\n * of the wave's length, flaring toward both ends.\n * - The strip |x| < FOLD_X becomes a semicircular hinge; the plane's two halves bend\n * around it into parallel arms offset to +r and -r.\n * - Two −90° rotations (about X then Y) orient the U upright and down its length.\n *\n * folded() leaves the U open along one side and hollow at both ends, so at oblique\n * camera angles you could see straight through it. We weld the open side and cap both\n * ends with extra triangles so the mesh is a watertight solid — welding/capping adds\n * faces only, no vertex positions move.\n *\n * All further deformation (displacement, twist, transform) happens in the vertex shader\n * on top of this base.\n *\n * UV AXES — the canonical statement, because this is easy to get backwards and the rest of\n * the codebase reasons in uv. The plane is folded along its local x, which is the COLUMN\n * direction (uv.x), and the two −90° rotations land world = (plane.y, plane.z, plane.x):\n *\n * uv.y → the ribbon's 400-unit LENGTH (world X — the axis `displaceFrequency.x` drives)\n * uv.x → the folded ~188-unit WIDTH, wrapping the hairpin cross-section (world Z)\n *\n * So u runs ACROSS the fold and v runs ALONG it. The welding below corroborates it twice:\n * the end-caps fan across columns at rows v=0 / v=subX, and the seam joins col 0 to col\n * subX down every row — a join that necessarily runs the full length. Measured on a built\n * mesh, the correlation of uv.y with world X is exactly 1.0, and of uv.x with world X, 0.\n * (uv.x vs world Z also reads 0 — the fold's own signature: a monotone mapping would give\n * ±1, but the hairpin runs out along one arm and back along the other.)\n *\n * Consequences that read backwards if you assume otherwise: a `NoiseBand`'s startX/endX\n * are the SHORT axis; `edgeFeather` softens the two ends, not the long edges; the palette\n * texture's \"edge tint\" lands on the ends; `parabolaPower` bunches streaks toward the long\n * edges; and the twist X/Z falloffs run lengthwise while Y runs across the width.\n */\nexport class WaveGeometry {\n readonly geometry: THREE.BufferGeometry;\n private segments = -1;\n\n constructor(segments: number) {\n this.geometry = new THREE.BufferGeometry();\n this.resize(segments);\n }\n\n resize(segments: number): void {\n if (segments === this.segments) return;\n this.segments = segments;\n\n // subX across the fold (the cross-section), subY along the length (twice as dense).\n const subX = THREE.MathUtils.clamp(Math.round(segments), 48, 200);\n const subY = subX * 2;\n\n const plane = new THREE.PlaneGeometry(NATIVE, NATIVE, subX, subY);\n const pos = plane.attributes.position as THREE.BufferAttribute;\n const uv = plane.attributes.uv as THREE.BufferAttribute;\n const v = new THREE.Vector3();\n\n for (let i = 0; i < pos.count; i++) {\n v.fromBufferAttribute(pos, i);\n const uy = uv.getY(i);\n // r: cross-section half-thickness — tight (2) at the middle of the length, flaring (4)\n // toward both ends. The pow() term is a sharp parabolic bump peaking at uv.y = 0.5.\n const r = 4 - 2 * Math.pow(4 * uy * (1 - uy), 9.5);\n\n if (v.x < -FOLD_X) {\n v.z += r; // long arm, at +r\n } else if (v.x < FOLD_X) {\n // semicircular hinge: z sweeps +r → -r, x collapses to the bend\n v.z = Math.cos(THREE.MathUtils.mapLinear(v.x, -FOLD_X, FOLD_X, 0, Math.PI)) * r;\n v.x =\n Math.cos(THREE.MathUtils.mapLinear(v.x, -FOLD_X, FOLD_X, -Math.PI / 2, Math.PI / 2)) * r -\n FOLD_X;\n } else {\n v.z -= r; // folded-over arm, mirrored back at -r\n v.x = -v.x;\n }\n\n v.x += SHIFT;\n v.applyAxisAngle(X_AXIS, -Math.PI / 2);\n v.applyAxisAngle(Y_AXIS, -Math.PI / 2);\n pos.setXYZ(i, v.x, v.y, v.z);\n }\n pos.needsUpdate = true;\n\n // Seal the hairpin's OPEN side. folded() leaves the two arm tips unconnected — the\n // plane's u=0 and u=subX edges, which fold to adjacent tips at +r and -r — so at oblique\n // camera angles you can see through the U to the background. Weld those two edges with a\n // strip of triangles, closing the tube. No vertex positions move; this only adds faces\n // over the previously-open seam.\n const cols = subX + 1;\n const srcIdx = plane.getIndex();\n const merged = srcIdx ? Array.from(srcIdx.array as ArrayLike<number>) : [];\n // (a) Weld the U's side opening: the u=0 and u=subX edges fold to adjacent tips at ±r.\n for (let iy = 0; iy < subY; iy++) {\n const a = iy * cols; // (row iy, col 0) — arm-A tip\n const b = (iy + 1) * cols; // (row iy+1, col 0)\n const c = a + subX; // (row iy, col subX) — arm-B tip\n const d = b + subX; // (row iy+1, col subX)\n merged.push(a, c, b, b, c, d);\n }\n // (b) Cap the two length-ends (v=0 and v=subX rows): the folded sheet is a hollow channel\n // open at both ends, so an edge-on camera sees straight through it. Fan-triangulate each\n // end's U cross-section (apex = the col-0 tip) to close it — making the wave a closed solid.\n for (const row of [0, subY]) {\n const apex = row * cols;\n for (let ix = 1; ix < subX; ix++) merged.push(apex, row * cols + ix, row * cols + ix + 1);\n }\n plane.setIndex(merged);\n\n plane.computeVertexNormals();\n\n // Move the baked attributes onto our reusable geometry, then drop the temp.\n this.geometry.setIndex(plane.getIndex());\n this.geometry.setAttribute(\"position\", plane.getAttribute(\"position\"));\n this.geometry.setAttribute(\"uv\", plane.getAttribute(\"uv\"));\n this.geometry.setAttribute(\"normal\", plane.getAttribute(\"normal\"));\n this.geometry.computeBoundingSphere();\n plane.dispose();\n }\n\n dispose(): void {\n this.geometry.dispose();\n }\n}\n"],"mappings":";;;;AAIA,MAAM,SAAS;AACf,MAAM,SAAS;AACf,MAAM,QAAQ,SAAS;AAEvB,MAAM,SAAS,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;AACxC,MAAM,SAAS,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDxC,IAAa,eAAb,MAA0B;CACxB;CACA,WAAmB;CAEnB,YAAY,UAAkB;EAC5B,KAAK,WAAW,IAAI,MAAM,eAAe;EACzC,KAAK,OAAO,QAAQ;CACtB;CAEA,OAAO,UAAwB;EAC7B,IAAI,aAAa,KAAK,UAAU;EAChC,KAAK,WAAW;EAGhB,MAAM,OAAO,MAAM,UAAU,MAAM,KAAK,MAAM,QAAQ,GAAG,IAAI,GAAG;EAChE,MAAM,OAAO,OAAO;EAEpB,MAAM,QAAQ,IAAI,MAAM,cAAc,QAAQ,QAAQ,MAAM,IAAI;EAChE,MAAM,MAAM,MAAM,WAAW;EAC7B,MAAM,KAAK,MAAM,WAAW;EAC5B,MAAM,IAAI,IAAI,MAAM,QAAQ;EAE5B,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,OAAO,KAAK;GAClC,EAAE,oBAAoB,KAAK,CAAC;GAC5B,MAAM,KAAK,GAAG,KAAK,CAAC;GAGpB,MAAM,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,GAAG;GAEjD,IAAI,EAAE,IAAI,KACR,EAAE,KAAK;QACF,IAAI,EAAE,IAAI,QAAQ;IAEvB,EAAE,IAAI,KAAK,IAAI,MAAM,UAAU,UAAU,EAAE,GAAG,KAAS,QAAQ,GAAG,KAAK,EAAE,CAAC,IAAI;IAC9E,EAAE,IACA,KAAK,IAAI,MAAM,UAAU,UAAU,EAAE,GAAG,KAAS,QAAQ,CAAC,KAAK,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,IAAI,IACvF;GACJ,OAAO;IACL,EAAE,KAAK;IACP,EAAE,IAAI,CAAC,EAAE;GACX;GAEA,EAAE,KAAK;GACP,EAAE,eAAe,QAAQ,CAAC,KAAK,KAAK,CAAC;GACrC,EAAE,eAAe,QAAQ,CAAC,KAAK,KAAK,CAAC;GACrC,IAAI,OAAO,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;EAC7B;EACA,IAAI,cAAc;EAOlB,MAAM,OAAO,OAAO;EACpB,MAAM,SAAS,MAAM,SAAS;EAC9B,MAAM,SAAS,SAAS,MAAM,KAAK,OAAO,KAA0B,IAAI,CAAC;EAEzE,KAAK,IAAI,KAAK,GAAG,KAAK,MAAM,MAAM;GAChC,MAAM,IAAI,KAAK;GACf,MAAM,KAAK,KAAK,KAAK;GACrB,MAAM,IAAI,IAAI;GACd,MAAM,IAAI,IAAI;GACd,OAAO,KAAK,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;EAC9B;EAIA,KAAK,MAAM,OAAO,CAAC,GAAG,IAAI,GAAG;GAC3B,MAAM,OAAO,MAAM;GACnB,KAAK,IAAI,KAAK,GAAG,KAAK,MAAM,MAAM,OAAO,KAAK,MAAM,MAAM,OAAO,IAAI,MAAM,OAAO,KAAK,CAAC;EAC1F;EACA,MAAM,SAAS,MAAM;EAErB,MAAM,qBAAqB;EAG3B,KAAK,SAAS,SAAS,MAAM,SAAS,CAAC;EACvC,KAAK,SAAS,aAAa,YAAY,MAAM,aAAa,UAAU,CAAC;EACrE,KAAK,SAAS,aAAa,MAAM,MAAM,aAAa,IAAI,CAAC;EACzD,KAAK,SAAS,aAAa,UAAU,MAAM,aAAa,QAAQ,CAAC;EACjE,KAAK,SAAS,sBAAsB;EACpC,MAAM,QAAQ;CAChB;CAEA,UAAgB;EACd,KAAK,SAAS,QAAQ;CACxB;AACF"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CameraFit, StudioConfig, WaveConfig } from "../config/model.js";
|
|
2
2
|
import { WaveGeometry } from "./WaveGeometry.js";
|
|
3
|
+
import { ParticleField } from "./particleField.js";
|
|
3
4
|
import { InteractionController } from "./interaction.js";
|
|
4
5
|
import * as THREE from "three";
|
|
5
|
-
|
|
6
6
|
//#region src/renderer/WaveRenderer.d.ts
|
|
7
7
|
/** Reference frame (world units) the orthographic camera fills at cameraZoom 1. The wave is
|
|
8
8
|
* framed by mapping this FRAME_W × FRAME_H rectangle (centred on cameraTarget) onto the canvas,
|
|
@@ -64,8 +64,12 @@ declare class WavePalette {
|
|
|
64
64
|
type Wave = {
|
|
65
65
|
mesh: THREE.Mesh;
|
|
66
66
|
material: THREE.ShaderMaterial;
|
|
67
|
-
geometry: WaveGeometry;
|
|
67
|
+
geometry: WaveGeometry;
|
|
68
|
+
/** This wave's own 2D palette texture + optional video. */
|
|
68
69
|
palette: WavePalette;
|
|
70
|
+
/** This wave's own particle / dust field — created when its `particles.count` first goes >0,
|
|
71
|
+
* disposed at 0 / absent (the WavePalette lifecycle pattern). Undefined = no dust for this wave. */
|
|
72
|
+
particleField?: ParticleField;
|
|
69
73
|
};
|
|
70
74
|
/** Convert an sRGB hex string to a linear-space RGB vector (three's ColorManagement does the
|
|
71
75
|
* sRGB→linear conversion on parse). Exported for the studio subclass's live light-uniform push. */
|
|
@@ -90,6 +94,9 @@ declare class WaveRenderer {
|
|
|
90
94
|
private heatmapPass?;
|
|
91
95
|
private paperTexturePass?;
|
|
92
96
|
private halftoneCmykPass?;
|
|
97
|
+
private readonly fxCenter;
|
|
98
|
+
private readonly fxRight;
|
|
99
|
+
private readonly fxUp;
|
|
93
100
|
protected readonly container: HTMLElement;
|
|
94
101
|
private readonly respectReducedMotion;
|
|
95
102
|
private readonly skipIntroRamp;
|
|
@@ -260,6 +267,19 @@ declare class WaveRenderer {
|
|
|
260
267
|
private updateTime;
|
|
261
268
|
/** Render exactly one frame at the current time. */
|
|
262
269
|
renderOnce(): void;
|
|
270
|
+
/** Insert / sync / remove EACH wave's particle field — mirrors applyBloom's lazy lifecycle, per wave.
|
|
271
|
+
* An absent block or count 0 means no THREE.Points for that wave (byte-identical). The seeded buffers
|
|
272
|
+
* rebuild only when the (count, seed, edgeBias, bias) signature changes; the rest are live uniforms.
|
|
273
|
+
* configure() (the wave-shape binding) runs later in updateSceneFx, once transforms are current. */
|
|
274
|
+
private applyParticles;
|
|
275
|
+
/** Per-frame placement for every wave's particle field: derive the camera's screen basis + each
|
|
276
|
+
* wave's world centre (drift radiates from there) and bind that wave's live shape (#defines +
|
|
277
|
+
* uniforms + matrixWorld) so the dust rides the SAME deform as the ribbon. Runs after refresh()'s
|
|
278
|
+
* per-wave loop so the wave transforms/uniforms are current. No allocation; a no-op when off. */
|
|
279
|
+
private updateSceneFx;
|
|
280
|
+
/** The shape-affecting subset of waveDefines() (what the shared waveShape reads) — used to compile
|
|
281
|
+
* each wave's particle shader to match its own deform. */
|
|
282
|
+
private shapeDefines;
|
|
263
283
|
/** Create/dispose the interaction controller as config toggles interaction on/off. Called from
|
|
264
284
|
* refresh(); the compiled define set (POINTER_FX etc.) is handled separately by waveDefines(). */
|
|
265
285
|
private syncInteraction;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ensureStudioConfig } from "../config/model.js";
|
|
2
|
-
import { ditherFragmentShader, fragmentShader, halftoneCmykFragmentShader, halftoneFragmentShader, heatmapFragmentShader, innerLightFragmentShader, lineFragmentShader, paperTextureFragmentShader, postFragmentShader, postVertexShader, vertexShader } from "./shaders.js";
|
|
3
2
|
import { WaveGeometry } from "./WaveGeometry.js";
|
|
3
|
+
import { ditherFragmentShader, fragmentShader, halftoneCmykFragmentShader, halftoneFragmentShader, heatmapFragmentShader, innerLightFragmentShader, lineFragmentShader, paperTextureFragmentShader, postFragmentShader, postVertexShader, vertexShader } from "./shaders.js";
|
|
4
|
+
import { ParticleField } from "./particleField.js";
|
|
4
5
|
import { InteractionController, SCENE_APPLIERS, WAVE_APPLIERS, anyPointerFxActive, interactionActive, wavePointerFxActive, waveRipplesActive } from "./interaction.js";
|
|
5
6
|
import { PALETTE_MAPS, buildBackgroundGradientCanvas, buildBackgroundImageCanvas, buildBackgroundMeshCanvas, buildPaletteTexture, canvasToTexture, configurePaletteTexture, drawBackgroundMediaFrame, loadPaletteImage, paletteMapCanvas, paletteSignature } from "./palette.js";
|
|
6
7
|
import { buildHeroPaletteCanvas, buildHeroPaletteTexture } from "./heroPalette.js";
|
|
@@ -186,6 +187,9 @@ var WaveRenderer = class {
|
|
|
186
187
|
heatmapPass;
|
|
187
188
|
paperTexturePass;
|
|
188
189
|
halftoneCmykPass;
|
|
190
|
+
fxCenter = new THREE.Vector3();
|
|
191
|
+
fxRight = new THREE.Vector3();
|
|
192
|
+
fxUp = new THREE.Vector3();
|
|
189
193
|
container;
|
|
190
194
|
respectReducedMotion;
|
|
191
195
|
skipIntroRamp;
|
|
@@ -392,7 +396,7 @@ var WaveRenderer = class {
|
|
|
392
396
|
uEdgeFeather: { value: .1 },
|
|
393
397
|
uOpacity: { value: 1 },
|
|
394
398
|
uSquared: { value: 1 },
|
|
395
|
-
uResolution: { value: new THREE.Vector2(
|
|
399
|
+
uResolution: { value: this.renderer.getDrawingBufferSize(new THREE.Vector2()) },
|
|
396
400
|
uAmbient: { value: .45 },
|
|
397
401
|
uNumLights: { value: 1 },
|
|
398
402
|
uLightPos: { value: lightPos },
|
|
@@ -407,6 +411,17 @@ var WaveRenderer = class {
|
|
|
407
411
|
uLineDerivativePower: { value: .95 },
|
|
408
412
|
uMaxWidth: { value: 1232 },
|
|
409
413
|
uClearColor: { value: new THREE.Vector3(1, 1, 1) },
|
|
414
|
+
uHelixTurns: { value: 0 },
|
|
415
|
+
uHelixRadius: { value: 0 },
|
|
416
|
+
uHelixRoll: { value: 0 },
|
|
417
|
+
uHelixPhase: { value: 0 },
|
|
418
|
+
uRadialAmount: { value: 0 },
|
|
419
|
+
uRadialArc: { value: 160 },
|
|
420
|
+
uRadialSpread: { value: 1 },
|
|
421
|
+
uRadialRadius: { value: 40 },
|
|
422
|
+
uRadialCenter: { value: 0 },
|
|
423
|
+
uRungAmount: { value: 0 },
|
|
424
|
+
uRungThickness: { value: 1 },
|
|
410
425
|
uPointer: { value: new THREE.Vector2(0, 0) },
|
|
411
426
|
uPointerActive: { value: 0 },
|
|
412
427
|
uPointerRadius: { value: .6 },
|
|
@@ -436,6 +451,10 @@ var WaveRenderer = class {
|
|
|
436
451
|
if ((sc?.detailAmount ?? 0) !== 0 || bindsDetail) defines.DETAIL_OCTAVE = "";
|
|
437
452
|
if ((sc?.depthTint ?? 0) > 0) defines.DEPTH_TINT = "";
|
|
438
453
|
if ((sc?.edgeFeather ?? .1) !== .1) defines.EDGE_FEATHER = "";
|
|
454
|
+
const bindsHelix = sc?.interaction?.bindings?.some((b) => b.target.startsWith("helix")) ?? false;
|
|
455
|
+
if ((sc?.helixRadius ?? 0) !== 0 || (sc?.helixRoll ?? 0) !== 0 || bindsHelix) defines.HELIX = "";
|
|
456
|
+
if ((sc?.radialAmount ?? 0) !== 0) defines.RADIAL = "";
|
|
457
|
+
if (sc?.theme === "wireframe" && (sc.rungAmount ?? 0) > 0) defines.RUNGS = "";
|
|
439
458
|
if (sc && wavePointerFxActive(this.config, sc)) {
|
|
440
459
|
defines.POINTER_FX = "";
|
|
441
460
|
if (waveRipplesActive(this.config, sc)) defines.POINTER_RIPPLES = "";
|
|
@@ -496,6 +515,10 @@ var WaveRenderer = class {
|
|
|
496
515
|
s.material.dispose();
|
|
497
516
|
s.geometry.dispose();
|
|
498
517
|
s.palette.dispose();
|
|
518
|
+
if (s.particleField) {
|
|
519
|
+
this.scene.remove(s.particleField.points);
|
|
520
|
+
s.particleField.dispose();
|
|
521
|
+
}
|
|
499
522
|
}
|
|
500
523
|
this.waves = [];
|
|
501
524
|
}
|
|
@@ -514,6 +537,10 @@ var WaveRenderer = class {
|
|
|
514
537
|
s.material.dispose();
|
|
515
538
|
s.geometry.dispose();
|
|
516
539
|
s.palette.dispose();
|
|
540
|
+
if (s.particleField) {
|
|
541
|
+
this.scene.remove(s.particleField.points);
|
|
542
|
+
s.particleField.dispose();
|
|
543
|
+
}
|
|
517
544
|
}
|
|
518
545
|
while (this.waves.length < target) this.addWave();
|
|
519
546
|
const segments = this.segments;
|
|
@@ -527,6 +554,7 @@ var WaveRenderer = class {
|
|
|
527
554
|
refresh() {
|
|
528
555
|
this.applyBackground();
|
|
529
556
|
this.applyPost();
|
|
557
|
+
this.applyParticles();
|
|
530
558
|
this.syncInteraction();
|
|
531
559
|
if (!this.isCameraExternallyDriven()) {
|
|
532
560
|
const p = this.config.cameraPosition;
|
|
@@ -587,6 +615,8 @@ var WaveRenderer = class {
|
|
|
587
615
|
u.uLineAmount.value = sc.lineAmount ?? 425;
|
|
588
616
|
u.uLineThickness.value = sc.lineThickness ?? 1;
|
|
589
617
|
u.uLineDerivativePower.value = sc.lineDerivativePower ?? .95;
|
|
618
|
+
u.uRungAmount.value = sc.rungAmount ?? 0;
|
|
619
|
+
u.uRungThickness.value = sc.rungThickness ?? 1;
|
|
590
620
|
u.uMaxWidth.value = sc.maxWidth ?? 1232;
|
|
591
621
|
hexToLinearVec3(this.config.background, u.uClearColor.value);
|
|
592
622
|
u.uFiberCount.value = sc.fiberCount;
|
|
@@ -643,6 +673,15 @@ var WaveRenderer = class {
|
|
|
643
673
|
u.uTwPowX.value = sc.twistPower.x;
|
|
644
674
|
u.uTwPowY.value = sc.twistPower.y;
|
|
645
675
|
u.uTwPowZ.value = sc.twistPower.z;
|
|
676
|
+
u.uHelixTurns.value = sc.helixTurns ?? 0;
|
|
677
|
+
u.uHelixRadius.value = sc.helixRadius ?? 0;
|
|
678
|
+
u.uHelixRoll.value = sc.helixRoll ?? 0;
|
|
679
|
+
u.uHelixPhase.value = sc.helixPhase ?? 0;
|
|
680
|
+
u.uRadialAmount.value = sc.radialAmount ?? 0;
|
|
681
|
+
u.uRadialArc.value = sc.radialArc ?? 160;
|
|
682
|
+
u.uRadialSpread.value = sc.radialSpread ?? 1;
|
|
683
|
+
u.uRadialRadius.value = sc.radialRadius ?? 40;
|
|
684
|
+
u.uRadialCenter.value = sc.radialCenter ?? 0;
|
|
646
685
|
wave.mesh.scale.set(sc.scale.x, sc.scale.y, sc.scale.z);
|
|
647
686
|
wave.mesh.rotation.set(THREE.MathUtils.degToRad(sc.rotation.x), THREE.MathUtils.degToRad(sc.rotation.y), THREE.MathUtils.degToRad(sc.rotation.z));
|
|
648
687
|
wave.mesh.position.set(sc.position.x, sc.position.y, sc.position.z);
|
|
@@ -1293,16 +1332,89 @@ var WaveRenderer = class {
|
|
|
1293
1332
|
}
|
|
1294
1333
|
}
|
|
1295
1334
|
this.postPass.uniforms.uTime.value = t;
|
|
1335
|
+
for (const w of this.waves) w.particleField?.setTime(t);
|
|
1296
1336
|
}
|
|
1297
1337
|
/** Render exactly one frame at the current time. */
|
|
1298
1338
|
renderOnce() {
|
|
1299
1339
|
this.updateBackgroundVideoFrame();
|
|
1300
1340
|
this.updateTime();
|
|
1301
1341
|
this.applyInteraction();
|
|
1342
|
+
this.updateSceneFx();
|
|
1302
1343
|
this.updateClipPlanes();
|
|
1303
1344
|
this.composer.render();
|
|
1304
1345
|
this.onAfterRenderFrame();
|
|
1305
1346
|
}
|
|
1347
|
+
/** Insert / sync / remove EACH wave's particle field — mirrors applyBloom's lazy lifecycle, per wave.
|
|
1348
|
+
* An absent block or count 0 means no THREE.Points for that wave (byte-identical). The seeded buffers
|
|
1349
|
+
* rebuild only when the (count, seed, edgeBias, bias) signature changes; the rest are live uniforms.
|
|
1350
|
+
* configure() (the wave-shape binding) runs later in updateSceneFx, once transforms are current. */
|
|
1351
|
+
applyParticles() {
|
|
1352
|
+
const loop = this.config.loopSeconds ?? 0;
|
|
1353
|
+
this.waves.forEach((wave, i) => {
|
|
1354
|
+
const cfg = (this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1])?.particles;
|
|
1355
|
+
if (cfg && cfg.count > 0) {
|
|
1356
|
+
if (!wave.particleField) {
|
|
1357
|
+
wave.particleField = new ParticleField();
|
|
1358
|
+
this.scene.add(wave.particleField.points);
|
|
1359
|
+
}
|
|
1360
|
+
wave.particleField.sync(cfg, loop);
|
|
1361
|
+
} else if (wave.particleField) {
|
|
1362
|
+
this.scene.remove(wave.particleField.points);
|
|
1363
|
+
wave.particleField.dispose();
|
|
1364
|
+
wave.particleField = void 0;
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
/** Per-frame placement for every wave's particle field: derive the camera's screen basis + each
|
|
1369
|
+
* wave's world centre (drift radiates from there) and bind that wave's live shape (#defines +
|
|
1370
|
+
* uniforms + matrixWorld) so the dust rides the SAME deform as the ribbon. Runs after refresh()'s
|
|
1371
|
+
* per-wave loop so the wave transforms/uniforms are current. No allocation; a no-op when off. */
|
|
1372
|
+
updateSceneFx() {
|
|
1373
|
+
let anyField = false;
|
|
1374
|
+
for (const w of this.waves) if (w.particleField) {
|
|
1375
|
+
anyField = true;
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
if (!anyField) return;
|
|
1379
|
+
this.camera.updateMatrixWorld();
|
|
1380
|
+
this.fxRight.setFromMatrixColumn(this.camera.matrixWorld, 0).normalize();
|
|
1381
|
+
this.fxUp.setFromMatrixColumn(this.camera.matrixWorld, 1).normalize();
|
|
1382
|
+
const pr = this.renderer.getPixelRatio();
|
|
1383
|
+
this.waves.forEach((wave, i) => {
|
|
1384
|
+
const field = wave.particleField;
|
|
1385
|
+
if (!field) return;
|
|
1386
|
+
const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];
|
|
1387
|
+
wave.mesh.updateWorldMatrix(true, false);
|
|
1388
|
+
wave.mesh.getWorldPosition(this.fxCenter);
|
|
1389
|
+
const frame = {
|
|
1390
|
+
center: this.fxCenter,
|
|
1391
|
+
right: this.fxRight,
|
|
1392
|
+
up: this.fxUp
|
|
1393
|
+
};
|
|
1394
|
+
field.frame(frame, pr);
|
|
1395
|
+
field.configure({
|
|
1396
|
+
defines: this.shapeDefines(sc),
|
|
1397
|
+
uniforms: wave.material.uniforms,
|
|
1398
|
+
matrixWorld: wave.mesh.matrixWorld,
|
|
1399
|
+
speed: sc.speed,
|
|
1400
|
+
seed: sc.seed
|
|
1401
|
+
});
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
/** The shape-affecting subset of waveDefines() (what the shared waveShape reads) — used to compile
|
|
1405
|
+
* each wave's particle shader to match its own deform. */
|
|
1406
|
+
shapeDefines(sc) {
|
|
1407
|
+
const all = this.waveDefines(sc);
|
|
1408
|
+
const out = {};
|
|
1409
|
+
for (const k of [
|
|
1410
|
+
"LOOP_MOTION",
|
|
1411
|
+
"DETAIL_OCTAVE",
|
|
1412
|
+
"HELIX",
|
|
1413
|
+
"TWIST_MOTION",
|
|
1414
|
+
"RADIAL"
|
|
1415
|
+
]) if (k in all) out[k] = "";
|
|
1416
|
+
return out;
|
|
1417
|
+
}
|
|
1306
1418
|
/** Create/dispose the interaction controller as config toggles interaction on/off. Called from
|
|
1307
1419
|
* refresh(); the compiled define set (POINTER_FX etc.) is handled separately by waveDefines(). */
|
|
1308
1420
|
syncInteraction() {
|
|
@@ -1520,7 +1632,9 @@ var WaveRenderer = class {
|
|
|
1520
1632
|
const h = sc.interaction?.hover;
|
|
1521
1633
|
pointerDisp = (h?.agitate ?? 0) + Math.abs(h?.push ?? 0) + (h?.wake ?? 0) + (sc.interaction?.press?.ripple ?? 0);
|
|
1522
1634
|
}
|
|
1523
|
-
const
|
|
1635
|
+
const localRadius = bs.center.length() + bs.radius + disp + pointerDisp;
|
|
1636
|
+
const drift = wave.particleField ? Math.abs(sc.particles?.drift ?? 0) : 0;
|
|
1637
|
+
const radius = localRadius * mesh.matrixWorld.getMaxScaleOnAxis() * 1.2 + drift;
|
|
1524
1638
|
this.clipBox.expandByPoint(this.clipTmpB.copy(center).addScalar(radius));
|
|
1525
1639
|
this.clipBox.expandByPoint(this.clipTmpB.copy(center).addScalar(-radius));
|
|
1526
1640
|
}
|
|
@@ -1618,6 +1732,7 @@ var WaveRenderer = class {
|
|
|
1618
1732
|
s.material.dispose();
|
|
1619
1733
|
s.geometry.dispose();
|
|
1620
1734
|
s.palette.dispose();
|
|
1735
|
+
s.particleField?.dispose();
|
|
1621
1736
|
}
|
|
1622
1737
|
this.bloomPass?.dispose();
|
|
1623
1738
|
this.ditherPass?.dispose();
|