use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
#[inline]
fn project(x: f32, y: f32, z: f32, ax: f32, ay: f32, cx: i32, cy: i32, scale: f32) -> (i32, i32) {
let (sax, cax) = ax.sin_cos();
let y1 = y * cax - z * sax;
let z1 = y * sax + z * cax;
let (say, cay) = ay.sin_cos();
let x2 = x * cay + z1 * say;
let y2 = y1;
let sx = cx + (x2 * scale).round() as i32;
let sy = cy - (y2 * scale).round() as i32;
(sx, sy)
}
#[inline]
fn plot_curve<F>(
grid: &mut BrailleGrid,
t0: f32,
t1: f32,
steps: usize,
ax: f32,
ay: f32,
cx: i32,
cy: i32,
scale: f32,
f: F,
) where
F: Fn(f32) -> (f32, f32, f32),
{
if steps == 0 {
return;
}
for i in 0..=steps {
let t = t0 + (t1 - t0) * (i as f32 / steps as f32);
let (x, y, z) = f(t);
let (sx, sy) = project(x, y, z, ax, ay, cx, cy, scale);
draw::dot_i(grid, sx, sy);
}
}
fn grid_cxys(grid: &BrailleGrid) -> (i32, i32, f32) {
let (dw, dh) = draw::dot_dims(grid);
let cx = (dw / 2) as i32;
let cy = (dh / 2) as i32;
let scale = (dw.min(dh * 2) as f32 * 0.42).max(1.0);
(cx, cy, scale)
}
struct MobiusStrip;
impl ProgressStyle for MobiusStrip {
fn name(&self) -> &str {
"mobius-strip"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"A one-sided Möbius strip unfurling along its parametric u-axis as progress advances, rotating lazily on time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.37;
let ay = ctx.time * 0.53;
let u_max = ctx.eased * 2.0 * PI;
let u_steps = 120usize;
let v_lines = 9usize; for vi in 0..v_lines {
let v = -1.0 + 2.0 * vi as f32 / (v_lines - 1).max(1) as f32;
plot_curve(grid, 0.0, u_max, u_steps, ax, ay, cx, cy, scale, |u| {
let half = 0.5 * v * (u / 2.0).cos();
let x = (1.0 + half) * u.cos();
let y = (1.0 + half) * u.sin();
let z = 0.5 * v * (u / 2.0).sin();
(x, y, z)
});
}
Ok(())
}
}
struct TorusWireframe;
impl ProgressStyle for TorusWireframe {
fn name(&self) -> &str {
"torus-wireframe"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"Latitude/longitude wireframe of a (R=1, r=0.38) torus, circles revealed row-by-row as progress grows"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.4 + 0.4;
let ay = ctx.time * 0.6;
let r_big = 1.0_f32;
let r_small = 0.38_f32;
let n_lat = 14usize; let n_lon = 10usize; let u_steps = 60usize;
let n_lit = (ctx.eased * (n_lat + n_lon) as f32).round() as usize;
for i in 0..n_lit.min(n_lat) {
let v = 2.0 * PI * i as f32 / n_lat as f32;
plot_curve(grid, 0.0, 2.0 * PI, u_steps, ax, ay, cx, cy, scale, |u| {
let x = (r_big + r_small * v.cos()) * u.cos();
let y = (r_big + r_small * v.cos()) * u.sin();
let z = r_small * v.sin();
(x, y, z)
});
}
let n_lon_lit = n_lit.saturating_sub(n_lat).min(n_lon);
for i in 0..n_lon_lit {
let u = 2.0 * PI * i as f32 / n_lon as f32;
plot_curve(grid, 0.0, 2.0 * PI, u_steps, ax, ay, cx, cy, scale, |v| {
let x = (r_big + r_small * v.cos()) * u.cos();
let y = (r_big + r_small * v.cos()) * u.sin();
let z = r_small * v.sin();
(x, y, z)
});
}
Ok(())
}
}
struct TorusKnot;
impl ProgressStyle for TorusKnot {
fn name(&self) -> &str {
"torus-knot"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"(2,3) torus knot traced on a torus surface — a trefoil path that wraps twice around the tube for every three loops of the core"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.29;
let ay = ctx.time * 0.47;
let p = 2_i32;
let q = 3_i32;
let r_big = 1.0_f32;
let r_small = 0.35_f32;
let t_max = ctx.eased * 2.0 * PI;
let steps = 200usize;
plot_curve(grid, 0.0, t_max, steps, ax, ay, cx, cy, scale, |t| {
let u = p as f32 * t;
let v = q as f32 * t;
let x = (r_big + r_small * v.cos()) * u.cos();
let y = (r_big + r_small * v.cos()) * u.sin();
let z = r_small * v.sin();
(x, y, z)
});
Ok(())
}
}
struct TrefoilKnot;
impl ProgressStyle for TrefoilKnot {
fn name(&self) -> &str {
"trefoil-knot"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"Trefoil knot: x=sin t+2sin 2t, y=cos t−2cos 2t, z=−sin 3t — the simplest non-trivial knot, spinning on time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.33;
let ay = ctx.time * 0.55;
let t_max = ctx.eased * 2.0 * PI;
let steps = 200usize;
let s = scale / 3.0;
plot_curve(grid, 0.0, t_max, steps, ax, ay, cx, cy, s, |t| {
let x = t.sin() + 2.0 * (2.0 * t).sin();
let y = t.cos() - 2.0 * (2.0 * t).cos();
let z = -(3.0 * t).sin();
(x, y, z)
});
Ok(())
}
}
struct KleinBottle;
impl ProgressStyle for KleinBottle {
fn name(&self) -> &str {
"klein-bottle"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"Figure-8 immersion of the Klein bottle in ℝ³ — a non-orientable surface with no inside revealed petal by petal"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.28 + 0.6;
let ay = ctx.time * 0.44;
let u_max = ctx.eased * PI;
let u_lines = 20usize;
let v_steps = 60usize;
let s = scale / 2.5;
for ui in 0..u_lines {
let u = u_max * ui as f32 / u_lines.max(1) as f32;
plot_curve(grid, 0.0, 2.0 * PI, v_steps, ax, ay, cx, cy, s, |v| {
let cu = u.cos();
let su = u.sin();
let cv = v.cos();
let sv = v.sin();
let a = 2.5;
let x = (a + cu * (cv + 1.0)) * (2.0 * u).cos() - su * (2.0 * u).cos() * cv;
let y = (a + cu * (cv + 1.0)) * (2.0 * u).sin() - su * (2.0 * u).sin() * cv;
let z = su * (cv + 1.0) + cu * sv;
(x * 0.3, y * 0.3, z * 0.5)
});
}
Ok(())
}
}
struct HopfFibers;
impl ProgressStyle for HopfFibers {
fn name(&self) -> &str {
"hopf-fibers"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"Three Hopf fibration circles stereographically projected from S³ — every fiber is a great circle linking every other"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.31;
let ay = ctx.time * 0.52;
let bases: [(f32, f32); 3] = [
(0.0, 0.0),
(PI / 3.0, 2.0 * PI / 3.0),
(-PI / 3.0, 4.0 * PI / 3.0),
];
let t_max = ctx.eased * 2.0 * PI;
let steps = 100usize;
let s = scale * 0.55;
for &(theta, phi) in &bases {
let (st, ct) = theta.sin_cos();
let (sp, cp) = phi.sin_cos();
let hth = theta / 2.0;
let (shth, chth) = hth.sin_cos();
plot_curve(grid, 0.0, t_max, steps, ax, ay, cx, cy, s, |t| {
let q0 = t.cos() * chth;
let q1 = t.cos() * shth * cp;
let q2 = t.sin() * chth;
let q3 = t.sin() * shth * cp;
let denom = 1.0 - q0;
if denom.abs() < 1e-4 {
(0.0, 0.0, 0.0)
} else {
let inv = 1.0 / denom;
let _ = (st, sp, ct); (q1 * inv, q2 * inv, q3 * inv)
}
});
}
Ok(())
}
}
struct SphereInflate;
impl ProgressStyle for SphereInflate {
fn name(&self) -> &str {
"sphere-inflate"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"Unit sphere wireframe expanding from a point to full radius as progress grows, spinning on time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.35;
let ay = ctx.time * 0.62;
let r = ctx.eased; let n_lat = 7usize;
let n_lon = 8usize;
let steps = 50usize;
for i in 0..n_lat {
let theta = PI * (i + 1) as f32 / (n_lat + 1) as f32;
let ring_r = r * theta.sin();
let z0 = r * theta.cos();
plot_curve(grid, 0.0, 2.0 * PI, steps, ax, ay, cx, cy, scale, |phi| {
(ring_r * phi.cos(), ring_r * phi.sin(), z0)
});
}
for i in 0..n_lon {
let phi = 2.0 * PI * i as f32 / n_lon as f32;
let (sp, cp) = phi.sin_cos();
plot_curve(grid, 0.0, PI, steps, ax, ay, cx, cy, scale, |theta| {
(r * theta.sin() * cp, r * theta.sin() * sp, r * theta.cos())
});
}
Ok(())
}
}
struct HelixClimb;
impl ProgressStyle for HelixClimb {
fn name(&self) -> &str {
"helix-climb"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"Double helix on a cylinder — two strands climb in opposite phase as progress advances, rotating with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.25 + 0.3;
let ay = ctx.time * 0.58;
let turns = 4.0_f32;
let t_max = ctx.eased * turns * 2.0 * PI;
let steps = 200usize;
let r = 0.6_f32;
let height = 2.0_f32; plot_curve(grid, 0.0, t_max, steps, ax, ay, cx, cy, scale, |t| {
let z = -1.0 + height * t / (turns * 2.0 * PI);
(r * t.cos(), r * t.sin(), z)
});
plot_curve(grid, 0.0, t_max, steps, ax, ay, cx, cy, scale, |t| {
let z = -1.0 + height * t / (turns * 2.0 * PI);
(r * (t + PI).cos(), r * (t + PI).sin(), z)
});
let n_links = (ctx.eased * turns * 2.0) as usize;
for i in 0..n_links {
let t_link = i as f32 * PI;
let z = -1.0 + height * t_link / (turns * 2.0 * PI);
plot_curve(grid, 0.0, 1.0, 4, ax, ay, cx, cy, scale, |s| {
let angle_a = t_link;
let angle_b = t_link + PI;
let xa = r * angle_a.cos();
let ya = r * angle_a.sin();
let xb = r * angle_b.cos();
let yb = r * angle_b.sin();
(xa + s * (xb - xa), ya + s * (yb - ya), z)
});
}
Ok(())
}
}
struct SaddleSurface;
impl ProgressStyle for SaddleSurface {
fn name(&self) -> &str {
"saddle-surface"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"Hyperbolic paraboloid z = x²−y² wireframe — the canonical saddle point, revealed as a grid of iso-lines, rotating with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.22 + 0.5;
let ay = ctx.time * 0.48;
let n_lines = 12usize;
let revealed = (ctx.eased * n_lines as f32 * 2.0).round() as usize;
let steps = 60usize;
for i in 0..revealed.min(n_lines) {
let x = -1.0 + 2.0 * i as f32 / (n_lines - 1).max(1) as f32;
plot_curve(grid, -1.0, 1.0, steps, ax, ay, cx, cy, scale, |y| {
(x, y, x * x - y * y)
});
}
let extra = revealed.saturating_sub(n_lines);
for i in 0..extra.min(n_lines) {
let y = -1.0 + 2.0 * i as f32 / (n_lines - 1).max(1) as f32;
plot_curve(grid, -1.0, 1.0, steps, ax, ay, cx, cy, scale, |x| {
(x, y, x * x - y * y)
});
}
Ok(())
}
}
struct TesseractSpin;
impl ProgressStyle for TesseractSpin {
fn name(&self) -> &str {
"tesseract-spin"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"4-D hypercube (tesseract) rotated in the XW and YZ planes then perspective-projected to 2-D — edges revealed by progress, spinning on time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let cx = (dw / 2) as i32;
let cy = (dh / 2) as i32;
let scale = (dw.min(dh * 2) as f32 * 0.35).max(1.0);
let verts: [[f32; 4]; 16] = {
let mut v = [[0.0f32; 4]; 16];
for i in 0..16usize {
v[i][0] = if i & 1 != 0 { 1.0 } else { -1.0 };
v[i][1] = if i & 2 != 0 { 1.0 } else { -1.0 };
v[i][2] = if i & 4 != 0 { 1.0 } else { -1.0 };
v[i][3] = if i & 8 != 0 { 1.0 } else { -1.0 };
}
v
};
let mut edges: Vec<(usize, usize)> = Vec::with_capacity(32);
for i in 0..16usize {
for j in (i + 1)..16usize {
if (i ^ j).count_ones() == 1 {
edges.push((i, j));
}
}
}
let n_show = (ctx.eased * edges.len() as f32).round() as usize;
let a_xw = ctx.time * 0.48;
let a_yz = ctx.time * 0.31;
let project4 = |vert: [f32; 4]| -> (i32, i32) {
let [x0, y0, z0, w0] = vert;
let (sxw, cxw) = a_xw.sin_cos();
let x1 = x0 * cxw - w0 * sxw;
let w1 = x0 * sxw + w0 * cxw;
let (syz, cyz) = a_yz.sin_cos();
let y1 = y0 * cyz - z0 * syz;
let z1 = y0 * syz + z0 * cyz;
let ax = ctx.time * 0.22;
let ay = ctx.time * 0.37;
let (sax, cax) = ax.sin_cos();
let y2 = y1 * cax - z1 * sax;
let z2 = y1 * sax + z1 * cax;
let (say, cay) = ay.sin_cos();
let x3 = x1 * cay + z2 * say;
let y3 = y2;
let w_dist = 2.5 - w1 * 0.5;
let inv = if w_dist.abs() < 0.01 {
0.0
} else {
1.0 / w_dist
};
let sx = cx + (x3 * inv * scale) as i32;
let sy = cy - (y3 * inv * scale) as i32;
(sx, sy)
};
let projected: Vec<(i32, i32)> = verts.iter().map(|&v| project4(v)).collect();
for &(a, b) in edges.iter().take(n_show) {
let (x0, y0) = projected[a];
let (x1, y1) = projected[b];
let dx = (x1 - x0).abs();
let dy = (y1 - y0).abs();
let sx = if x0 < x1 { 1i32 } else { -1i32 };
let sy = if y0 < y1 { 1i32 } else { -1i32 };
let mut x = x0;
let mut y = y0;
let mut err = dx - dy;
let max_steps = (dx + dy + 1).min(300);
for _ in 0..max_steps {
draw::dot_i(grid, x, y);
if x == x1 && y == y1 {
break;
}
let e2 = 2 * err;
if e2 > -dy {
err -= dy;
x += sx;
}
if e2 < dx {
err += dx;
y += sy;
}
}
}
Ok(())
}
}
struct RomanSurface;
impl ProgressStyle for RomanSurface {
fn name(&self) -> &str {
"roman-surface"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"Steiner's Roman surface — one of the most beautiful immersions of ℝP² into ℝ³, with six Whitney umbrella singularities, revealed petal by petal"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.27 + 0.4;
let ay = ctx.time * 0.43;
let u_max = ctx.eased * PI;
let u_lines = 18usize;
let v_steps = 60usize;
let s = scale * 0.6;
for ui in 0..u_lines {
let u = u_max * ui as f32 / u_lines.max(1) as f32;
let (su, cu) = u.sin_cos();
let s2u = (2.0 * u).sin();
plot_curve(grid, 0.0, PI, v_steps, ax, ay, cx, cy, s, |v| {
let (sv, _cv) = v.sin_cos();
let s2v = (2.0 * v).sin();
let x = su * su * s2v;
let y = s2u * sv * sv;
let z = 0.5 * s2u * s2v;
let _ = cu;
(x, y, z)
});
}
Ok(())
}
}
struct SeifertRamp;
impl ProgressStyle for SeifertRamp {
fn name(&self) -> &str {
"seifert-ramp"
}
fn theme(&self) -> &str {
"topology"
}
fn describe(&self) -> &str {
"Seifert-surface-inspired spiral ramp spanning a trefoil knot boundary — a twisted disk that rises through a full turn as progress advances"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_cxys(grid);
let ax = ctx.time * 0.30 + 0.5;
let ay = ctx.time * 0.50;
let theta_max = ctx.eased * 2.0 * PI;
let n_radii = 8usize;
let steps = 80usize;
for ri in 1..=n_radii {
let r = ri as f32 / n_radii as f32;
plot_curve(
grid,
0.0,
theta_max,
steps,
ax,
ay,
cx,
cy,
scale,
|theta| {
let x = r * theta.cos();
let y = r * theta.sin();
let z = r * (theta / 3.0).sin() * (1.0 - r * 0.5);
(x, y, z)
},
);
}
let n_spokes = 12usize;
for si in 0..n_spokes {
let theta = theta_max * si as f32 / n_spokes.max(1) as f32;
plot_curve(grid, 0.0, 1.0, 20, ax, ay, cx, cy, scale, |r| {
let x = r * theta.cos();
let y = r * theta.sin();
let z = r * (theta / 3.0).sin() * (1.0 - r * 0.5);
(x, y, z)
});
}
Ok(())
}
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(MobiusStrip),
Box::new(TorusWireframe),
Box::new(TorusKnot),
Box::new(TrefoilKnot),
Box::new(KleinBottle),
Box::new(HopfFibers),
Box::new(SphereInflate),
Box::new(HelixClimb),
Box::new(SaddleSurface),
Box::new(TesseractSpin),
Box::new(RomanSurface),
Box::new(SeifertRamp),
]
}