@wave3d/core 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/config/model.d.ts +209 -6
  2. package/dist/config/model.js +85 -3
  3. package/dist/config/model.js.map +1 -1
  4. package/dist/index.d.ts +3 -2
  5. package/dist/index.js +3 -2
  6. package/dist/presets.js +295 -0
  7. package/dist/presets.js.map +1 -1
  8. package/dist/renderer/WaveGeometry.js +21 -0
  9. package/dist/renderer/WaveGeometry.js.map +1 -1
  10. package/dist/renderer/WaveRenderer.d.ts +62 -0
  11. package/dist/renderer/WaveRenderer.js +349 -10
  12. package/dist/renderer/WaveRenderer.js.map +1 -1
  13. package/dist/renderer/WaveRendererGPU.js +32 -2
  14. package/dist/renderer/WaveRendererGPU.js.map +1 -1
  15. package/dist/renderer/interaction.js +12 -0
  16. package/dist/renderer/interaction.js.map +1 -1
  17. package/dist/renderer/particleField.js +26 -2
  18. package/dist/renderer/particleField.js.map +1 -1
  19. package/dist/renderer/particleFieldGPU.js +6 -0
  20. package/dist/renderer/particleFieldGPU.js.map +1 -1
  21. package/dist/renderer/shaders.js +748 -13
  22. package/dist/renderer/shaders.js.map +1 -1
  23. package/dist/renderer/tsl/dissolve.js +56 -0
  24. package/dist/renderer/tsl/dissolve.js.map +1 -0
  25. package/dist/renderer/tsl/particleMaterial.js +46 -8
  26. package/dist/renderer/tsl/particleMaterial.js.map +1 -1
  27. package/dist/renderer/tsl/uniforms.js +40 -1
  28. package/dist/renderer/tsl/uniforms.js.map +1 -1
  29. package/dist/renderer/tsl/waveMaterial.js +275 -26
  30. package/dist/renderer/tsl/waveMaterial.js.map +1 -1
  31. package/dist/renderer/tsl/waveShape.js +31 -10
  32. package/dist/renderer/tsl/waveShape.js.map +1 -1
  33. package/dist/renderer/wavePath.js +189 -0
  34. package/dist/renderer/wavePath.js.map +1 -0
  35. package/dist/shell/createWave.d.ts +23 -3
  36. package/dist/shell/createWave.js +5 -4
  37. package/dist/shell/createWave.js.map +1 -1
  38. package/dist/shell/probe.d.ts +28 -0
  39. package/dist/shell/probe.js +58 -11
  40. package/dist/shell/probe.js.map +1 -1
  41. package/dist/standalone/wave3d.standalone.js +3401 -2108
  42. package/dist/standalone/wave3d.standalone.webgpu.js +7372 -5848
  43. package/dist/standalone.d.ts +2 -2
  44. package/dist/standalone.js +2 -2
  45. package/dist/studio/StudioWaveRenderer.d.ts +115 -8
  46. package/dist/studio/StudioWaveRenderer.js +588 -14
  47. package/dist/studio/StudioWaveRenderer.js.map +1 -1
  48. package/dist/studio/index.d.ts +2 -2
  49. package/dist/studio/index.js.map +1 -1
  50. package/dist/studio/randomize.js +0 -1
  51. package/dist/studio/randomize.js.map +1 -1
  52. package/package.json +1 -1
  53. package/skills/wave3d/SKILL.md +142 -5
