Streamlines
32-bit, display onlyTraces lines along the flow from seed points for drawing.
x ← x + ds × v(x + ½ ds v̂) ÷ |v|
Midpoint steps of 0.4 cell through the 32-bit cell velocities, stopped when the speed falls under 1e-4 of the largest. Points and speeds are returned as Float32Array for line drawing and coloring.
/**
* Streamlines through a cell-centred field F {nx, ny, nz, h, origin, mask (1 fluid), vx, vy, vz} from seed points (world mm):
* RK2 (midpoint) with steps of `ds` cells along the direction of flow, stopping on leaving the fluid, stalling, or after
* `maxSteps`. → [{ points: Float32Array (xyz world), speed: Float32Array }]
*/
export function streamlines(F, seeds, { ds = 0.4, maxSteps = 3000, both = false } = {}) {
const out = [], v = [0, 0, 0], v2 = [0, 0, 0], h = F.h, o = F.origin;
let vmax = 0;
for (let e = 0; e < F.mask.length; e++) if (F.mask[e] === 1) vmax = Math.max(vmax, Math.hypot(F.vx[e], F.vy[e], F.vz[e]));
const stall = 1e-4 * (vmax || 1);
const trace = (p0, dirSign) => {
const pts = [], sp = [];
let x = (p0[0] - o[0]) / h, y = (p0[1] - o[1]) / h, z = (p0[2] - o[2]) / h;
for (let s = 0; s < maxSteps; s++) {
if (!isFluidAt(F, x, y, z)) break;
sampleVelocity(F, x, y, z, v);
const m = Math.hypot(v[0], v[1], v[2]);
pts.push(o[0] + x * h, o[1] + y * h, o[2] + z * h); sp.push(m);
if (m < stall) break;
const a = dirSign * ds / m;
sampleVelocity(F, x + 0.5 * a * v[0], y + 0.5 * a * v[1], z + 0.5 * a * v[2], v2);
const m2 = Math.hypot(v2[0], v2[1], v2[2]);
if (m2 < stall) break;
const b = dirSign * ds / m2;
x += b * v2[0]; y += b * v2[1]; z += b * v2[2];
}
return { pts, sp };
};
for (const p of seeds) {
const f = trace(p, 1);
let pts = f.pts, sp = f.sp;
if (both) { const r = trace(p, -1); const n = r.sp.length; const rp = []; const rs = []; for (let i = n - 1; i >= 1; i--) { rp.push(r.pts[3 * i], r.pts[3 * i + 1], r.pts[3 * i + 2]); rs.push(r.sp[i]); } pts = rp.concat(pts); sp = rs.concat(sp); }
if (sp.length >= 2) out.push({ points: Float32Array.from(pts), speed: Float32Array.from(sp) });
}
return out;
}