streaklines = (grid, DZX, DZY, { avoidance = 0.01, seed = 42 } = {}) => {
function altitude(x, y) {
const k = Math.floor(x) + w * Math.floor(y);
return grid[k];
}
function gradient(x, y) {
const k = Math.floor(x) + w * Math.floor(y);
return [DZX[k], DZY[k]];
}
const rng = d3.randomLcg(seed);
const cW = w >> 3;
const cH = h >> 3;
const visited = new Float32Array(cW * cH).fill(-1);
let maxstep = 200000;
const streaklines = [];
const order = d3.shuffle(d3.range(cW * cH));
for (const i of order) {
if (visited[i] > -1) continue;
const streakline = [];
visited[i] = i;
let x;
let y;
let j = i;
let vx, vy, dx, dy;
for (const direction of [-1, 1]) {
x = (i % cW) * (w / cW);
y = Math.floor(i / cW) * (h / cH);
vx = 0;
vy = 0;
do {
dx = vx;
dy = vy;
if (altitude(x, y) <= 0) break;
[vx, vy] = gradient(x, y).map((d) => d * direction);
const delta = Math.hypot(vx, vy);
if (delta === 0) break;
x += vx / delta;
y += vy / delta;
j = Math.floor((x / w) * cW) + cW * Math.floor((y / w) * cW);
streakline.push([x, y, i, j]);
if (visited[j] < 0) visited[j] = i;
if (visited[j] !== i && rng() > 1 - avoidance) break;
} while (
vx * dx + vy * dy >= 0 &&
x > 0 &&
y > 0 &&
x < w &&
y < h &&
maxstep-- > 0
);
streakline.reverse();
}
if (streakline.length > 5) streaklines.push(streakline);
}
return streaklines;
}