@@ -1 +1 @@
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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"}
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.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.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Framework-agnostic 3D gradient-wave renderer + config model (the engine behind Wave Studio)",
5
5
  "keywords": [
6
6
  "animation",
@@ -10,7 +10,7 @@ description: >
10
10
  metadata:
11
11
  type: core
12
12
  library: "@wave3d/core"
13
- library_version: "0.9.0"
13
+ library_version: "0.11.0"
14
14
  sources:
15
15
  - "wave3d/wave3d:README.md"
16
16
  - "wave3d/wave3d:packages/core/src/config/model.ts"
@@ -109,9 +109,134 @@ a repeating helix:
109
109
  - `helixRoll` rolls the ribbon's own cross-section in step (1 = rigid twisted ribbon), swinging its
110
110
  two long edges onto opposite sides of the axis, so **one wave becomes a ladder whose edges are
111
111
  both strands**. Add `rungAmount` (wireframe theme) for the rungs between them.
112
+ - `radialCone` (on the radial fan, not the helix) lifts the fan out of its own plane as it spreads,
113
+ turning the flat plume into a TRUMPET whose combed strands run down the slant into the throat. A
114
+ helix carries the ribbon around an axis but its WIDTH never follows the slant, so this is the only
115
+ route to a cone mouth.
116
+ - `radialSwirl` (degrees) lets the fan's ANGLE advance along the band as well as across it. Radius
117
+ already grows with uv.y, so angle growing with it too is what curls a straight arm into a SPIRAL
118
+ one wrapping the throat. Narrow the `radialArc` and a few of these, posed a quarter-turn apart,
119
+ read as one vortex.
112
120
  - Both are off at 0, and the helix code path isn't compiled unless `helixRadius` or `helixRoll` is
113
121
  non-zero — a wave without one renders byte-identically to before.
114
122
 
123
+ **Which shape control to reach for.** They overlap less than they look:
124
+
125
+ | want | use | why not the other |
126
+ | --------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
127
+ | a coil, a spring, a double helix | `helix*` | it carries the ribbon around an axis WITHOUT banking it, and it is animatable — `helixPhase` / `helixTurns` / `helixRadius` are binding targets, so scroll can spin it |
128
+ | a fan, a plume, a trumpet | `radial*` | it remaps the ribbon's WIDTH to an angle; a path only moves the centreline, so it can never splay a sheet into a sector |
129
+ | anything else: an S-curve, a knot, a ring, a throat, a shape you draw | `path` | the others bend a centreline that stays put |
130
+
131
+ A path SWEEPS with a transported frame, so it banks into its curves like a
132
+ rollercoaster track; a helix TRANSLATES around an axis, so the ribbon keeps facing the same way. Same
133
+ coil, different object — which is why both stay.
134
+
135
+ **`path` — the ribbon's centreline, and the one shape control the others cannot substitute for.**
136
+ Everything else deforms a ribbon whose centreline is fixed: the twists rotate it, the helix carries it
137
+ around an axis, the radial fan splays it. So none of them can make a ribbon that changes direction
138
+ more than once, crosses itself, or is wide here and narrow there. A path can, because it IS the
139
+ centreline.
140
+
141
+ ```ts
142
+ path: [
143
+ { x: -200, y: 0, z: -8 },
144
+ { x: 0, y: 120, z: 32, width: 0.3, twist: 45 }, // a throat, and a quarter turn of the section
145
+ { x: 200, y: 0, z: -8 },
146
+ ];
147
+ ```
148
+
149
+ - Points are where the centreline goes, in the wave's LOCAL space. The un-pathed ribbon runs
150
+ x −200 → +200 along the plane z = −8 (the folded geometry's centre), so a straight path there —
151
+ `straightPath()` — reproduces any wave exactly, twists and all. Absent ⇒ that straight ribbon.
152
+ - `width` per point is what a separate "pinch" knob would be, and `twist` (degrees) rotates the
153
+ cross-section. Both interpolate smoothly between points.
154
+ - A path whose LAST point sits on its FIRST is a closed ring. That is the whole declaration — there
155
+ is no `closed` flag, because the points already say it.
156
+ - `arcPath(turns)` builds one: `arcPath(1)` is a closed ring whose radius comes from the ribbon's own
157
+ length, `arcPath(0.35)` an open curve.
158
+ - It is swept by ARC LENGTH with a parallel-transported frame, so the strand comb stays even wherever
159
+ the points are dragged and the ribbon never flips through an inflection.
160
+
161
+ **In the studio, double-click a ribbon to shape it.** The primary gesture is dragging the RIBBON
162
+ itself: it moves like putty, with the push falling off smoothly along the length so only the part you
163
+ grabbed follows (hold Shift for a fingertip-tight push instead of a palm). The path densifies itself
164
+ as you sculpt, so there is always something local to move. Control points are still there for
165
+ precision — drag a handle, double-click a point to remove it, double-click the ribbon to insert one —
166
+ and Escape leaves. The gestures are listed on a bar while the mode is on, and the cursor says what is
167
+ under it (grab on the ribbon, pointer on a handle). The wave takes a straight path on entry, so nothing moves until you move it.
168
+
169
+ **Disintegration (`WaveConfig.dissolve`).** A front sweeps across the wave and eats it away chunk by
170
+ chunk, so the surface CRUMBLES rather than fading. Absent ⇒ intact and byte-identical.
171
+
172
+ ```ts
173
+ dissolve: { amount: 0.3, axis: "screenX", reverse: true, band: 0.55, scale: 150, blocky: 0.75, dust: 1 }
174
+ ```
175
+
176
+ - `amount` is the whole animation: 0 whole, 1 gone, whatever the `band` (fray width). It is a binding
177
+ target, so `{ source: "scroll", target: "dissolveAmount", to: 1 }` snaps the wave as the page moves.
178
+ - `axis` `"length"` / `"width"` ride the ribbon's own uv; **`"screenX"` / `"screenY"` are a straight
179
+ line on the canvas**, which is what makes a multi-wave STACK crumble as one object — give every
180
+ wave the same axis and amount and they share one edge. `reverse` flips which end goes first.
181
+ - `scale` sets how finely the sheet is diced and `blocky` the chunk character (0 organic tatters,
182
+ 1 hard quantized cells).
183
+ - `dust` pins the wave's own `particles` field to that same front: each mote is the chunk that just
184
+ left, so the dust and the holes are ONE event. Under a screen axis the debris also blows along the
185
+ sweep instead of radiating in every direction. Pair with `shape: "square"` and `blend: "normal"`
186
+ for hard dark debris — `blend` defaults to `"additive"`, which can only brighten and so is
187
+ invisible on a pale page.
188
+
189
+ **Which way the stripes run is the biggest single decision.** `lineAmount` carves at constant uv.x, so
190
+ each strand runs the ribbon's LENGTH; `rungAmount` carves at constant uv.y, so each crosses its WIDTH.
191
+ On a twisting ribbon the two look nothing alike: along-stripes just follow the flow, while
192
+ across-stripes ARE the twist made visible — they fan wide where the sheet faces the camera, pinch to a
193
+ dense line where it turns edge-on, and sweep as nested arcs through a fold. For a striped-ribbon look,
194
+ reach for rungs first and set `lineThickness: 0` to switch the lengthwise family off. `rungThickness`
195
+ is in PIXELS, so it must come DOWN as `rungAmount` goes up or the strands merge into solid; and past
196
+ ~300-400 rungs the periods go sub-pixel in a small embed, where a pixel-width stripe stops being
197
+ stable (it falls back to the strands' analytic duty cycle, which is a flat tone, not detail).
198
+
199
+ **A wireframe strand is a MASK, not a material — unless you light it.** Its colour comes from its uv
200
+ alone, so it holds one tone wherever the surface turns, which is why a dense wireframe reads as
201
+ hatching however it is posed. `lineLight` (0..1) shades it with the same derivative normal and scene
202
+ `lights` the solid theme uses, AND gives each strand a round cross-section so a specular travels along
203
+ one strand and not its neighbour — which is what separates combed thread from print. One knob, not
204
+ three: shading a flat stripe barely reads, so the rounding is not optional to it. Default 0.
205
+
206
+ **`lineDerivativePower` decides whether a close-framed wave has any ink.** It scales strand thickness
207
+ by the screen-space uv derivative, which is right for a ribbon seen whole (strands thicken where the
208
+ surface turns away, which reads as light on a curve). But the bigger a sheet gets on screen the
209
+ SMALLER its uv derivative, so framed close the strands thin to pale grey exactly where you want them
210
+ black. Set it to 0 and `lineThickness` alone is the duty cycle, crisp at any zoom.
211
+
212
+ **Wireframe ink (`theme: "wireframe"`).** The strand is a soft ramp by default, so `lineThickness`
213
+ alone goes from pale hairlines to flat solid without passing through dense ink. `lineSharpness`
214
+ (0..1) steepens it, which turns `lineThickness` into a DUTY CYCLE and gives engraved black strands
215
+ with the background still showing between them; `lineDepthFade` (default 1) turns off the recede
216
+ into the background colour, which a deep or stacked composition needs.
217
+
218
+ **`lineGapColor` is the one to know about** — it decides what sits BETWEEN the strands, which is most
219
+ of what a wireframe looks like. Absent, the gaps take the page background, so the wave is a window
220
+ onto the page (dark strands, paper between) and, being opaque, the near fold HIDES the far one, which
221
+ is what makes a stack read solid. Give it a dark colour and the ribbon becomes its own body: bright
222
+ combed lines on a dark ground. Set it to `"transparent"` (or an 8-digit hex) and the gaps go clear, so
223
+ stacked folds show through each other — airier, at the cost of that occlusion. Note that `lineAmount` counts strands across the whole of uv.x, which the
224
+ radial fan maps to `radialArc`, so a narrow arc needs a proportionally SMALLER count or the strands
225
+ go sub-pixel and average to grey. `edgeFeather` now applies to the wireframe too — without it a
226
+ ribbon ends at a flat end-cap that reads as a straight cut across the strands.
227
+
228
+ **Glass (`theme: "glass"`) is a lens over whatever is behind it — so put something there.** The
229
+ sheet draws opaque and samples the frame behind it, bent along its own normal: over a flat page it is
230
+ a silvered ribbon with a bright rim and nothing more; a gradient, an image or another wave behind it
231
+ is what makes it read. The palette only sets the HUE of what gets through — `glassTint` (default
232
+ 0.12) and `glassDensity` decide how much — and `blendMode`, `lights`, `fiber*` and noise bands are
233
+ not read at all. The knobs: `glassStrength` (bend at the silhouette, px), `glassChroma` (dispersion),
234
+ `glassRim` / `glassRimPower`, `glassSpec`, `glassIrid` + `glassFilmNm`, `glassFrost` (scatter; under
235
+ ~0.1 it is sub-pixel and skipped), `glassCaustic`, `glassLayerGain` (a fold over itself thickens),
236
+ `glassFusion` (two close sheets bend along one merged silhouette), and the liquid trio `glassRipple`
237
+ / `glassRippleScale` / `glassFlow`. `glassRipple`, `glassStrength` and `glassTint` are binding
238
+ targets.
239
+
115
240
  **React flat props** are a shortcut mapped onto `waves[0]` and the scene:
116
241
  `palette` (`string[]` | `ColorStop[]`), `fiberCount`, `fiberStrength`, `sheen`, `iridescence`,
117
242
  `displaceAmount`, `speed`, `opacity`, `blendMode`, `theme` → the first wave; `background`,
@@ -200,9 +325,10 @@ Only `blur` and `grain` are bindable from an interaction input; the others are a
200
325
 
201
326
  ## Presets
202
327
 
203
- 14 built-in presets (`@wave3d/core/presets`): **Hero**, **Wave 2**, **Wave 3**, **Wave 4**,
204
- **Wireframe**, **Neon Dark Multistrand**, **Mesh Gradient**, **Solar Bloom**, **Holographic**,
205
- **Aurora**, **Palestine**, **Spain**, **Vaporwave Sunset**, **Kaleidoscope**.
328
+ 18 built-in presets (`@wave3d/core/presets`): **Hero**, **Wave 2**, **Wave 3**, **Wave 4**,
329
+ **Wireframe**, **Neon Dark Multistrand**, **Mesh Gradient**, **Solar Bloom**, **Latte Ring**,
330
+ **Particle Zoo**, **Holographic**, **Aurora**, **Palestine**, **Spain**, **Vaporwave Sunset**,
331
+ **Corkscrew**, **Disintegration**, **Kaleidoscope**.
206
332
 
207
333
  - React: `preset="Hero"` (a **string** lazy-imports the presets chunk) or
208
334
  `preset={() => PRESETS["Hero"]()}` (a **function** is tree-shakeable — bundles only that preset).
@@ -227,7 +353,18 @@ The shell shows a poster immediately, then crossfades to the live wave.
227
353
  `@wave3d/vite/client` for React / `createWave`. Re-snapshots over HMR; `vite build` just uses the
228
354
  committed file.
229
355
  - **`onFallback(reason)`** fires when the shell keeps the poster instead of upgrading; reasons:
230
- `"no-webgl" | "reduced-motion" | "save-data" | "context-lost" | "load-error"`.
356
+ `"no-webgl" | "software-renderer" | "reduced-motion" | "save-data" | "context-lost" |
357
+ "load-error"`.
358
+ - **`webgl="auto"` upgrades only onto a GPU.** A software rasteriser (SwiftShader, llvmpipe) keeps
359
+ the poster and reports `"software-renderer"` — it can run the wave, at roughly 2 fps with seconds
360
+ of blocked main thread, which is worse for the page than the still it already has. Use
361
+ `webgl="force"` if you want the live render anyway. `probeWebGL()` and `isSoftwareRenderer(gl)`
362
+ are exported for pages that need the same answer for their own reasons; note that an unreadable
363
+ renderer string counts as HARDWARE, because the extension is hidden under some privacy settings
364
+ and guessing "software" there downgrades people silently.
365
+ - **`paused` does not decline the upgrade.** It stops FRAMES: the engine is still fetched, the
366
+ renderer still builds, ready still fires and the poster is still swapped for a static canvas.
367
+ `webgl="off"` is the one that keeps the poster and builds nothing.
231
368
  - **`onStateChange(state)`**: `"poster" → "loading" → "running"`, or `"fallback"`.
232
369
 
233
370
  ## Performance