Files
bubblechambersimart/src/field/wind.js
noisedestroyers 63066ee533 Hidden Realities — Milkweed & shared wind field
First plate of the hidden-realities series: natural subjects as
instrument photographs. One invisible wind field, three detectors —
dunes (time-integrated, what the sand remembers), water (instantaneous),
milkweed seeds (advected test particles).

- src/field/wind.js: divergence-free curl-noise field with closed-form
  time integral; displacement / scalar / stepped-vec samplers.
- src/scene/umbel.js: milkweed head as a particle interaction vertex;
  burst releases pedicels to seed. src/scene/drift.js: seeds advected,
  same track contract as track.js.
- composition.js: switch -> GROUP_BUILDERS registry; fieldLake/flora/
  drift layers + wind plumbing. carpet.js/perspgrid.js: warpFn/phaseFn/
  modesFn/heightFn injection hooks (default off, no regression).
- schema + composer panels, dunelake ink palette, dune-lake template,
  tools/dunelake.mjs (17-plate matrix) and its curated SVG set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:23:55 -04:00

73 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
field/wind.js — the SHARED WIND FIELD: one invisible field,
three detectors. The same seeded flow is sampled three ways:
displacement(x,y,T) time-integrated — what the SAND remembers
(warps the dune ridgelines)
vec(x,y,t) / scalar instantaneous — what the WATER shows
(drives lake ripple phase)
vec stepped advected — what the SEEDS trace
(milkweed drift = test particles)
Divergence-free curl noise from a streamfunction ψ(x,y,t) built
as a sum of K seeded sinusoidal modes plus a mean drift, so the
velocity has a closed-form time integral (no numerics, fully
deterministic). Coordinates are frame-normalized 1..1; typical
|vec| ≈ strength.
============================================================ */
import { makeRng, range } from '../rng.js';
export function makeWindField(seed, opts = {}) {
const o = Object.assign({
modes: 6, scale: 1.6, strength: 1,
drift: [0.6, 0.05], // prevailing wind (mean velocity, frame units)
gust: 0.35, // how much the modes stir vs. the mean drift
}, opts);
const rng = makeRng(seed, 'wind');
const modes = [];
for (let m = 0; m < o.modes; m++) {
const ang = range(rng, 0, Math.PI * 2);
const k = range(rng, 0.6, 2.2) * o.scale; // low wavenumber: long swells
modes.push({
kx: Math.cos(ang) * k, ky: Math.sin(ang) * k,
a: range(rng, 0.5, 1) / k, // energy at the large scales
w: range(rng, 0.4, 1.4), // temporal frequency
phi: range(rng, 0, Math.PI * 2),
});
}
const norm = modes.reduce((s, m) => s + m.a * Math.hypot(m.kx, m.ky), 0) || 1;
const g = o.gust * o.strength / norm; // mode velocity normaliser
const dx0 = o.drift[0] * o.strength, dy0 = o.drift[1] * o.strength;
// instantaneous velocity: u = ∂ψ/∂y, v = −∂ψ/∂x (divergence-free) + drift
const vec = (x, y, t = 0) => {
let u = 0, v = 0;
for (const m of modes) {
const c = m.a * Math.cos(m.kx * x + m.ky * y + m.w * t + m.phi);
u += m.ky * c; v -= m.kx * c;
}
return { u: u * g * modes.length + dx0, v: v * g * modes.length + dy0 };
};
// Eulerian time integral 0..T of the velocity at a fixed point — the
// closed-form "memory" of the wind (dune displacement field)
const displacement = (x, y, T = 1) => {
let dx = 0, dy = 0;
for (const m of modes) {
const p = m.kx * x + m.ky * y + m.phi;
const s = m.a * (Math.sin(p + m.w * T) - Math.sin(p)) / m.w;
dx += m.ky * s; dy -= m.kx * s;
}
return { dx: dx * g * modes.length + dx0 * T, dy: dy * g * modes.length + dy0 * T };
};
// the streamfunction itself — a scalar to phase ripples with
const scalar = (x, y, t = 0) => {
let s = 0;
for (const m of modes) s += m.a * Math.sin(m.kx * x + m.ky * y + m.w * t + m.phi);
return s * o.strength / (modes.reduce((q, m) => q + m.a, 0) || 1);
};
return { vec, displacement, scalar };
}