use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(Rose),
Box::new(Spirograph),
Box::new(Epitrochoid),
Box::new(Superformula),
Box::new(Phyllotaxis),
Box::new(Cardioid),
Box::new(Astroid),
Box::new(Lemniscate),
Box::new(MaurerRose),
Box::new(MysticRose),
Box::new(FermatSpiral),
]
}
#[inline]
fn polar_to_dot(cx: f32, cy: f32, r: f32, theta: f32, scale: f32) -> (i32, i32) {
let x = cx + r * theta.cos() * scale;
let y = cy - r * theta.sin() * scale; (x.round() as i32, y.round() as i32)
}
#[inline]
fn fit_scale(dw: usize, dh: usize) -> f32 {
let hw = (dw as f32 / 2.0 - 1.0).max(1.0);
let hh = (dh as f32 / 2.0 - 1.0).max(1.0);
hw.min(hh)
}
#[inline]
fn center(dw: usize, dh: usize) -> (f32, f32) {
(dw as f32 / 2.0, dh as f32 / 2.0)
}
struct Rose;
impl ProgressStyle for Rose {
fn name(&self) -> &str {
"rose"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Rhodonea rose r=cos(k·θ): petals unfurl from the center as progress rises, \
the whole bloom slowly rotating with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let k: f32 = 5.0;
let theta_max = ctx.eased * PI;
let rot = ctx.time * 0.3;
let steps = (512.0 * ctx.eased).max(4.0) as usize;
for i in 0..=steps {
let theta = i as f32 / steps as f32 * theta_max;
let r = (k * theta).cos();
let (x, y) = polar_to_dot(cx, cy, r, theta + rot, scale);
draw::dot_i(grid, x, y);
}
Ok(())
}
}
struct Spirograph;
impl ProgressStyle for Spirograph {
fn name(&self) -> &str {
"spirograph"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Hypotrochoid spirograph: the inner-wheel trace of a circle rolling inside \
a larger circle, drawing an intricate looping flower as progress sweeps θ"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let big_r: f32 = 5.0;
let small_r: f32 = 3.0;
let d: f32 = 5.0;
let ratio = (big_r - small_r) / small_r;
let theta_max = ctx.eased * 2.0 * PI * small_r; let rot = ctx.time * 0.2;
let steps = (800.0 * ctx.eased).max(4.0) as usize;
for i in 0..=steps {
let theta = i as f32 / steps as f32 * theta_max;
let xf = (big_r - small_r) * theta.cos() + d * (ratio * theta).cos();
let yf = (big_r - small_r) * theta.sin() - d * (ratio * theta).sin();
let norm = big_r + d;
let sx = cx + (xf / norm) * scale * (rot.cos());
let sy = cy - (yf / norm) * scale;
let dx = xf / norm * scale;
let dy = yf / norm * scale;
let rx = dx * rot.cos() - dy * rot.sin();
let ry = dx * rot.sin() + dy * rot.cos();
let px = (cx + rx).round() as i32;
let py = (cy - ry).round() as i32;
let _ = (sx, sy); draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct Epitrochoid;
impl ProgressStyle for Epitrochoid {
fn name(&self) -> &str {
"epitrochoid"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Epitrochoid: a small circle rolling *outside* a fixed circle traces \
a multi-lobed crown; progress reveals the full pattern chord by chord"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let big_r: f32 = 3.0;
let small_r: f32 = 1.0;
let d: f32 = 2.5;
let ratio = (big_r + small_r) / small_r;
let theta_max = ctx.eased * 2.0 * PI;
let rot = ctx.time * 0.25;
let steps = (600.0 * ctx.eased).max(4.0) as usize;
let norm = big_r + small_r + d;
for i in 0..=steps {
let theta = i as f32 / steps as f32 * theta_max;
let xf = (big_r + small_r) * theta.cos() - d * (ratio * theta).cos();
let yf = (big_r + small_r) * theta.sin() - d * (ratio * theta).sin();
let dx = xf / norm * scale;
let dy = yf / norm * scale;
let rx = dx * rot.cos() - dy * rot.sin();
let ry = dx * rot.sin() + dy * rot.cos();
let px = (cx + rx).round() as i32;
let py = (cy - ry).round() as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct Superformula;
impl ProgressStyle for Superformula {
fn name(&self) -> &str {
"superformula"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Gielis superformula: a single equation spanning circles, stars, flowers, \
and alien blobs — n-exponents morph continuously with time as the curve \
draws itself with progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let m: f32 = 6.0; let a: f32 = 1.0;
let b: f32 = 1.0;
let n1 = 1.5 + 2.5 * (ctx.time * 0.4).sin().abs();
let n2 = 2.0 + 1.5 * (ctx.time * 0.31).cos();
let n3 = 2.0 + 1.5 * (ctx.time * 0.27).sin();
let rot = ctx.time * 0.18;
let theta_max = ctx.eased * 2.0 * PI;
let steps = (512.0 * ctx.eased).max(4.0) as usize;
let mut r_max: f32 = 0.001;
for i in 0..=256 {
let phi = i as f32 / 256.0 * 2.0 * PI;
let t1 = ((m * phi / 4.0).cos() / a).abs().powf(n2);
let t2 = ((m * phi / 4.0).sin() / b).abs().powf(n3);
let sum = t1 + t2;
if sum > 1e-6 {
let r = sum.powf(-1.0 / n1);
if r > r_max {
r_max = r;
}
}
}
for i in 0..=steps {
let phi = i as f32 / steps as f32 * theta_max;
let t1 = ((m * phi / 4.0).cos() / a).abs().powf(n2);
let t2 = ((m * phi / 4.0).sin() / b).abs().powf(n3);
let sum = t1 + t2;
if sum < 1e-6 {
continue;
}
let r = sum.powf(-1.0 / n1) / r_max;
let dx = r * phi.cos() * scale;
let dy = r * phi.sin() * scale;
let rx = dx * rot.cos() - dy * rot.sin();
let ry = dx * rot.sin() + dy * rot.cos();
let px = (cx + rx).round() as i32;
let py = (cy - ry).round() as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct Phyllotaxis;
impl ProgressStyle for Phyllotaxis {
fn name(&self) -> &str {
"phyllotaxis"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Sunflower phyllotaxis: seeds appear at the golden angle 137.5°, \
radius ∝ √n, producing the mesmerising Fibonacci spiral pattern of \
real sunflowers and pinecones"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let golden_angle: f32 = 2.0 * PI * (1.0 - 1.0 / 1.618_033_9); let n_max: usize = 400;
let n_plot = (ctx.eased * n_max as f32).round() as usize;
let c = scale / (n_max as f32).sqrt();
let rot = ctx.time * 0.15;
for n in 0..n_plot {
let angle = n as f32 * golden_angle + rot;
let r = c * (n as f32).sqrt();
let px = (cx + r * angle.cos()).round() as i32;
let py = (cy - r * angle.sin()).round() as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct Cardioid;
impl ProgressStyle for Cardioid {
fn name(&self) -> &str {
"cardioid"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Cardioid string-art: connecting point i to (2·i mod N) on a circle \
traces the cardioid r=a(1−cosθ) as an emergent envelope — chords appear \
one by one as progress rises"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh) * 0.9;
let n_total: usize = 180; let n_chords = (ctx.eased * n_total as f32).round() as usize;
let rot = ctx.time * 0.2;
for i in 0..n_chords {
let j = (2 * i) % n_total;
let a_i = 2.0 * PI * i as f32 / n_total as f32 + rot;
let a_j = 2.0 * PI * j as f32 / n_total as f32 + rot;
let x0 = (cx + a_i.cos() * scale).round() as i32;
let y0 = (cy - a_i.sin() * scale).round() as i32;
let x1 = (cx + a_j.cos() * scale).round() as i32;
let y1 = (cy - a_j.sin() * scale).round() as i32;
bresenham(grid, x0, y0, x1, y1);
}
Ok(())
}
}
struct Astroid;
impl ProgressStyle for Astroid {
fn name(&self) -> &str {
"astroid"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Astroid (4-cusp hypocycloid): x=R·cos³θ, y=R·sin³θ — a star-shaped \
curve with four sharp cusps that sweeps closed as progress completes a \
full 2π revolution"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let theta_max = ctx.eased * 2.0 * PI;
let rot = ctx.time * 0.22;
let steps = (512.0 * ctx.eased).max(4.0) as usize;
for i in 0..=steps {
let theta = i as f32 / steps as f32 * theta_max;
let xf = theta.cos().powi(3);
let yf = theta.sin().powi(3);
let dx = xf * scale;
let dy = yf * scale;
let rx = dx * rot.cos() - dy * rot.sin();
let ry = dx * rot.sin() + dy * rot.cos();
let px = (cx + rx).round() as i32;
let py = (cy - ry).round() as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct Lemniscate;
impl ProgressStyle for Lemniscate {
fn name(&self) -> &str {
"lemniscate"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Lemniscate of Bernoulli: the ∞ figure-eight (x²+y²)²=a²(x²−y²), \
traced with the rational parametric form — both lobes materialise \
symmetrically as progress sweeps 0→2π"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let theta_max = ctx.eased * 2.0 * PI;
let rot = ctx.time * 0.18;
let steps = (512.0 * ctx.eased).max(4.0) as usize;
for i in 0..=steps {
let t = i as f32 / steps as f32 * theta_max;
let denom = 1.0 + t.sin().powi(2);
if denom.abs() < 1e-6 {
continue;
}
let xf = t.cos() / denom;
let yf = t.sin() * t.cos() / denom;
let dx = xf * scale;
let dy = yf * scale;
let rx = dx * rot.cos() - dy * rot.sin();
let ry = dx * rot.sin() + dy * rot.cos();
let px = (cx + rx).round() as i32;
let py = (cy - ry).round() as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct MaurerRose;
impl ProgressStyle for MaurerRose {
fn name(&self) -> &str {
"maurer-rose"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Maurer rose: rose curve points at d-degree integer steps connected by \
straight chords, producing a densely interlaced star web — d=71°, k=5 \
gives the canonical intricate design"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let k: f32 = 5.0;
let d_deg: f32 = 71.0; let d_rad = d_deg * PI / 180.0;
let n_total: usize = 361; let n_chords = (ctx.eased * n_total as f32).round() as usize;
let rot = ctx.time * 0.15;
let point = |n: usize| -> (i32, i32) {
let theta = n as f32 * d_rad + rot;
let r = (k * theta).cos();
let x = (cx + r * theta.cos() * scale).round() as i32;
let y = (cy - r * theta.sin() * scale).round() as i32;
(x, y)
};
for n in 0..n_chords.saturating_sub(1) {
let (x0, y0) = point(n);
let (x1, y1) = point(n + 1);
bresenham(grid, x0, y0, x1, y1);
}
Ok(())
}
}
struct MysticRose;
impl ProgressStyle for MysticRose {
fn name(&self) -> &str {
"mystic-rose"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Mystic rose: every pair of vertices in a regular 24-gon connected by a \
chord — 276 chords in total, appearing progressively to build a densely \
woven stained-glass wheel"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let n: usize = 24; let total_chords = n * (n - 1) / 2;
let chords_to_draw = (ctx.eased * total_chords as f32).round() as usize;
let rot = ctx.time * 0.12;
let vertex = |i: usize| -> (i32, i32) {
let angle = 2.0 * PI * i as f32 / n as f32 + rot;
let px = (cx + angle.cos() * scale).round() as i32;
let py = (cy - angle.sin() * scale).round() as i32;
(px, py)
};
let mut drawn = 0usize;
'outer: for i in 0..n {
for j in (i + 1)..n {
if drawn >= chords_to_draw {
break 'outer;
}
let (x0, y0) = vertex(i);
let (x1, y1) = vertex(j);
bresenham(grid, x0, y0, x1, y1);
drawn += 1;
}
}
Ok(())
}
}
struct FermatSpiral;
impl ProgressStyle for FermatSpiral {
fn name(&self) -> &str {
"fermat-spiral"
}
fn theme(&self) -> &str {
"geometry"
}
fn describe(&self) -> &str {
"Fermat's (parabolic) spiral r=±a√θ: both symmetric arms uncoil from the \
origin as progress sweeps outward, the pair slowly rotating with time to \
reveal a balanced double helix in braille dots"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let theta_max = ctx.eased * 6.0 * PI;
let rot = ctx.time * 0.17;
let a = 1.0 / (6.0 * PI).sqrt();
let steps = (600.0 * ctx.eased).max(4.0) as usize;
for i in 0..=steps {
let theta = i as f32 / steps as f32 * theta_max;
let r = a * theta.sqrt();
let angle = theta + rot;
let px = (cx + r * angle.cos() * scale).round() as i32;
let py = (cy - r * angle.sin() * scale).round() as i32;
draw::dot_i(grid, px, py);
let qx = (cx - r * angle.cos() * scale).round() as i32;
let qy = (cy + r * angle.sin() * scale).round() as i32;
draw::dot_i(grid, qx, qy);
}
Ok(())
}
}
fn bresenham(grid: &mut BrailleGrid, x0: i32, y0: i32, x1: i32, y1: i32) {
let mut x0 = x0;
let mut y0 = y0;
let dx = (x1 - x0).abs();
let dy = -(y1 - y0).abs();
let sx: i32 = if x0 < x1 { 1 } else { -1 };
let sy: i32 = if y0 < y1 { 1 } else { -1 };
let mut err = dx + dy;
let max_steps = (dx.abs() + dy.abs() + 2) as usize;
let mut steps = 0usize;
loop {
draw::dot_i(grid, x0, y0);
if x0 == x1 && y0 == y1 {
break;
}
steps += 1;
if steps > max_steps {
break;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x0 += sx;
}
if e2 <= dx {
err += dx;
y0 += sy;
}
}
}