@wave3d/core 0.6.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.
@@ -3,7 +3,7 @@ import * as THREE from "three";
3
3
  //#region src/renderer/palette.d.ts
4
4
  interface PaletteTextureOptions {
5
5
  stops: ColorStop[];
6
- /** Cross-width edge tint colour (e.g. periwinkle). */
6
+ /** Edge tint colour (e.g. periwinkle), blended toward the ribbon's two ends. */
7
7
  edgeColor: string;
8
8
  /** 0 = flat 1-D gradient (no 2nd axis); higher = stronger cool edges. */
9
9
  edgeAmount: number;
@@ -3,11 +3,13 @@ import { createDefaultMeshPoints } from "../config/model.js";
3
3
  import * as THREE from "three";
4
4
  //#region src/renderer/palette.ts
5
5
  /**
6
- * Bakes the gradient stops into a 2D palette texture, sampled in the shader as
7
- * `texture2D(u_paletteTexture, vec2(uv.x, uv.y))`. The texture is a real image, so
8
- * colour can vary independently along BOTH axes:
9
- * - X (uv.x, along the length): the gradient stops.
10
- * - Y (uv.y, across the width): an "edge tint" blended toward both long edges
6
+ * Bakes the gradient stops into a 2D palette texture. It's a real image, so colour can vary
7
+ * independently along BOTH of its axes:
8
+ * - X: the gradient ramp. A raw/image palette samples it by uv.x (the folded WIDTH — see
9
+ * WaveGeometry's UV AXES note); a stops-generated one samples it by `gradCoord(uv)`, so
10
+ * the gradient's own angle/type/warp still apply.
11
+ * - Y: an "edge tint". Always sampled by uv.y, which is the ribbon's LENGTH — so the tint
12
+ * lands on the wave's two ENDS, not on its long edges.
11
13
  */
12
14
  const TEX_W = 256;
13
15
  const TEX_H = 64;
@@ -1 +1 @@
1
- {"version":3,"file":"palette.js","names":[],"sources":["../../src/renderer/palette.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport { clamp01 } from \"../util/math\";\nimport { createDefaultMeshPoints } from \"../config/model\";\nimport type {\n BackgroundImageFit,\n BasicGradientType,\n ColorStop,\n MeshGradientPoint,\n} from \"../config/model\";\n\n/**\n * Bakes the gradient stops into a 2D palette texture, sampled in the shader as\n * `texture2D(u_paletteTexture, vec2(uv.x, uv.y))`. The texture is a real image, so\n * colour can vary independently along BOTH axes:\n * - X (uv.x, along the length): the gradient stops.\n * - Y (uv.y, across the width): an \"edge tint\" blended toward both long edges\n */\n\nconst TEX_W = 256; // resolution along the gradient (length)\nconst TEX_H = 64; // resolution across the width\n\nfunction smoothstep(value: number): number {\n return value * value * (3 - 2 * value);\n}\n\n/** \"#rrggbb\" → \"rgba(r,g,b,a)\" for canvas fills with alpha. */\nfunction hexToRgba(hex: string, alpha: number): string {\n const c = new THREE.Color(hex); // parses many formats; .r/.g/.b are linear…\n // …but we want the original sRGB bytes for the canvas, so re-encode.\n const srgb = c.clone().convertLinearToSRGB();\n const r = Math.round(srgb.r * 255);\n const g = Math.round(srgb.g * 255);\n const b = Math.round(srgb.b * 255);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nexport interface PaletteTextureOptions {\n stops: ColorStop[];\n /** Cross-width edge tint colour (e.g. periwinkle). */\n edgeColor: string;\n /** 0 = flat 1-D gradient (no 2nd axis); higher = stronger cool edges. */\n edgeAmount: number;\n}\n\n/** Draw the palette into a 2D canvas. */\nexport function buildPaletteCanvas(opts: PaletteTextureOptions): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.width = TEX_W;\n canvas.height = TEX_H;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return canvas;\n\n // X axis: the main gradient (stops sorted by position).\n const stops = [...opts.stops].sort((a, b) => a.pos - b.pos);\n const grad = ctx.createLinearGradient(0, 0, TEX_W, 0);\n if (stops.length === 0) {\n grad.addColorStop(0, \"#ffffff\");\n } else {\n for (const s of stops) grad.addColorStop(clamp01(s.pos), s.color);\n }\n ctx.fillStyle = grad;\n ctx.fillRect(0, 0, TEX_W, TEX_H);\n\n // Y axis: blend the edge colour toward both long edges (V-shaped alpha), 0 in the\n // middle — a genuine second dimension of colour.\n const a = clamp01(opts.edgeAmount);\n if (a > 0.001) {\n const eg = ctx.createLinearGradient(0, 0, 0, TEX_H);\n eg.addColorStop(0, hexToRgba(opts.edgeColor, a));\n eg.addColorStop(0.5, hexToRgba(opts.edgeColor, 0));\n eg.addColorStop(1, hexToRgba(opts.edgeColor, a));\n ctx.fillStyle = eg;\n ctx.fillRect(0, 0, TEX_W, TEX_H);\n }\n\n return canvas;\n}\n\n/** A small string that changes whenever the texture would need rebuilding. */\nexport function paletteSignature(opts: PaletteTextureOptions): string {\n const s = opts.stops.map((x) => `${x.color}@${x.pos.toFixed(3)}`).join(\",\");\n return `${s}|${opts.edgeColor}|${opts.edgeAmount.toFixed(3)}`;\n}\n\n/** Sampling config shared by every palette-texture source (canvas, image, LUT, video):\n * sRGB (the GPU linearizes on sample), linear filtering, clamped edges, no mipmaps. */\nexport function configurePaletteTexture<T extends THREE.Texture>(tex: T): T {\n tex.colorSpace = THREE.SRGBColorSpace;\n tex.minFilter = THREE.LinearFilter;\n tex.magFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n return tex;\n}\n\n/** Wrap a canvas as a sampling-ready CanvasTexture. */\nexport function canvasToTexture(canvas: HTMLCanvasElement): THREE.CanvasTexture {\n return configurePaletteTexture(new THREE.CanvasTexture(canvas));\n}\n\n/** Build a ready-to-use CanvasTexture from gradient stops + edge tint. */\nexport function buildPaletteTexture(opts: PaletteTextureOptions): THREE.CanvasTexture {\n return canvasToTexture(buildPaletteCanvas(opts));\n}\n\nexport interface BackgroundGradientOptions {\n stops: ColorStop[];\n type: BasicGradientType;\n angle: number;\n width: number;\n height: number;\n}\n\n/** Draw an export-ready linear, radial, or conic background gradient. */\nexport function buildBackgroundGradientCanvas(opts: BackgroundGradientOptions): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.round(opts.width));\n canvas.height = Math.max(1, Math.round(opts.height));\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return canvas;\n\n const { width, height } = canvas;\n const cx = width / 2;\n const cy = height / 2;\n const angle = (opts.angle * Math.PI) / 180;\n let gradient: CanvasGradient;\n if (opts.type === \"radial\") {\n gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.hypot(cx, cy));\n } else if (opts.type === \"conic\") {\n gradient = ctx.createConicGradient(angle, cx, cy);\n } else {\n const radius = Math.abs(Math.sin(angle)) * cx + Math.abs(Math.cos(angle)) * cy;\n const dx = Math.sin(angle) * radius;\n const dy = -Math.cos(angle) * radius;\n gradient = ctx.createLinearGradient(cx - dx, cy - dy, cx + dx, cy + dy);\n }\n\n const stops = [...opts.stops].sort((a, b) => a.pos - b.pos);\n if (stops.length === 0) gradient.addColorStop(0, \"#ffffff\");\n else for (const stop of stops) gradient.addColorStop(clamp01(stop.pos), stop.color);\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, width, height);\n return canvas;\n}\n\n/** Linear (0–1) → sRGB byte (0–255). */\nfunction linearToSrgbByte(linear: number): number {\n const c = clamp01(linear);\n const srgb = c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;\n return Math.round(clamp01(srgb) * 255);\n}\n\n/**\n * The single source of truth for mesh-gradient rendering — shared by the background\n * (buildBackgroundMeshCanvas) and the on-canvas MeshGradientEditor preview, and mirroring the\n * wave's meshGradient shader. An inverse-distance-weighted blend of coloured points, computed in\n * LINEAR RGB with y measured UP (point.y = 1 is the top). Returns an ImageData to putImageData\n * onto any 2D context.\n */\nexport function renderMeshGradient(\n points: MeshGradientPoint[],\n softness: number,\n width: number,\n height: number,\n): ImageData {\n const w = Math.max(1, Math.round(width));\n const h = Math.max(1, Math.round(height));\n const image = new ImageData(w, h);\n const data = image.data;\n const pts = points.length >= 2 ? points : createDefaultMeshPoints();\n const colors = pts.map((p) => new THREE.Color(p.color)); // .r/.g/.b are LINEAR\n const exponent = 4.8 + (1.35 - 4.8) * clamp01(softness);\n for (let py = 0; py < h; py++) {\n const y = 1 - py / Math.max(1, h - 1);\n for (let px = 0; px < w; px++) {\n const x = px / Math.max(1, w - 1);\n let r = 0;\n let g = 0;\n let b = 0;\n let weightSum = 0;\n for (let i = 0; i < pts.length; i++) {\n const influence = Math.max(pts[i].influence, 0.05);\n const distance = Math.hypot(x - pts[i].x, y - pts[i].y) / influence;\n const weight = 1 / (Math.pow(Math.max(distance, 0.012), exponent) + 0.002);\n r += colors[i].r * weight;\n g += colors[i].g * weight;\n b += colors[i].b * weight;\n weightSum += weight;\n }\n const iw = Math.max(weightSum, 0.0001);\n const off = (py * w + px) * 4;\n data[off] = linearToSrgbByte(r / iw);\n data[off + 1] = linearToSrgbByte(g / iw);\n data[off + 2] = linearToSrgbByte(b / iw);\n data[off + 3] = 255;\n }\n }\n return image;\n}\n\n/**\n * Draw a mesh-gradient background. Mesh gradients are low-frequency, so we render at a small\n * internal size (capped ~320 px) via renderMeshGradient and scale up smoothly.\n */\nexport function buildBackgroundMeshCanvas(\n points: MeshGradientPoint[],\n softness: number,\n width: number,\n height: number,\n): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return canvas;\n\n const scale = Math.min(1, 320 / Math.max(canvas.width, canvas.height));\n const lw = Math.max(2, Math.round(canvas.width * scale));\n const lh = Math.max(2, Math.round(canvas.height * scale));\n const buf = document.createElement(\"canvas\");\n buf.width = lw;\n buf.height = lh;\n const bctx = buf.getContext(\"2d\");\n if (!bctx) return canvas;\n bctx.putImageData(renderMeshGradient(points, softness, lw, lh), 0, 0);\n ctx.imageSmoothingEnabled = true;\n ctx.imageSmoothingQuality = \"high\";\n ctx.drawImage(buf, 0, 0, lw, lh, 0, 0, canvas.width, canvas.height);\n return canvas;\n}\n\n/** Fit a built-in canvas or uploaded image into a background-sized canvas. */\nexport function buildBackgroundImageCanvas(\n source: CanvasImageSource,\n sourceWidth: number,\n sourceHeight: number,\n width: number,\n height: number,\n fit: BackgroundImageFit,\n matte: string,\n zoom = 1,\n positionX = 0,\n positionY = 0,\n): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n drawBackgroundMediaFrame(\n canvas,\n source,\n sourceWidth,\n sourceHeight,\n fit,\n matte,\n zoom,\n positionX,\n positionY,\n );\n return canvas;\n}\n\n/** Draw one image or video frame into an existing fitted background canvas. */\nexport function drawBackgroundMediaFrame(\n canvas: HTMLCanvasElement,\n source: CanvasImageSource,\n sourceWidth: number,\n sourceHeight: number,\n fit: BackgroundImageFit,\n matte: string,\n zoom = 1,\n positionX = 0,\n positionY = 0,\n): void {\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n ctx.fillStyle = matte;\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const safeZoom = Math.max(0.1, zoom);\n const baseWidth =\n fit === \"stretch\"\n ? canvas.width\n : sourceWidth *\n (fit === \"contain\"\n ? Math.min(canvas.width / sourceWidth, canvas.height / sourceHeight)\n : Math.max(canvas.width / sourceWidth, canvas.height / sourceHeight));\n const baseHeight =\n fit === \"stretch\"\n ? canvas.height\n : sourceHeight *\n (fit === \"contain\"\n ? Math.min(canvas.width / sourceWidth, canvas.height / sourceHeight)\n : Math.max(canvas.width / sourceWidth, canvas.height / sourceHeight));\n const drawWidth = baseWidth * safeZoom;\n const drawHeight = baseHeight * safeZoom;\n const offsetX = (positionX / 100) * canvas.width;\n const offsetY = (positionY / 100) * canvas.height;\n ctx.drawImage(\n source,\n (canvas.width - drawWidth) / 2 + offsetX,\n (canvas.height - drawHeight) / 2 + offsetY,\n drawWidth,\n drawHeight,\n );\n}\n\n// ---- Built-in palette maps (pick via config.paletteSource) ----\n\nexport interface PaletteMapDef {\n label: string;\n /** \"gradient\" = a color-stop preset (1-D ramp + edge tint, reproducible with stops);\n * \"image\" = a true 2-D texture (build()) that the stops can't reproduce. */\n kind: \"gradient\" | \"image\";\n stops?: ColorStop[];\n edgeColor?: string;\n edgeAmount?: number;\n build?: (resolution?: number) => HTMLCanvasElement;\n}\n\nconst mk = (pairs: Array<[string, number]>): ColorStop[] =>\n pairs.map(([color, pos]) => ({ color, pos }));\n\n// ---- A genuine 2-D image map (procedural nebula): organic colour patches that vary in\n// BOTH axes via domain-warped value noise — the kind of thing flat stops can't make. ----\n\nfunction valueNoise2D(seed: number): (x: number, y: number) => number {\n const hash = (x: number, y: number): number => {\n let h = (x * 374761393 + y * 668265263 + seed * 1442695041) | 0;\n h = Math.imul(h ^ (h >>> 13), 1274126177);\n return ((h ^ (h >>> 16)) >>> 0) / 4294967295;\n };\n return (x, y) => {\n const xi = Math.floor(x);\n const yi = Math.floor(y);\n const u = smoothstep(x - xi);\n const v = smoothstep(y - yi);\n const a = hash(xi, yi);\n const b = hash(xi + 1, yi);\n const c = hash(xi, yi + 1);\n const d = hash(xi + 1, yi + 1);\n return a * (1 - u) * (1 - v) + b * u * (1 - v) + c * (1 - u) * v + d * u * v;\n };\n}\n\nfunction fbm(n: (x: number, y: number) => number, x: number, y: number, oct: number): number {\n let s = 0;\n let amp = 0.5;\n let f = 1;\n let tot = 0;\n for (let i = 0; i < oct; i++) {\n s += amp * n(x * f, y * f);\n tot += amp;\n f *= 2;\n amp *= 0.5;\n }\n return s / tot;\n}\n\nconst nebulaCache = new Map<number, HTMLCanvasElement>();\nfunction buildNebulaCanvas(resolution = 220): HTMLCanvasElement {\n const N = Math.max(64, Math.min(1280, Math.round(resolution)));\n const cached = nebulaCache.get(N);\n if (cached) return cached;\n const cv = document.createElement(\"canvas\");\n cv.width = N;\n cv.height = N;\n const ctx = cv.getContext(\"2d\");\n if (!ctx) return cv;\n const img = ctx.createImageData(N, N);\n const field = valueNoise2D(11);\n const warp = valueNoise2D(91);\n const bloomN = valueNoise2D(47);\n const COLORS: Array<[number, [number, number, number]]> = [\n [0.0, [34, 26, 92]], // deep indigo\n [0.24, [104, 48, 196]], // violet\n [0.46, [226, 70, 158]], // magenta\n [0.64, [255, 122, 92]], // coral\n [0.82, [255, 202, 110]], // gold\n [1.0, [70, 196, 188]], // teal\n ];\n const ramp = (t: number): [number, number, number] => {\n t = clamp01(t);\n for (let i = 1; i < COLORS.length; i++) {\n if (t <= COLORS[i][0]) {\n const [p0, c0] = COLORS[i - 1];\n const [p1, c1] = COLORS[i];\n const k = (t - p0) / (p1 - p0);\n return [\n c0[0] + (c1[0] - c0[0]) * k,\n c0[1] + (c1[1] - c0[1]) * k,\n c0[2] + (c1[2] - c0[2]) * k,\n ];\n }\n }\n return COLORS[COLORS.length - 1][1];\n };\n const S = 3.0;\n for (let y = 0; y < N; y++) {\n for (let x = 0; x < N; x++) {\n const u = (x / N) * S;\n const v = (y / N) * S;\n const wx = u + 0.7 * warp(u * 0.7 + 5, v * 0.7);\n const wy = v + 0.7 * warp(u * 0.7, v * 0.7 + 9);\n const f = fbm(field, wx, wy, 4);\n let [r, g, b] = ramp(f);\n const bloom = Math.max(0, fbm(bloomN, wx * 1.6 + 3, wy * 1.6, 3) - 0.55) * 1.7;\n r = r * (1 - bloom) + 70 * bloom;\n g = g * (1 - bloom) + 210 * bloom;\n b = b * (1 - bloom) + 200 * bloom;\n const i = (y * N + x) * 4;\n img.data[i] = r;\n img.data[i + 1] = g;\n img.data[i + 2] = b;\n img.data[i + 3] = 255;\n }\n }\n ctx.putImageData(img, 0, 0);\n nebulaCache.set(N, cv);\n return cv;\n}\n\nconst imageMapCache = new Map<string, HTMLCanvasElement>();\n\nfunction cachedImageMap(\n id: string,\n width: number,\n height: number,\n paint: (ctx: CanvasRenderingContext2D, width: number, height: number) => void,\n): HTMLCanvasElement {\n const cacheKey = `${id}|${width}x${height}`;\n const cached = imageMapCache.get(cacheKey);\n if (cached) return cached;\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (ctx) paint(ctx, width, height);\n imageMapCache.set(cacheKey, canvas);\n return canvas;\n}\n\nfunction buildVaporwaveCanvas(resolution = 240): HTMLCanvasElement {\n const width = Math.max(120, Math.min(2048, Math.round(resolution)));\n const height = Math.round((width * 2) / 3);\n return cachedImageMap(\"vaporwave\", width, height, (ctx, canvasWidth, canvasHeight) => {\n const unit = canvasWidth / 240;\n const sky = ctx.createLinearGradient(0, 0, 0, canvasHeight);\n sky.addColorStop(0, \"#120638\");\n sky.addColorStop(0.56, \"#8e2de2\");\n sky.addColorStop(1, \"#ff2fa8\");\n ctx.fillStyle = sky;\n ctx.fillRect(0, 0, canvasWidth, canvasHeight);\n\n const sunY = canvasHeight * 0.48;\n const sunRadius = canvasHeight * 0.28;\n const sun = ctx.createLinearGradient(0, sunY - sunRadius, 0, sunY + sunRadius);\n sun.addColorStop(0, \"#fff66d\");\n sun.addColorStop(1, \"#ff5cbe\");\n ctx.fillStyle = sun;\n ctx.beginPath();\n ctx.arc(canvasWidth / 2, sunY, sunRadius, 0, Math.PI * 2);\n ctx.fill();\n\n ctx.fillStyle = \"#3b126c\";\n for (let y = sunY + 3 * unit; y < sunY + sunRadius; y += 9 * unit) {\n ctx.fillRect(canvasWidth / 2 - sunRadius, y, sunRadius * 2, 4 * unit);\n }\n\n const horizon = canvasHeight * 0.72;\n ctx.fillStyle = \"#09051f\";\n ctx.fillRect(0, horizon, canvasWidth, canvasHeight - horizon);\n ctx.strokeStyle = \"#19e3ff\";\n ctx.lineWidth = Math.max(1, unit);\n ctx.globalAlpha = 0.72;\n for (let x = -canvasWidth; x <= canvasWidth * 2; x += canvasWidth / 12) {\n ctx.beginPath();\n ctx.moveTo(canvasWidth / 2, horizon);\n ctx.lineTo(x, canvasHeight);\n ctx.stroke();\n }\n for (let row = 0; row < 7; row++) {\n const t = row / 6;\n const y = horizon + (canvasHeight - horizon) * t * t;\n ctx.beginPath();\n ctx.moveTo(0, y);\n ctx.lineTo(canvasWidth, y);\n ctx.stroke();\n }\n ctx.globalAlpha = 1;\n });\n}\n\nfunction buildKaleidoscopeCanvas(resolution = 220): HTMLCanvasElement {\n const size = Math.max(110, Math.min(2048, Math.round(resolution)));\n return cachedImageMap(\"kaleidoscope\", size, size, (ctx, width, height) => {\n const unit = width / 220;\n const colors = [\"#00e5ff\", \"#6c3bff\", \"#ff2ca8\", \"#ff7a00\", \"#ffe600\", \"#16f7a6\"];\n const cx = width / 2;\n const cy = height / 2;\n const radius = Math.hypot(width, height);\n const slices = 24;\n for (let i = 0; i < slices; i++) {\n const a0 = (i / slices) * Math.PI * 2;\n const a1 = ((i + 1) / slices) * Math.PI * 2;\n ctx.fillStyle = colors[(i * 5) % colors.length];\n ctx.beginPath();\n ctx.moveTo(cx, cy);\n ctx.lineTo(cx + Math.cos(a0) * radius, cy + Math.sin(a0) * radius);\n ctx.lineTo(cx + Math.cos(a1) * radius, cy + Math.sin(a1) * radius);\n ctx.closePath();\n ctx.fill();\n }\n for (let ring = 1; ring <= 4; ring++) {\n ctx.strokeStyle = ring % 2 ? \"rgba(255,255,255,.55)\" : \"rgba(8,5,35,.6)\";\n ctx.lineWidth = 5 * unit;\n ctx.beginPath();\n ctx.arc(cx, cy, ring * 25 * unit, 0, Math.PI * 2);\n ctx.stroke();\n }\n });\n}\n\n/** Named palette maps. \"gradient\" kinds are stop presets; \"image\" kinds are true 2-D maps. */\nexport const PALETTE_MAPS: Record<string, PaletteMapDef> = {\n palestine: {\n label: \"Palestine\",\n kind: \"gradient\",\n stops: mk([\n [\"#000000\", 0],\n [\"#f7f7f2\", 0.34],\n [\"#149954\", 0.67],\n [\"#e4312b\", 1],\n ]),\n edgeColor: \"#e4312b\",\n edgeAmount: 0.22,\n },\n spain: {\n label: \"Spain\",\n kind: \"gradient\",\n stops: mk([\n [\"#aa151b\", 0], // crimson — top stripe\n [\"#aa151b\", 0.24], // hold the red band\n [\"#f1bf00\", 0.34], // into gold\n [\"#f1bf00\", 0.66], // hold the wide gold band (middle 50%)\n [\"#aa151b\", 0.76], // back to red\n [\"#aa151b\", 1], // crimson — bottom stripe\n ]),\n edgeColor: \"#7a0f14\", // deep oxblood edge tint (mirrors Palestine's red edge)\n edgeAmount: 0.22,\n },\n grandLine: {\n label: \"Grand Line\",\n kind: \"gradient\",\n stops: mk([\n [\"#071b33\", 0],\n [\"#087ea4\", 0.22],\n [\"#45d4c5\", 0.4],\n [\"#f4d35e\", 0.58],\n [\"#f2b84b\", 0.72],\n [\"#d62828\", 0.86],\n [\"#18130f\", 1],\n ]),\n edgeColor: \"#061426\",\n edgeAmount: 0.28,\n },\n vaporwave: { label: \"Vaporwave Sunset\", kind: \"image\", build: buildVaporwaveCanvas },\n kaleidoscope: { label: \"Kaleidoscope\", kind: \"image\", build: buildKaleidoscopeCanvas },\n nebula: { label: \"Nebula (2D)\", kind: \"image\", build: buildNebulaCanvas },\n sunset: {\n label: \"Sunset\",\n kind: \"gradient\",\n stops: mk([\n [\"#3b1c6b\", 0],\n [\"#8b2fa0\", 0.3],\n [\"#e0457a\", 0.56],\n [\"#ff7a3d\", 0.8],\n [\"#ffd166\", 1],\n ]),\n edgeColor: \"#2a1a6b\",\n edgeAmount: 0.3,\n },\n aurora: {\n label: \"Aurora\",\n kind: \"gradient\",\n stops: mk([\n [\"#0b3d4f\", 0],\n [\"#1fb89e\", 0.3],\n [\"#5ee0a0\", 0.52],\n [\"#4d8ef0\", 0.76],\n [\"#9b5de5\", 1],\n ]),\n edgeColor: \"#0a2540\",\n edgeAmount: 0.35,\n },\n ocean: {\n label: \"Ocean\",\n kind: \"gradient\",\n stops: mk([\n [\"#0a1f4d\", 0],\n [\"#1f6fb8\", 0.36],\n [\"#2bd0d0\", 0.66],\n [\"#a8f0e2\", 1],\n ]),\n edgeColor: \"#061233\",\n edgeAmount: 0.4,\n },\n ember: {\n label: \"Ember\",\n kind: \"gradient\",\n stops: mk([\n [\"#2a0707\", 0],\n [\"#a81e1e\", 0.34],\n [\"#ff5a2e\", 0.64],\n [\"#ffd24a\", 1],\n ]),\n edgeColor: \"#150404\",\n edgeAmount: 0.28,\n },\n iris: {\n label: \"Iris\",\n kind: \"gradient\",\n stops: mk([\n [\"#2e1065\", 0],\n [\"#7c3aed\", 0.34],\n [\"#db2777\", 0.64],\n [\"#f5b8f0\", 1],\n ]),\n edgeColor: \"#1a0840\",\n edgeAmount: 0.35,\n },\n mono: {\n label: \"Mono\",\n kind: \"gradient\",\n stops: mk([\n [\"#16161e\", 0],\n [\"#6b7280\", 0.4],\n [\"#c2c8d2\", 0.72],\n [\"#f6f8fb\", 1],\n ]),\n edgeColor: \"#0a0a12\",\n edgeAmount: 0.2,\n },\n};\n\n/** The canvas for a named map (its 2-D image, or its stops+edge gradient). */\nexport function paletteMapCanvas(def: PaletteMapDef, resolution?: number): HTMLCanvasElement {\n if (def.build) return def.build(resolution);\n return buildPaletteCanvas({\n stops: def.stops ?? [],\n edgeColor: def.edgeColor ?? \"#8e9dff\",\n edgeAmount: def.edgeAmount ?? 0,\n });\n}\n\n/** Load an arbitrary image (URL or object-URL) as a palette texture — \"bring your own map\". */\nexport function loadPaletteImage(url: string): THREE.Texture {\n return configurePaletteTexture(new THREE.TextureLoader().load(url));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"palette.js","names":[],"sources":["../../src/renderer/palette.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport { clamp01 } from \"../util/math\";\nimport { createDefaultMeshPoints } from \"../config/model\";\nimport type {\n BackgroundImageFit,\n BasicGradientType,\n ColorStop,\n MeshGradientPoint,\n} from \"../config/model\";\n\n/**\n * Bakes the gradient stops into a 2D palette texture. It's a real image, so colour can vary\n * independently along BOTH of its axes:\n * - X: the gradient ramp. A raw/image palette samples it by uv.x (the folded WIDTH — see\n * WaveGeometry's UV AXES note); a stops-generated one samples it by `gradCoord(uv)`, so\n * the gradient's own angle/type/warp still apply.\n * - Y: an \"edge tint\". Always sampled by uv.y, which is the ribbon's LENGTH — so the tint\n * lands on the wave's two ENDS, not on its long edges.\n */\n\nconst TEX_W = 256; // resolution along the gradient ramp\nconst TEX_H = 64; // resolution along the tint axis\n\nfunction smoothstep(value: number): number {\n return value * value * (3 - 2 * value);\n}\n\n/** \"#rrggbb\" → \"rgba(r,g,b,a)\" for canvas fills with alpha. */\nfunction hexToRgba(hex: string, alpha: number): string {\n const c = new THREE.Color(hex); // parses many formats; .r/.g/.b are linear…\n // …but we want the original sRGB bytes for the canvas, so re-encode.\n const srgb = c.clone().convertLinearToSRGB();\n const r = Math.round(srgb.r * 255);\n const g = Math.round(srgb.g * 255);\n const b = Math.round(srgb.b * 255);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nexport interface PaletteTextureOptions {\n stops: ColorStop[];\n /** Edge tint colour (e.g. periwinkle), blended toward the ribbon's two ends. */\n edgeColor: string;\n /** 0 = flat 1-D gradient (no 2nd axis); higher = stronger cool edges. */\n edgeAmount: number;\n}\n\n/** Draw the palette into a 2D canvas. */\nexport function buildPaletteCanvas(opts: PaletteTextureOptions): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.width = TEX_W;\n canvas.height = TEX_H;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return canvas;\n\n // X axis: the main gradient (stops sorted by position).\n const stops = [...opts.stops].sort((a, b) => a.pos - b.pos);\n const grad = ctx.createLinearGradient(0, 0, TEX_W, 0);\n if (stops.length === 0) {\n grad.addColorStop(0, \"#ffffff\");\n } else {\n for (const s of stops) grad.addColorStop(clamp01(s.pos), s.color);\n }\n ctx.fillStyle = grad;\n ctx.fillRect(0, 0, TEX_W, TEX_H);\n\n // Y axis: blend the edge colour toward both ends of the ribbon (V-shaped alpha), 0 in the\n // middle — a genuine second dimension of colour.\n const a = clamp01(opts.edgeAmount);\n if (a > 0.001) {\n const eg = ctx.createLinearGradient(0, 0, 0, TEX_H);\n eg.addColorStop(0, hexToRgba(opts.edgeColor, a));\n eg.addColorStop(0.5, hexToRgba(opts.edgeColor, 0));\n eg.addColorStop(1, hexToRgba(opts.edgeColor, a));\n ctx.fillStyle = eg;\n ctx.fillRect(0, 0, TEX_W, TEX_H);\n }\n\n return canvas;\n}\n\n/** A small string that changes whenever the texture would need rebuilding. */\nexport function paletteSignature(opts: PaletteTextureOptions): string {\n const s = opts.stops.map((x) => `${x.color}@${x.pos.toFixed(3)}`).join(\",\");\n return `${s}|${opts.edgeColor}|${opts.edgeAmount.toFixed(3)}`;\n}\n\n/** Sampling config shared by every palette-texture source (canvas, image, LUT, video):\n * sRGB (the GPU linearizes on sample), linear filtering, clamped edges, no mipmaps. */\nexport function configurePaletteTexture<T extends THREE.Texture>(tex: T): T {\n tex.colorSpace = THREE.SRGBColorSpace;\n tex.minFilter = THREE.LinearFilter;\n tex.magFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n return tex;\n}\n\n/** Wrap a canvas as a sampling-ready CanvasTexture. */\nexport function canvasToTexture(canvas: HTMLCanvasElement): THREE.CanvasTexture {\n return configurePaletteTexture(new THREE.CanvasTexture(canvas));\n}\n\n/** Build a ready-to-use CanvasTexture from gradient stops + edge tint. */\nexport function buildPaletteTexture(opts: PaletteTextureOptions): THREE.CanvasTexture {\n return canvasToTexture(buildPaletteCanvas(opts));\n}\n\nexport interface BackgroundGradientOptions {\n stops: ColorStop[];\n type: BasicGradientType;\n angle: number;\n width: number;\n height: number;\n}\n\n/** Draw an export-ready linear, radial, or conic background gradient. */\nexport function buildBackgroundGradientCanvas(opts: BackgroundGradientOptions): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.round(opts.width));\n canvas.height = Math.max(1, Math.round(opts.height));\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return canvas;\n\n const { width, height } = canvas;\n const cx = width / 2;\n const cy = height / 2;\n const angle = (opts.angle * Math.PI) / 180;\n let gradient: CanvasGradient;\n if (opts.type === \"radial\") {\n gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.hypot(cx, cy));\n } else if (opts.type === \"conic\") {\n gradient = ctx.createConicGradient(angle, cx, cy);\n } else {\n const radius = Math.abs(Math.sin(angle)) * cx + Math.abs(Math.cos(angle)) * cy;\n const dx = Math.sin(angle) * radius;\n const dy = -Math.cos(angle) * radius;\n gradient = ctx.createLinearGradient(cx - dx, cy - dy, cx + dx, cy + dy);\n }\n\n const stops = [...opts.stops].sort((a, b) => a.pos - b.pos);\n if (stops.length === 0) gradient.addColorStop(0, \"#ffffff\");\n else for (const stop of stops) gradient.addColorStop(clamp01(stop.pos), stop.color);\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, width, height);\n return canvas;\n}\n\n/** Linear (0–1) → sRGB byte (0–255). */\nfunction linearToSrgbByte(linear: number): number {\n const c = clamp01(linear);\n const srgb = c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;\n return Math.round(clamp01(srgb) * 255);\n}\n\n/**\n * The single source of truth for mesh-gradient rendering — shared by the background\n * (buildBackgroundMeshCanvas) and the on-canvas MeshGradientEditor preview, and mirroring the\n * wave's meshGradient shader. An inverse-distance-weighted blend of coloured points, computed in\n * LINEAR RGB with y measured UP (point.y = 1 is the top). Returns an ImageData to putImageData\n * onto any 2D context.\n */\nexport function renderMeshGradient(\n points: MeshGradientPoint[],\n softness: number,\n width: number,\n height: number,\n): ImageData {\n const w = Math.max(1, Math.round(width));\n const h = Math.max(1, Math.round(height));\n const image = new ImageData(w, h);\n const data = image.data;\n const pts = points.length >= 2 ? points : createDefaultMeshPoints();\n const colors = pts.map((p) => new THREE.Color(p.color)); // .r/.g/.b are LINEAR\n const exponent = 4.8 + (1.35 - 4.8) * clamp01(softness);\n for (let py = 0; py < h; py++) {\n const y = 1 - py / Math.max(1, h - 1);\n for (let px = 0; px < w; px++) {\n const x = px / Math.max(1, w - 1);\n let r = 0;\n let g = 0;\n let b = 0;\n let weightSum = 0;\n for (let i = 0; i < pts.length; i++) {\n const influence = Math.max(pts[i].influence, 0.05);\n const distance = Math.hypot(x - pts[i].x, y - pts[i].y) / influence;\n const weight = 1 / (Math.pow(Math.max(distance, 0.012), exponent) + 0.002);\n r += colors[i].r * weight;\n g += colors[i].g * weight;\n b += colors[i].b * weight;\n weightSum += weight;\n }\n const iw = Math.max(weightSum, 0.0001);\n const off = (py * w + px) * 4;\n data[off] = linearToSrgbByte(r / iw);\n data[off + 1] = linearToSrgbByte(g / iw);\n data[off + 2] = linearToSrgbByte(b / iw);\n data[off + 3] = 255;\n }\n }\n return image;\n}\n\n/**\n * Draw a mesh-gradient background. Mesh gradients are low-frequency, so we render at a small\n * internal size (capped ~320 px) via renderMeshGradient and scale up smoothly.\n */\nexport function buildBackgroundMeshCanvas(\n points: MeshGradientPoint[],\n softness: number,\n width: number,\n height: number,\n): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return canvas;\n\n const scale = Math.min(1, 320 / Math.max(canvas.width, canvas.height));\n const lw = Math.max(2, Math.round(canvas.width * scale));\n const lh = Math.max(2, Math.round(canvas.height * scale));\n const buf = document.createElement(\"canvas\");\n buf.width = lw;\n buf.height = lh;\n const bctx = buf.getContext(\"2d\");\n if (!bctx) return canvas;\n bctx.putImageData(renderMeshGradient(points, softness, lw, lh), 0, 0);\n ctx.imageSmoothingEnabled = true;\n ctx.imageSmoothingQuality = \"high\";\n ctx.drawImage(buf, 0, 0, lw, lh, 0, 0, canvas.width, canvas.height);\n return canvas;\n}\n\n/** Fit a built-in canvas or uploaded image into a background-sized canvas. */\nexport function buildBackgroundImageCanvas(\n source: CanvasImageSource,\n sourceWidth: number,\n sourceHeight: number,\n width: number,\n height: number,\n fit: BackgroundImageFit,\n matte: string,\n zoom = 1,\n positionX = 0,\n positionY = 0,\n): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n drawBackgroundMediaFrame(\n canvas,\n source,\n sourceWidth,\n sourceHeight,\n fit,\n matte,\n zoom,\n positionX,\n positionY,\n );\n return canvas;\n}\n\n/** Draw one image or video frame into an existing fitted background canvas. */\nexport function drawBackgroundMediaFrame(\n canvas: HTMLCanvasElement,\n source: CanvasImageSource,\n sourceWidth: number,\n sourceHeight: number,\n fit: BackgroundImageFit,\n matte: string,\n zoom = 1,\n positionX = 0,\n positionY = 0,\n): void {\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n ctx.fillStyle = matte;\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const safeZoom = Math.max(0.1, zoom);\n const baseWidth =\n fit === \"stretch\"\n ? canvas.width\n : sourceWidth *\n (fit === \"contain\"\n ? Math.min(canvas.width / sourceWidth, canvas.height / sourceHeight)\n : Math.max(canvas.width / sourceWidth, canvas.height / sourceHeight));\n const baseHeight =\n fit === \"stretch\"\n ? canvas.height\n : sourceHeight *\n (fit === \"contain\"\n ? Math.min(canvas.width / sourceWidth, canvas.height / sourceHeight)\n : Math.max(canvas.width / sourceWidth, canvas.height / sourceHeight));\n const drawWidth = baseWidth * safeZoom;\n const drawHeight = baseHeight * safeZoom;\n const offsetX = (positionX / 100) * canvas.width;\n const offsetY = (positionY / 100) * canvas.height;\n ctx.drawImage(\n source,\n (canvas.width - drawWidth) / 2 + offsetX,\n (canvas.height - drawHeight) / 2 + offsetY,\n drawWidth,\n drawHeight,\n );\n}\n\n// ---- Built-in palette maps (pick via config.paletteSource) ----\n\nexport interface PaletteMapDef {\n label: string;\n /** \"gradient\" = a color-stop preset (1-D ramp + edge tint, reproducible with stops);\n * \"image\" = a true 2-D texture (build()) that the stops can't reproduce. */\n kind: \"gradient\" | \"image\";\n stops?: ColorStop[];\n edgeColor?: string;\n edgeAmount?: number;\n build?: (resolution?: number) => HTMLCanvasElement;\n}\n\nconst mk = (pairs: Array<[string, number]>): ColorStop[] =>\n pairs.map(([color, pos]) => ({ color, pos }));\n\n// ---- A genuine 2-D image map (procedural nebula): organic colour patches that vary in\n// BOTH axes via domain-warped value noise — the kind of thing flat stops can't make. ----\n\nfunction valueNoise2D(seed: number): (x: number, y: number) => number {\n const hash = (x: number, y: number): number => {\n let h = (x * 374761393 + y * 668265263 + seed * 1442695041) | 0;\n h = Math.imul(h ^ (h >>> 13), 1274126177);\n return ((h ^ (h >>> 16)) >>> 0) / 4294967295;\n };\n return (x, y) => {\n const xi = Math.floor(x);\n const yi = Math.floor(y);\n const u = smoothstep(x - xi);\n const v = smoothstep(y - yi);\n const a = hash(xi, yi);\n const b = hash(xi + 1, yi);\n const c = hash(xi, yi + 1);\n const d = hash(xi + 1, yi + 1);\n return a * (1 - u) * (1 - v) + b * u * (1 - v) + c * (1 - u) * v + d * u * v;\n };\n}\n\nfunction fbm(n: (x: number, y: number) => number, x: number, y: number, oct: number): number {\n let s = 0;\n let amp = 0.5;\n let f = 1;\n let tot = 0;\n for (let i = 0; i < oct; i++) {\n s += amp * n(x * f, y * f);\n tot += amp;\n f *= 2;\n amp *= 0.5;\n }\n return s / tot;\n}\n\nconst nebulaCache = new Map<number, HTMLCanvasElement>();\nfunction buildNebulaCanvas(resolution = 220): HTMLCanvasElement {\n const N = Math.max(64, Math.min(1280, Math.round(resolution)));\n const cached = nebulaCache.get(N);\n if (cached) return cached;\n const cv = document.createElement(\"canvas\");\n cv.width = N;\n cv.height = N;\n const ctx = cv.getContext(\"2d\");\n if (!ctx) return cv;\n const img = ctx.createImageData(N, N);\n const field = valueNoise2D(11);\n const warp = valueNoise2D(91);\n const bloomN = valueNoise2D(47);\n const COLORS: Array<[number, [number, number, number]]> = [\n [0.0, [34, 26, 92]], // deep indigo\n [0.24, [104, 48, 196]], // violet\n [0.46, [226, 70, 158]], // magenta\n [0.64, [255, 122, 92]], // coral\n [0.82, [255, 202, 110]], // gold\n [1.0, [70, 196, 188]], // teal\n ];\n const ramp = (t: number): [number, number, number] => {\n t = clamp01(t);\n for (let i = 1; i < COLORS.length; i++) {\n if (t <= COLORS[i][0]) {\n const [p0, c0] = COLORS[i - 1];\n const [p1, c1] = COLORS[i];\n const k = (t - p0) / (p1 - p0);\n return [\n c0[0] + (c1[0] - c0[0]) * k,\n c0[1] + (c1[1] - c0[1]) * k,\n c0[2] + (c1[2] - c0[2]) * k,\n ];\n }\n }\n return COLORS[COLORS.length - 1][1];\n };\n const S = 3.0;\n for (let y = 0; y < N; y++) {\n for (let x = 0; x < N; x++) {\n const u = (x / N) * S;\n const v = (y / N) * S;\n const wx = u + 0.7 * warp(u * 0.7 + 5, v * 0.7);\n const wy = v + 0.7 * warp(u * 0.7, v * 0.7 + 9);\n const f = fbm(field, wx, wy, 4);\n let [r, g, b] = ramp(f);\n const bloom = Math.max(0, fbm(bloomN, wx * 1.6 + 3, wy * 1.6, 3) - 0.55) * 1.7;\n r = r * (1 - bloom) + 70 * bloom;\n g = g * (1 - bloom) + 210 * bloom;\n b = b * (1 - bloom) + 200 * bloom;\n const i = (y * N + x) * 4;\n img.data[i] = r;\n img.data[i + 1] = g;\n img.data[i + 2] = b;\n img.data[i + 3] = 255;\n }\n }\n ctx.putImageData(img, 0, 0);\n nebulaCache.set(N, cv);\n return cv;\n}\n\nconst imageMapCache = new Map<string, HTMLCanvasElement>();\n\nfunction cachedImageMap(\n id: string,\n width: number,\n height: number,\n paint: (ctx: CanvasRenderingContext2D, width: number, height: number) => void,\n): HTMLCanvasElement {\n const cacheKey = `${id}|${width}x${height}`;\n const cached = imageMapCache.get(cacheKey);\n if (cached) return cached;\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (ctx) paint(ctx, width, height);\n imageMapCache.set(cacheKey, canvas);\n return canvas;\n}\n\nfunction buildVaporwaveCanvas(resolution = 240): HTMLCanvasElement {\n const width = Math.max(120, Math.min(2048, Math.round(resolution)));\n const height = Math.round((width * 2) / 3);\n return cachedImageMap(\"vaporwave\", width, height, (ctx, canvasWidth, canvasHeight) => {\n const unit = canvasWidth / 240;\n const sky = ctx.createLinearGradient(0, 0, 0, canvasHeight);\n sky.addColorStop(0, \"#120638\");\n sky.addColorStop(0.56, \"#8e2de2\");\n sky.addColorStop(1, \"#ff2fa8\");\n ctx.fillStyle = sky;\n ctx.fillRect(0, 0, canvasWidth, canvasHeight);\n\n const sunY = canvasHeight * 0.48;\n const sunRadius = canvasHeight * 0.28;\n const sun = ctx.createLinearGradient(0, sunY - sunRadius, 0, sunY + sunRadius);\n sun.addColorStop(0, \"#fff66d\");\n sun.addColorStop(1, \"#ff5cbe\");\n ctx.fillStyle = sun;\n ctx.beginPath();\n ctx.arc(canvasWidth / 2, sunY, sunRadius, 0, Math.PI * 2);\n ctx.fill();\n\n ctx.fillStyle = \"#3b126c\";\n for (let y = sunY + 3 * unit; y < sunY + sunRadius; y += 9 * unit) {\n ctx.fillRect(canvasWidth / 2 - sunRadius, y, sunRadius * 2, 4 * unit);\n }\n\n const horizon = canvasHeight * 0.72;\n ctx.fillStyle = \"#09051f\";\n ctx.fillRect(0, horizon, canvasWidth, canvasHeight - horizon);\n ctx.strokeStyle = \"#19e3ff\";\n ctx.lineWidth = Math.max(1, unit);\n ctx.globalAlpha = 0.72;\n for (let x = -canvasWidth; x <= canvasWidth * 2; x += canvasWidth / 12) {\n ctx.beginPath();\n ctx.moveTo(canvasWidth / 2, horizon);\n ctx.lineTo(x, canvasHeight);\n ctx.stroke();\n }\n for (let row = 0; row < 7; row++) {\n const t = row / 6;\n const y = horizon + (canvasHeight - horizon) * t * t;\n ctx.beginPath();\n ctx.moveTo(0, y);\n ctx.lineTo(canvasWidth, y);\n ctx.stroke();\n }\n ctx.globalAlpha = 1;\n });\n}\n\nfunction buildKaleidoscopeCanvas(resolution = 220): HTMLCanvasElement {\n const size = Math.max(110, Math.min(2048, Math.round(resolution)));\n return cachedImageMap(\"kaleidoscope\", size, size, (ctx, width, height) => {\n const unit = width / 220;\n const colors = [\"#00e5ff\", \"#6c3bff\", \"#ff2ca8\", \"#ff7a00\", \"#ffe600\", \"#16f7a6\"];\n const cx = width / 2;\n const cy = height / 2;\n const radius = Math.hypot(width, height);\n const slices = 24;\n for (let i = 0; i < slices; i++) {\n const a0 = (i / slices) * Math.PI * 2;\n const a1 = ((i + 1) / slices) * Math.PI * 2;\n ctx.fillStyle = colors[(i * 5) % colors.length];\n ctx.beginPath();\n ctx.moveTo(cx, cy);\n ctx.lineTo(cx + Math.cos(a0) * radius, cy + Math.sin(a0) * radius);\n ctx.lineTo(cx + Math.cos(a1) * radius, cy + Math.sin(a1) * radius);\n ctx.closePath();\n ctx.fill();\n }\n for (let ring = 1; ring <= 4; ring++) {\n ctx.strokeStyle = ring % 2 ? \"rgba(255,255,255,.55)\" : \"rgba(8,5,35,.6)\";\n ctx.lineWidth = 5 * unit;\n ctx.beginPath();\n ctx.arc(cx, cy, ring * 25 * unit, 0, Math.PI * 2);\n ctx.stroke();\n }\n });\n}\n\n/** Named palette maps. \"gradient\" kinds are stop presets; \"image\" kinds are true 2-D maps. */\nexport const PALETTE_MAPS: Record<string, PaletteMapDef> = {\n palestine: {\n label: \"Palestine\",\n kind: \"gradient\",\n stops: mk([\n [\"#000000\", 0],\n [\"#f7f7f2\", 0.34],\n [\"#149954\", 0.67],\n [\"#e4312b\", 1],\n ]),\n edgeColor: \"#e4312b\",\n edgeAmount: 0.22,\n },\n spain: {\n label: \"Spain\",\n kind: \"gradient\",\n stops: mk([\n [\"#aa151b\", 0], // crimson — top stripe\n [\"#aa151b\", 0.24], // hold the red band\n [\"#f1bf00\", 0.34], // into gold\n [\"#f1bf00\", 0.66], // hold the wide gold band (middle 50%)\n [\"#aa151b\", 0.76], // back to red\n [\"#aa151b\", 1], // crimson — bottom stripe\n ]),\n edgeColor: \"#7a0f14\", // deep oxblood edge tint (mirrors Palestine's red edge)\n edgeAmount: 0.22,\n },\n grandLine: {\n label: \"Grand Line\",\n kind: \"gradient\",\n stops: mk([\n [\"#071b33\", 0],\n [\"#087ea4\", 0.22],\n [\"#45d4c5\", 0.4],\n [\"#f4d35e\", 0.58],\n [\"#f2b84b\", 0.72],\n [\"#d62828\", 0.86],\n [\"#18130f\", 1],\n ]),\n edgeColor: \"#061426\",\n edgeAmount: 0.28,\n },\n vaporwave: { label: \"Vaporwave Sunset\", kind: \"image\", build: buildVaporwaveCanvas },\n kaleidoscope: { label: \"Kaleidoscope\", kind: \"image\", build: buildKaleidoscopeCanvas },\n nebula: { label: \"Nebula (2D)\", kind: \"image\", build: buildNebulaCanvas },\n sunset: {\n label: \"Sunset\",\n kind: \"gradient\",\n stops: mk([\n [\"#3b1c6b\", 0],\n [\"#8b2fa0\", 0.3],\n [\"#e0457a\", 0.56],\n [\"#ff7a3d\", 0.8],\n [\"#ffd166\", 1],\n ]),\n edgeColor: \"#2a1a6b\",\n edgeAmount: 0.3,\n },\n aurora: {\n label: \"Aurora\",\n kind: \"gradient\",\n stops: mk([\n [\"#0b3d4f\", 0],\n [\"#1fb89e\", 0.3],\n [\"#5ee0a0\", 0.52],\n [\"#4d8ef0\", 0.76],\n [\"#9b5de5\", 1],\n ]),\n edgeColor: \"#0a2540\",\n edgeAmount: 0.35,\n },\n ocean: {\n label: \"Ocean\",\n kind: \"gradient\",\n stops: mk([\n [\"#0a1f4d\", 0],\n [\"#1f6fb8\", 0.36],\n [\"#2bd0d0\", 0.66],\n [\"#a8f0e2\", 1],\n ]),\n edgeColor: \"#061233\",\n edgeAmount: 0.4,\n },\n ember: {\n label: \"Ember\",\n kind: \"gradient\",\n stops: mk([\n [\"#2a0707\", 0],\n [\"#a81e1e\", 0.34],\n [\"#ff5a2e\", 0.64],\n [\"#ffd24a\", 1],\n ]),\n edgeColor: \"#150404\",\n edgeAmount: 0.28,\n },\n iris: {\n label: \"Iris\",\n kind: \"gradient\",\n stops: mk([\n [\"#2e1065\", 0],\n [\"#7c3aed\", 0.34],\n [\"#db2777\", 0.64],\n [\"#f5b8f0\", 1],\n ]),\n edgeColor: \"#1a0840\",\n edgeAmount: 0.35,\n },\n mono: {\n label: \"Mono\",\n kind: \"gradient\",\n stops: mk([\n [\"#16161e\", 0],\n [\"#6b7280\", 0.4],\n [\"#c2c8d2\", 0.72],\n [\"#f6f8fb\", 1],\n ]),\n edgeColor: \"#0a0a12\",\n edgeAmount: 0.2,\n },\n};\n\n/** The canvas for a named map (its 2-D image, or its stops+edge gradient). */\nexport function paletteMapCanvas(def: PaletteMapDef, resolution?: number): HTMLCanvasElement {\n if (def.build) return def.build(resolution);\n return buildPaletteCanvas({\n stops: def.stops ?? [],\n edgeColor: def.edgeColor ?? \"#8e9dff\",\n edgeAmount: def.edgeAmount ?? 0,\n });\n}\n\n/** Load an arbitrary image (URL or object-URL) as a palette texture — \"bring your own map\". */\nexport function loadPaletteImage(url: string): THREE.Texture {\n return configurePaletteTexture(new THREE.TextureLoader().load(url));\n}\n"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { ParticlesConfig } from "../config/model.js";
2
+ import * as THREE from "three";
3
+ //#region src/renderer/particleField.d.ts
4
+ /**
5
+ * A WAVE's additive particle / dust field: a single {@link THREE.Points} whose every particle is placed
6
+ * and animated ENTIRELY in the vertex shader from `uTime` + baked per-particle attributes, so the field
7
+ * is deterministic — the same `(count, seed, edgeBias, bias)` yields byte-identical buffers, and all
8
+ * motion is a pure function of the scene time `t` (so timeOffset scrub / loopSeconds / paused reproduce).
9
+ *
10
+ * One field belongs to ONE wave (created/disposed alongside it, like {@link WavePalette}). Every particle
11
+ * spawns on that wave's DEFORMED surface / edge (via the shared waveShape chunk, riding the exact deform
12
+ * the ribbon uses) and drifts outward from the wave centre. Byte-identical when off: absent
13
+ * `wave.particles` ⇒ the renderer never creates a field.
14
+ */
15
+ interface ParticleFrame {
16
+ /** The owning wave's world-space centre — drift radiates from here. */
17
+ center: THREE.Vector3;
18
+ /** Screen-right / screen-up unit vectors (world space) for screen-relative motion. */
19
+ right: THREE.Vector3;
20
+ up: THREE.Vector3;
21
+ }
22
+ declare class ParticleField {
23
+ readonly points: THREE.Points;
24
+ private readonly geometry;
25
+ private readonly material;
26
+ /** Layout signature — only these rebuild the seeded buffers; the rest are live uniforms. */
27
+ private sig;
28
+ constructor();
29
+ /** Reconcile to `cfg`: rebuild the seeded buffers if the layout signature changed, then push the
30
+ * frame-independent uniforms. `loopSeconds` is scene-level (passed in). Called from refresh(). */
31
+ sync(cfg: ParticlesConfig, loopSeconds: number): void;
32
+ /** Push the per-frame frame basis (the camera can move, so this runs every frame). */
33
+ frame(f: ParticleFrame, pixelRatio: number): void;
34
+ /** Bind the OWNING wave's shape: mirror its shape #defines + shape uniforms + world matrix so the
35
+ * dust rides the SAME deform as the ribbon. Recompiles the point program only when the define set
36
+ * changes. Called from refresh() each frame with the wave's live state. */
37
+ configure(shape: {
38
+ defines: Record<string, string>;
39
+ uniforms: Record<string, THREE.IUniform>;
40
+ matrixWorld: THREE.Matrix4;
41
+ speed: number;
42
+ seed: number;
43
+ }): void;
44
+ /** Advance the field to scene time `t` (= the same `t` the waves get). */
45
+ setTime(t: number): void;
46
+ dispose(): void;
47
+ }
48
+ //#endregion
49
+ export { ParticleField, ParticleFrame };
50
+ //# sourceMappingURL=particleField.d.ts.map
@@ -0,0 +1,220 @@
1
+ import { particleFragmentShader, particleVertexShader } from "./shaders.js";
2
+ import * as THREE from "three";
3
+ //#region src/renderer/particleField.ts
4
+ /** Deterministic PRNG (mulberry32): a pure function of the seed, so a `(count, seed)` layout
5
+ * reproduces exactly. NEVER Math.random — that would desync timeOffset scrub / loop / paused. */
6
+ function mulberry32(seed) {
7
+ let a = seed >>> 0;
8
+ return () => {
9
+ a |= 0;
10
+ a = a + 1831565813 | 0;
11
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
12
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
13
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
14
+ };
15
+ }
16
+ const HEX_SCRATCH = new THREE.Color();
17
+ function setLinear(target, hex) {
18
+ const c = HEX_SCRATCH.set(hex);
19
+ target.set(c.r, c.g, c.b);
20
+ }
21
+ const DEFAULT_COLOR = "#ffcf8a";
22
+ /** Sprite-shape name → the int the fragment shader branches on (see particleFragmentShader). */
23
+ const SHAPE_INDEX = {
24
+ glitter: 0,
25
+ soft: 1,
26
+ ring: 2,
27
+ star: 3,
28
+ streak: 4
29
+ };
30
+ /** The owning wave's shape uniforms mirrored onto the particle material so the dust rides the same
31
+ * deform as the ribbon. Names match the wave material's uniforms 1:1 (copied by value in configure). */
32
+ const SHAPE_UNIFORMS = [
33
+ "uDispFreqX",
34
+ "uDispFreqZ",
35
+ "uDispAmount",
36
+ "uDetailFreq",
37
+ "uDetailAmount",
38
+ "uTwFreqX",
39
+ "uTwFreqY",
40
+ "uTwFreqZ",
41
+ "uTwPowX",
42
+ "uTwPowY",
43
+ "uTwPowZ",
44
+ "uHelixTurns",
45
+ "uHelixRadius",
46
+ "uHelixRoll",
47
+ "uHelixPhase",
48
+ "uRadialAmount",
49
+ "uRadialArc",
50
+ "uRadialSpread",
51
+ "uRadialRadius",
52
+ "uRadialCenter"
53
+ ];
54
+ /** Build the seeded per-particle attribute buffers. Pure function of `(count, seed, edgeBias, bias)` —
55
+ * exported so a unit test can assert reproducibility without a GPU. */
56
+ function buildParticleAttributes(count, seed, edgeBias = 1, bias = 0) {
57
+ const rand = mulberry32(seed >>> 0 || 1);
58
+ const position = new Float32Array(count * 3);
59
+ const aSeed = new Float32Array(count);
60
+ const aRnd = new Float32Array(count * 4);
61
+ const aUv = new Float32Array(count * 2);
62
+ for (let i = 0; i < count; i++) {
63
+ aSeed[i] = rand();
64
+ aRnd[i * 4 + 0] = rand();
65
+ aRnd[i * 4 + 1] = rand();
66
+ aRnd[i * 4 + 2] = rand();
67
+ aRnd[i * 4 + 3] = rand();
68
+ }
69
+ const pexp = bias === 0 ? 1 : Math.exp(-bias * 2);
70
+ const eb = Math.min(Math.max(edgeBias, 0), 1);
71
+ for (let i = 0; i < count; i++) {
72
+ const ux = rand();
73
+ aUv[i * 2 + 0] = bias === 0 ? ux : Math.pow(ux, pexp);
74
+ const e = rand();
75
+ const rim = 1 - e * e * .45;
76
+ aUv[i * 2 + 1] = e + (rim - e) * eb;
77
+ }
78
+ return {
79
+ position,
80
+ aSeed,
81
+ aRnd,
82
+ aUv
83
+ };
84
+ }
85
+ var ParticleField = class {
86
+ points;
87
+ geometry = new THREE.BufferGeometry();
88
+ material;
89
+ /** Layout signature — only these rebuild the seeded buffers; the rest are live uniforms. */
90
+ sig = "";
91
+ constructor() {
92
+ this.material = new THREE.ShaderMaterial({
93
+ uniforms: {
94
+ uTime: { value: 0 },
95
+ uLoopSeconds: { value: 0 },
96
+ uLife: { value: 6 },
97
+ uPartSpeed: { value: 1 },
98
+ uSize: { value: 2 },
99
+ uSizeJitter: { value: 0 },
100
+ uTwinkle: { value: 0 },
101
+ uPixelRatio: { value: 1 },
102
+ uColor: { value: new THREE.Vector3(1, .81, .54) },
103
+ uColor2: { value: new THREE.Vector3(1, .81, .54) },
104
+ uCenter: { value: new THREE.Vector3() },
105
+ uRight: { value: new THREE.Vector3(1, 0, 0) },
106
+ uUp: { value: new THREE.Vector3(0, 1, 0) },
107
+ uDrift: { value: 0 },
108
+ uRise: { value: 0 },
109
+ uSwirl: { value: 0 },
110
+ uWander: { value: 0 },
111
+ uShape: { value: 0 },
112
+ uDispFreqX: { value: 0 },
113
+ uDispFreqZ: { value: 0 },
114
+ uDispAmount: { value: 0 },
115
+ uDetailFreq: { value: 0 },
116
+ uDetailAmount: { value: 0 },
117
+ uTwFreqX: { value: 0 },
118
+ uTwFreqY: { value: 0 },
119
+ uTwFreqZ: { value: 0 },
120
+ uTwPowX: { value: 0 },
121
+ uTwPowY: { value: 0 },
122
+ uTwPowZ: { value: 0 },
123
+ uHelixTurns: { value: 0 },
124
+ uHelixRadius: { value: 0 },
125
+ uHelixRoll: { value: 0 },
126
+ uHelixPhase: { value: 0 },
127
+ uRadialAmount: { value: 0 },
128
+ uRadialArc: { value: 0 },
129
+ uRadialSpread: { value: 0 },
130
+ uRadialRadius: { value: 0 },
131
+ uRadialCenter: { value: 0 },
132
+ uShedModel: { value: new THREE.Matrix4() },
133
+ uShedSpeed: { value: 0 },
134
+ uShedSeed: { value: 0 }
135
+ },
136
+ vertexShader: particleVertexShader,
137
+ fragmentShader: particleFragmentShader,
138
+ transparent: true,
139
+ depthTest: false,
140
+ depthWrite: false,
141
+ blending: THREE.AdditiveBlending
142
+ });
143
+ this.points = new THREE.Points(this.geometry, this.material);
144
+ this.points.frustumCulled = false;
145
+ this.points.renderOrder = 10;
146
+ }
147
+ /** Reconcile to `cfg`: rebuild the seeded buffers if the layout signature changed, then push the
148
+ * frame-independent uniforms. `loopSeconds` is scene-level (passed in). Called from refresh(). */
149
+ sync(cfg, loopSeconds) {
150
+ const count = Math.max(0, Math.floor(cfg.count));
151
+ const edgeBias = cfg.edgeBias ?? 1;
152
+ const bias = cfg.bias ?? 0;
153
+ const sig = `${count}|${cfg.seed}|${edgeBias}|${bias}`;
154
+ if (sig !== this.sig) {
155
+ this.sig = sig;
156
+ const { position, aSeed, aRnd, aUv } = buildParticleAttributes(count, cfg.seed, edgeBias, bias);
157
+ this.geometry.setAttribute("position", new THREE.BufferAttribute(position, 3));
158
+ this.geometry.setAttribute("aSeed", new THREE.BufferAttribute(aSeed, 1));
159
+ this.geometry.setAttribute("aRnd", new THREE.BufferAttribute(aRnd, 4));
160
+ this.geometry.setAttribute("aUv", new THREE.BufferAttribute(aUv, 2));
161
+ this.geometry.setDrawRange(0, count);
162
+ }
163
+ const u = this.material.uniforms;
164
+ u.uLoopSeconds.value = loopSeconds;
165
+ u.uLife.value = cfg.life ?? 6;
166
+ u.uPartSpeed.value = cfg.speed ?? 1;
167
+ u.uSize.value = cfg.size;
168
+ u.uSizeJitter.value = cfg.sizeJitter ?? 0;
169
+ u.uTwinkle.value = cfg.twinkle ?? 0;
170
+ u.uDrift.value = cfg.drift ?? 0;
171
+ u.uRise.value = cfg.rise ?? 0;
172
+ u.uSwirl.value = cfg.swirl ?? 0;
173
+ u.uWander.value = cfg.wander ?? 0;
174
+ u.uShape.value = SHAPE_INDEX[cfg.shape ?? "glitter"] ?? 0;
175
+ setLinear(u.uColor.value, cfg.color ?? DEFAULT_COLOR);
176
+ setLinear(u.uColor2.value, cfg.color2 ?? cfg.color ?? DEFAULT_COLOR);
177
+ }
178
+ /** Push the per-frame frame basis (the camera can move, so this runs every frame). */
179
+ frame(f, pixelRatio) {
180
+ const u = this.material.uniforms;
181
+ u.uCenter.value.copy(f.center);
182
+ u.uRight.value.copy(f.right);
183
+ u.uUp.value.copy(f.up);
184
+ u.uPixelRatio.value = pixelRatio;
185
+ }
186
+ /** Bind the OWNING wave's shape: mirror its shape #defines + shape uniforms + world matrix so the
187
+ * dust rides the SAME deform as the ribbon. Recompiles the point program only when the define set
188
+ * changes. Called from refresh() each frame with the wave's live state. */
189
+ configure(shape) {
190
+ const want = shape.defines;
191
+ const cur = this.material.defines ?? {};
192
+ if (Object.keys(want).sort().join(",") !== Object.keys(cur).sort().join(",")) {
193
+ this.material.defines = { ...want };
194
+ this.material.needsUpdate = true;
195
+ }
196
+ const u = this.material.uniforms;
197
+ for (const name of SHAPE_UNIFORMS) {
198
+ const src = shape.uniforms[name];
199
+ if (!src || u[name] === void 0) continue;
200
+ const dst = u[name].value;
201
+ if (typeof src.value === "number") u[name].value = src.value;
202
+ else if (dst && typeof dst.copy === "function") dst.copy(src.value);
203
+ }
204
+ u.uShedModel.value.copy(shape.matrixWorld);
205
+ u.uShedSpeed.value = shape.speed;
206
+ u.uShedSeed.value = shape.seed;
207
+ }
208
+ /** Advance the field to scene time `t` (= the same `t` the waves get). */
209
+ setTime(t) {
210
+ this.material.uniforms.uTime.value = t;
211
+ }
212
+ dispose() {
213
+ this.geometry.dispose();
214
+ this.material.dispose();
215
+ }
216
+ };
217
+ //#endregion
218
+ export { ParticleField, buildParticleAttributes };
219
+
220
+ //# sourceMappingURL=particleField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"particleField.js","names":[],"sources":["../../src/renderer/particleField.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport type { ParticlesConfig } from \"../config/model\";\nimport { particleFragmentShader, particleVertexShader } from \"./shaders\";\n\n/**\n * A WAVE's additive particle / dust field: a single {@link THREE.Points} whose every particle is placed\n * and animated ENTIRELY in the vertex shader from `uTime` + baked per-particle attributes, so the field\n * is deterministic — the same `(count, seed, edgeBias, bias)` yields byte-identical buffers, and all\n * motion is a pure function of the scene time `t` (so timeOffset scrub / loopSeconds / paused reproduce).\n *\n * One field belongs to ONE wave (created/disposed alongside it, like {@link WavePalette}). Every particle\n * spawns on that wave's DEFORMED surface / edge (via the shared waveShape chunk, riding the exact deform\n * the ribbon uses) and drifts outward from the wave centre. Byte-identical when off: absent\n * `wave.particles` ⇒ the renderer never creates a field.\n */\nexport interface ParticleFrame {\n /** The owning wave's world-space centre — drift radiates from here. */\n center: THREE.Vector3;\n /** Screen-right / screen-up unit vectors (world space) for screen-relative motion. */\n right: THREE.Vector3;\n up: THREE.Vector3;\n}\n\n/** Deterministic PRNG (mulberry32): a pure function of the seed, so a `(count, seed)` layout\n * reproduces exactly. NEVER Math.random — that would desync timeOffset scrub / loop / paused. */\nfunction mulberry32(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a |= 0;\n a = (a + 0x6d2b79f5) | 0;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n// sRGB hex → linear RGB. Replicates WaveRenderer.hexToLinearVec3 locally to avoid a circular import\n// (the renderer imports this module): three's ColorManagement linearizes on parse, so read .r/.g/.b\n// directly — a second convertSRGBToLinear() would double-linearize.\nconst HEX_SCRATCH = new THREE.Color();\nfunction setLinear(target: THREE.Vector3, hex: string): void {\n const c = HEX_SCRATCH.set(hex);\n target.set(c.r, c.g, c.b);\n}\n\nconst DEFAULT_COLOR = \"#ffcf8a\"; // warm gold\n\n/** Sprite-shape name → the int the fragment shader branches on (see particleFragmentShader). */\nconst SHAPE_INDEX: Record<string, number> = { glitter: 0, soft: 1, ring: 2, star: 3, streak: 4 };\n\n/** The owning wave's shape uniforms mirrored onto the particle material so the dust rides the same\n * deform as the ribbon. Names match the wave material's uniforms 1:1 (copied by value in configure). */\nconst SHAPE_UNIFORMS = [\n \"uDispFreqX\",\n \"uDispFreqZ\",\n \"uDispAmount\",\n \"uDetailFreq\",\n \"uDetailAmount\",\n \"uTwFreqX\",\n \"uTwFreqY\",\n \"uTwFreqZ\",\n \"uTwPowX\",\n \"uTwPowY\",\n \"uTwPowZ\",\n \"uHelixTurns\",\n \"uHelixRadius\",\n \"uHelixRoll\",\n \"uHelixPhase\",\n \"uRadialAmount\",\n \"uRadialArc\",\n \"uRadialSpread\",\n \"uRadialRadius\",\n \"uRadialCenter\",\n] as const;\n\n/** Build the seeded per-particle attribute buffers. Pure function of `(count, seed, edgeBias, bias)` —\n * exported so a unit test can assert reproducibility without a GPU. */\nexport function buildParticleAttributes(\n count: number,\n seed: number,\n edgeBias = 1,\n bias = 0,\n): {\n position: Float32Array;\n aSeed: Float32Array;\n aRnd: Float32Array;\n aUv: Float32Array;\n} {\n const rand = mulberry32(seed >>> 0 || 1);\n const position = new Float32Array(count * 3); // dummy — the vertex shader computes real positions\n const aSeed = new Float32Array(count);\n const aRnd = new Float32Array(count * 4);\n const aUv = new Float32Array(count * 2);\n for (let i = 0; i < count; i++) {\n aSeed[i] = rand();\n aRnd[i * 4 + 0] = rand();\n aRnd[i * 4 + 1] = rand();\n aRnd[i * 4 + 2] = rand();\n aRnd[i * 4 + 3] = rand();\n }\n // aUv in a SEPARATE pass so adding/changing it never shifts the aSeed/aRnd RNG sequence above. aUv.x\n // = the flank position across the ribbon width; `bias` skews it toward one side (`u^p`, p<1 crowds\n // →1, p>1 crowds →0; p=1 / bias 0 leaves it untouched). aUv.y = WHERE ALONG the ribbon the particle\n // spawns, interpolated by `edgeBias`: 0 → uniform across the whole SURFACE, 1 → crowded to the outer\n // rim/EDGE (`1 − rand²·0.45`, the silk-dissolving-into-glitter look).\n const pexp = bias === 0 ? 1 : Math.exp(-bias * 2);\n const eb = Math.min(Math.max(edgeBias, 0), 1);\n for (let i = 0; i < count; i++) {\n const ux = rand();\n aUv[i * 2 + 0] = bias === 0 ? ux : Math.pow(ux, pexp);\n const e = rand();\n const rim = 1.0 - e * e * 0.45; // outer-rim biased\n aUv[i * 2 + 1] = e + (rim - e) * eb; // mix(surface, edge) by edgeBias\n }\n return { position, aSeed, aRnd, aUv };\n}\n\nexport class ParticleField {\n readonly points: THREE.Points;\n private readonly geometry = new THREE.BufferGeometry();\n private readonly material: THREE.ShaderMaterial;\n /** Layout signature — only these rebuild the seeded buffers; the rest are live uniforms. */\n private sig = \"\";\n\n constructor() {\n this.material = new THREE.ShaderMaterial({\n uniforms: {\n uTime: { value: 0 },\n uLoopSeconds: { value: 0 },\n uLife: { value: 6 },\n uPartSpeed: { value: 1 },\n uSize: { value: 2 },\n uSizeJitter: { value: 0 },\n uTwinkle: { value: 0 },\n uPixelRatio: { value: 1 },\n uColor: { value: new THREE.Vector3(1, 0.81, 0.54) },\n uColor2: { value: new THREE.Vector3(1, 0.81, 0.54) },\n uCenter: { value: new THREE.Vector3() },\n uRight: { value: new THREE.Vector3(1, 0, 0) },\n uUp: { value: new THREE.Vector3(0, 1, 0) },\n uDrift: { value: 0 },\n uRise: { value: 0 },\n uSwirl: { value: 0 },\n uWander: { value: 0 },\n uShape: { value: 0 },\n // The owning wave's shape uniforms + world matrix, mirrored in configure() so the dust rides\n // the same deform as the ribbon. The nested HELIX/RADIAL uniforms are only declared (and\n // uploaded) when the matching #define is set — the byte-identity precedent from the wave material.\n uDispFreqX: { value: 0 },\n uDispFreqZ: { value: 0 },\n uDispAmount: { value: 0 },\n uDetailFreq: { value: 0 },\n uDetailAmount: { value: 0 },\n uTwFreqX: { value: 0 },\n uTwFreqY: { value: 0 },\n uTwFreqZ: { value: 0 },\n uTwPowX: { value: 0 },\n uTwPowY: { value: 0 },\n uTwPowZ: { value: 0 },\n uHelixTurns: { value: 0 },\n uHelixRadius: { value: 0 },\n uHelixRoll: { value: 0 },\n uHelixPhase: { value: 0 },\n uRadialAmount: { value: 0 },\n uRadialArc: { value: 0 },\n uRadialSpread: { value: 0 },\n uRadialRadius: { value: 0 },\n uRadialCenter: { value: 0 },\n uShedModel: { value: new THREE.Matrix4() },\n uShedSpeed: { value: 0 },\n uShedSeed: { value: 0 },\n },\n vertexShader: particleVertexShader,\n fragmentShader: particleFragmentShader,\n transparent: true,\n depthTest: false, // always composite OVER the waves...\n depthWrite: false, // ...and never occlude anything (additive glints)\n blending: THREE.AdditiveBlending,\n });\n this.points = new THREE.Points(this.geometry, this.material);\n this.points.frustumCulled = false; // positions are shader-computed; the base geometry is dummy\n this.points.renderOrder = 10; // after the waves (0..5)\n }\n\n /** Reconcile to `cfg`: rebuild the seeded buffers if the layout signature changed, then push the\n * frame-independent uniforms. `loopSeconds` is scene-level (passed in). Called from refresh(). */\n sync(cfg: ParticlesConfig, loopSeconds: number): void {\n const count = Math.max(0, Math.floor(cfg.count));\n const edgeBias = cfg.edgeBias ?? 1;\n const bias = cfg.bias ?? 0;\n const sig = `${count}|${cfg.seed}|${edgeBias}|${bias}`;\n if (sig !== this.sig) {\n this.sig = sig;\n const { position, aSeed, aRnd, aUv } = buildParticleAttributes(\n count,\n cfg.seed,\n edgeBias,\n bias,\n );\n this.geometry.setAttribute(\"position\", new THREE.BufferAttribute(position, 3));\n this.geometry.setAttribute(\"aSeed\", new THREE.BufferAttribute(aSeed, 1));\n this.geometry.setAttribute(\"aRnd\", new THREE.BufferAttribute(aRnd, 4));\n this.geometry.setAttribute(\"aUv\", new THREE.BufferAttribute(aUv, 2));\n this.geometry.setDrawRange(0, count);\n }\n const u = this.material.uniforms;\n u.uLoopSeconds.value = loopSeconds;\n u.uLife.value = cfg.life ?? 6;\n u.uPartSpeed.value = cfg.speed ?? 1;\n u.uSize.value = cfg.size;\n u.uSizeJitter.value = cfg.sizeJitter ?? 0;\n u.uTwinkle.value = cfg.twinkle ?? 0;\n u.uDrift.value = cfg.drift ?? 0;\n u.uRise.value = cfg.rise ?? 0;\n u.uSwirl.value = cfg.swirl ?? 0;\n u.uWander.value = cfg.wander ?? 0;\n u.uShape.value = SHAPE_INDEX[cfg.shape ?? \"glitter\"] ?? 0;\n setLinear(u.uColor.value as THREE.Vector3, cfg.color ?? DEFAULT_COLOR);\n setLinear(u.uColor2.value as THREE.Vector3, cfg.color2 ?? cfg.color ?? DEFAULT_COLOR);\n }\n\n /** Push the per-frame frame basis (the camera can move, so this runs every frame). */\n frame(f: ParticleFrame, pixelRatio: number): void {\n const u = this.material.uniforms;\n (u.uCenter.value as THREE.Vector3).copy(f.center);\n (u.uRight.value as THREE.Vector3).copy(f.right);\n (u.uUp.value as THREE.Vector3).copy(f.up);\n u.uPixelRatio.value = pixelRatio;\n }\n\n /** Bind the OWNING wave's shape: mirror its shape #defines + shape uniforms + world matrix so the\n * dust rides the SAME deform as the ribbon. Recompiles the point program only when the define set\n * changes. Called from refresh() each frame with the wave's live state. */\n configure(shape: {\n defines: Record<string, string>;\n uniforms: Record<string, THREE.IUniform>;\n matrixWorld: THREE.Matrix4;\n speed: number;\n seed: number;\n }): void {\n const want = shape.defines;\n const cur = (this.material.defines ?? {}) as Record<string, string>;\n if (Object.keys(want).sort().join(\",\") !== Object.keys(cur).sort().join(\",\")) {\n this.material.defines = { ...want };\n this.material.needsUpdate = true; // define set changed → recompile the point program\n }\n const u = this.material.uniforms;\n for (const name of SHAPE_UNIFORMS) {\n const src = shape.uniforms[name];\n if (!src || u[name] === undefined) continue;\n const dst = u[name].value;\n if (typeof src.value === \"number\") u[name].value = src.value;\n else if (dst && typeof (dst as { copy?: unknown }).copy === \"function\") {\n (dst as THREE.Vector3).copy(src.value as THREE.Vector3);\n }\n }\n (u.uShedModel.value as THREE.Matrix4).copy(shape.matrixWorld);\n u.uShedSpeed.value = shape.speed;\n u.uShedSeed.value = shape.seed;\n }\n\n /** Advance the field to scene time `t` (= the same `t` the waves get). */\n setTime(t: number): void {\n this.material.uniforms.uTime.value = t;\n }\n\n dispose(): void {\n this.geometry.dispose();\n this.material.dispose();\n }\n}\n"],"mappings":"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"}