use crate::orbs::engine::{
core::{Dot, Frame, Line, frac, hash_d, lerp, make_proj, radius_scale, vnoise, with_fib_dirs},
profiles::{MAX_NODE_N, MAX_SIGNALS, ModeOpts, count_usize},
};
use std::cell::RefCell;
thread_local! {
static NODES: RefCell<Vec<[f32; 6]>> = const { RefCell::new(Vec::new()) };
static EDGES: RefCell<Vec<(usize, usize)>> = const { RefCell::new(Vec::new()) };
}
pub fn draw_web_into(size: f32, t: f32, o: &ModeOpts, out: &mut Frame) {
let cx = size / 2.0;
let cy = size / 2.0;
let r = (size / 2.0) * 0.8 * o.spread.unwrap_or(1.0);
let pt = make_proj(t * 0.12, 0.32, cx, cy, r);
let rs = radius_scale(size, o.rs_pow.unwrap_or(0.6));
let node_n = count_usize(o.node_n, 30.0, 0, MAX_NODE_N as usize);
let thr = o.thr.unwrap_or(0.72);
let node_r = o.node_r.unwrap_or(1.4);
let node_r_depth = o.node_r_depth.unwrap_or(1.8);
let line_w = (o.line_w.unwrap_or(0.8) * rs).max(0.6);
let inv_thr = if thr > 0.0 { 1.0 / thr } else { 0.0 };
NODES.with(|cell| {
EDGES.with(|edges_cell| {
let mut nodes = cell.borrow_mut();
let mut edges = edges_cell.borrow_mut();
nodes.clear();
edges.clear();
nodes.reserve(node_n);
with_fib_dirs(node_n, |directions| {
for (i, d) in directions.iter().enumerate() {
let i_f = i as f32;
let x = d[0] + 0.3 * (vnoise(i_f * 0.31 + 9.0, t * 0.24) - 0.5) * 2.0;
let y = d[1] + 0.3 * (vnoise(i_f * 0.53 + 27.0, t * 0.21) - 0.5) * 2.0;
let z = d[2] + 0.3 * (vnoise(i_f * 0.77 + 55.0, t * 0.27) - 0.5) * 2.0;
let l = (x * x + y * y + z * z).sqrt();
let inv_l = if l > 1e-6 { 1.0 / l } else { 0.0 };
let (ux, uy, uz) = (x * inv_l, y * inv_l, z * inv_l);
let (px, py, pz) = pt.project(ux, uy, uz);
nodes.push([ux, uy, uz, px, py, pz]);
}
});
for i in 0..node_n {
let a = nodes[i];
for j in (i + 1)..node_n {
let b = nodes[j];
let dx = a[0] - b[0];
let dy = a[1] - b[1];
let dz = a[2] - b[2];
let d2 = dx * dx + dy * dy + dz * dz;
if d2 >= thr * thr {
continue;
}
let dist = d2.sqrt();
let depth = ((a[5] + b[5]) / 2.0 + 1.0) / 2.0;
out.lines.push(Line {
x1: a[3],
y1: a[4],
x2: b[3],
y2: b[4],
white: 0.42,
a: (1.0 - dist * inv_thr) * (0.3 + 0.55 * depth),
w: line_w,
});
edges.push((i, j));
}
}
for (i, n) in nodes.iter().enumerate() {
let depth = (n[5] + 1.0) / 2.0;
let pulse = 1.0 + 0.25 * (t * 1.4 + i as f32 * 2.7).sin();
out.dots.push(Dot::new(
n[3],
n[4],
n[5],
(node_r + node_r_depth * depth) * pulse * rs,
0.55 - 0.45 * depth,
));
}
let signals = count_usize(o.signals, 5.0, 0, MAX_SIGNALS as usize);
for s in 0..signals {
if edges.is_empty() {
break;
}
let s_f = s as f32;
let journey = t * 0.48 + s_f * 3.73;
let cycle = journey.floor();
let edge_index = (hash_d(cycle, s_f * 4.7 + 1.3) * edges.len() as f32)
.floor()
.min((edges.len() - 1) as f32) as usize;
let (a, b) = edges[edge_index];
let f = frac(journey);
let px = lerp(nodes[a][3], nodes[b][3], f);
let py = lerp(nodes[a][4], nodes[b][4], f);
let zr = lerp(nodes[a][5], nodes[b][5], f);
let depth = (zr + 1.0) / 2.0;
let life = (std::f32::consts::PI * f).sin().max(0.0);
out.dots.push(
Dot::new(px, py, zr, (node_r * 1.5 + node_r_depth * depth) * rs, 0.05)
.with_a(life * (0.5 + 0.5 * depth)),
);
}
});
});
}