use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
#[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 draw_edge(grid: &mut BrailleGrid, x0: i32, y0: i32, x1: i32, y1: i32) {
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 + 2).min(400);
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;
}
}
}
fn grid_centre_scale(grid: &BrailleGrid, shrink: f32) -> (i32, i32, f32) {
let (dw, dh) = draw::dot_dims(grid);
let cx = (dw / 2) as i32;
let cy = (dh / 2) as i32;
let r = (dw / 2).min(dh) as f32;
let scale = (r * 0.82 * shrink).max(1.0);
(cx, cy, scale)
}
fn project_verts(
verts: &[[f32; 3]],
ax: f32,
ay: f32,
cx: i32,
cy: i32,
scale: f32,
) -> Vec<(i32, i32)> {
verts
.iter()
.map(|&[x, y, z]| project(x, y, z, ax, ay, cx, cy, scale))
.collect()
}
fn draw_edges_partial(
grid: &mut BrailleGrid,
pts: &[(i32, i32)],
edges: &[(usize, usize)],
n_show: usize,
) {
for &(a, b) in edges.iter().take(n_show) {
if a < pts.len() && b < pts.len() {
let (x0, y0) = pts[a];
let (x1, y1) = pts[b];
draw_edge(grid, x0, y0, x1, y1);
}
}
}
const TETRA_VERTS: [[f32; 3]; 4] = [
[0.0, 1.0, 0.0], [0.942809, -0.333333, 0.0], [-0.471405, -0.333333, 0.816497], [-0.471405, -0.333333, -0.816497], ];
const TETRA_EDGES: [(usize, usize); 6] = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)];
struct Tetrahedron;
impl ProgressStyle for Tetrahedron {
fn name(&self) -> &str {
"tetrahedron"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Tetrahedron — the fire solid: 4 vertices and 6 edges assembling as progress grows, spinning on time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, 1.0);
let ax = ctx.time * 0.41;
let ay = ctx.time * 0.63;
let pts = project_verts(&TETRA_VERTS, ax, ay, cx, cy, scale);
let n_show = (ctx.eased * TETRA_EDGES.len() as f32).ceil() as usize;
draw_edges_partial(grid, &pts, &TETRA_EDGES, n_show);
Ok(())
}
}
const S3: f32 = 0.577_350_3;
const CUBE_VERTS: [[f32; 3]; 8] = [
[-S3, -S3, -S3],
[S3, -S3, -S3],
[S3, S3, -S3],
[-S3, S3, -S3],
[-S3, -S3, S3],
[S3, -S3, S3],
[S3, S3, S3],
[-S3, S3, S3],
];
const CUBE_EDGES: [(usize, usize); 12] = [
(0, 1),
(1, 2),
(2, 3),
(3, 0),
(4, 5),
(5, 6),
(6, 7),
(7, 4),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
];
struct Cube;
impl ProgressStyle for Cube {
fn name(&self) -> &str {
"cube"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Hexahedron — the earth solid: 8-vertex cube with 12 edges revealed face-by-face as progress grows"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, 1.0);
let ax = ctx.time * 0.37;
let ay = ctx.time * 0.51;
let pts = project_verts(&CUBE_VERTS, ax, ay, cx, cy, scale);
let n_show = (ctx.eased * CUBE_EDGES.len() as f32).ceil() as usize;
draw_edges_partial(grid, &pts, &CUBE_EDGES, n_show);
Ok(())
}
}
const OCTA_VERTS: [[f32; 3]; 6] = [
[1.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, -1.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, -1.0],
];
const OCTA_EDGES: [(usize, usize); 12] = [
(0, 2),
(0, 3),
(0, 4),
(0, 5),
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(2, 4),
(2, 5),
(3, 4),
(3, 5),
];
struct Octahedron;
impl ProgressStyle for Octahedron {
fn name(&self) -> &str {
"octahedron"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Octahedron — the air solid: 6-vertex double-pyramid with 12 equilateral-triangle edges spinning on time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, 1.0);
let ax = ctx.time * 0.44;
let ay = ctx.time * 0.59;
let pts = project_verts(&OCTA_VERTS, ax, ay, cx, cy, scale);
let n_show = (ctx.eased * OCTA_EDGES.len() as f32).ceil() as usize;
draw_edges_partial(grid, &pts, &OCTA_EDGES, n_show);
Ok(())
}
}
const PHI: f32 = 1.618_033_9;
const INV_PHI: f32 = 0.618_033_9;
const DODECA_SCALE: f32 = 1.0 / 1.732_050_8;
const DODECA_VERTS: [[f32; 3]; 20] = [
[1.0, 1.0, 1.0],
[1.0, 1.0, -1.0],
[1.0, -1.0, 1.0],
[1.0, -1.0, -1.0],
[-1.0, 1.0, 1.0],
[-1.0, 1.0, -1.0],
[-1.0, -1.0, 1.0],
[-1.0, -1.0, -1.0],
[0.0, INV_PHI, PHI],
[0.0, INV_PHI, -PHI],
[0.0, -INV_PHI, PHI],
[0.0, -INV_PHI, -PHI],
[INV_PHI, PHI, 0.0],
[INV_PHI, -PHI, 0.0],
[-INV_PHI, PHI, 0.0],
[-INV_PHI, -PHI, 0.0],
[PHI, 0.0, INV_PHI],
[PHI, 0.0, -INV_PHI],
[-PHI, 0.0, INV_PHI],
[-PHI, 0.0, -INV_PHI],
];
const DODECA_EDGES: [(usize, usize); 30] = [
(0, 8),
(0, 12),
(0, 16),
(1, 9),
(1, 12),
(1, 17),
(2, 10),
(2, 13),
(2, 16),
(3, 11),
(3, 13),
(3, 17),
(4, 8),
(4, 14),
(4, 18),
(5, 9),
(5, 14),
(5, 19),
(6, 10),
(6, 15),
(6, 18),
(7, 11),
(7, 15),
(7, 19),
(8, 10),
(9, 11),
(12, 14),
(13, 15),
(16, 17),
(18, 19),
];
struct Dodecahedron;
impl ProgressStyle for Dodecahedron {
fn name(&self) -> &str {
"dodecahedron"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Dodecahedron — the aether solid: 20 golden-ratio vertices and 30 pentagonal edges spinning and assembling"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, DODECA_SCALE);
let ax = ctx.time * 0.29;
let ay = ctx.time * 0.47;
let pts = project_verts(&DODECA_VERTS, ax, ay, cx, cy, scale);
let n_show = (ctx.eased * DODECA_EDGES.len() as f32).ceil() as usize;
draw_edges_partial(grid, &pts, &DODECA_EDGES, n_show);
Ok(())
}
}
const ICOSA_SCALE: f32 = 1.0 / 1.902_113_0;
const ICOSA_VERTS: [[f32; 3]; 12] = [
[0.0, 1.0, PHI],
[0.0, 1.0, -PHI],
[0.0, -1.0, PHI],
[0.0, -1.0, -PHI],
[1.0, PHI, 0.0],
[1.0, -PHI, 0.0],
[-1.0, PHI, 0.0],
[-1.0, -PHI, 0.0],
[PHI, 0.0, 1.0],
[PHI, 0.0, -1.0],
[-PHI, 0.0, 1.0],
[-PHI, 0.0, -1.0],
];
const ICOSA_EDGES: [(usize, usize); 30] = [
(0, 2),
(0, 4),
(0, 6),
(0, 8),
(0, 10),
(4, 8),
(8, 2),
(2, 10),
(10, 6),
(6, 4),
(3, 1),
(3, 5),
(3, 7),
(3, 9),
(3, 11),
(1, 9),
(9, 5),
(5, 7),
(7, 11),
(11, 1),
(4, 1),
(1, 6),
(6, 11),
(11, 10),
(10, 7),
(7, 5),
(5, 2),
(2, 8),
(8, 9),
(9, 3),
];
struct Icosahedron;
impl ProgressStyle for Icosahedron {
fn name(&self) -> &str {
"icosahedron"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Icosahedron — the water solid: 12 golden-ratio vertices forming 30 equilateral-triangle edges, spinning on time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, ICOSA_SCALE);
let ax = ctx.time * 0.33;
let ay = ctx.time * 0.54;
let pts = project_verts(&ICOSA_VERTS, ax, ay, cx, cy, scale);
let n_show = (ctx.eased * ICOSA_EDGES.len() as f32).ceil() as usize;
draw_edges_partial(grid, &pts, &ICOSA_EDGES, n_show);
Ok(())
}
}
struct Merkaba;
impl ProgressStyle for Merkaba {
fn name(&self) -> &str {
"merkaba"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Merkaba (star-tetrahedron): two interlocked tetrahedra counter-rotating — the light-body vehicle of sacred geometry"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, 1.0);
let ax_up = ctx.time * 0.39;
let ay_up = ctx.time * 0.57;
let ax_dn = ctx.time * 0.39;
let ay_dn = -ctx.time * 0.57;
let pts_up = project_verts(&TETRA_VERTS, ax_up, ay_up, cx, cy, scale);
let tetra_down: [[f32; 3]; 4] = TETRA_VERTS.map(|[x, y, z]| [x, -y, z]);
let pts_dn = project_verts(&tetra_down, ax_dn, ay_dn, cx, cy, scale);
let total_edges = TETRA_EDGES.len() * 2;
let n_show = (ctx.eased * total_edges as f32).ceil() as usize;
let n_up = n_show.min(TETRA_EDGES.len());
let n_dn = n_show.saturating_sub(TETRA_EDGES.len());
draw_edges_partial(grid, &pts_up, &TETRA_EDGES, n_up);
draw_edges_partial(grid, &pts_dn, &TETRA_EDGES, n_dn);
Ok(())
}
}
struct StarOctangulum;
impl ProgressStyle for StarOctangulum {
fn name(&self) -> &str {
"star-octangulum"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Stella octangula: two tetrahedra dual to each other inscribed in an octahedron — eight triangular spikes, one rotation axis"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, 1.0);
let ax = ctx.time * 0.35;
let ay = ctx.time * 0.52;
let pts_up = project_verts(&TETRA_VERTS, ax, ay, cx, cy, scale);
let tetra_dual: [[f32; 3]; 4] = TETRA_VERTS.map(|[x, y, z]| [-x, -y, -z]);
let pts_dn = project_verts(&tetra_dual, ax, ay, cx, cy, scale);
let pts_oct = project_verts(&OCTA_VERTS, ax, ay, cx, cy, scale * 0.577_350_3);
let total = TETRA_EDGES.len() * 2 + OCTA_EDGES.len();
let n_show = (ctx.eased * total as f32).ceil() as usize;
let n_up = n_show.min(TETRA_EDGES.len());
let n_dn = n_show
.saturating_sub(TETRA_EDGES.len())
.min(TETRA_EDGES.len());
let n_oct = n_show.saturating_sub(TETRA_EDGES.len() * 2);
draw_edges_partial(grid, &pts_up, &TETRA_EDGES, n_up);
draw_edges_partial(grid, &pts_dn, &TETRA_EDGES, n_dn);
draw_edges_partial(grid, &pts_oct, &OCTA_EDGES, n_oct);
Ok(())
}
}
const CUBOCTA_VERTS: [[f32; 3]; 12] = [
[1.0, 1.0, 0.0],
[1.0, -1.0, 0.0],
[-1.0, 1.0, 0.0],
[-1.0, -1.0, 0.0],
[1.0, 0.0, 1.0],
[1.0, 0.0, -1.0],
[-1.0, 0.0, 1.0],
[-1.0, 0.0, -1.0],
[0.0, 1.0, 1.0],
[0.0, 1.0, -1.0],
[0.0, -1.0, 1.0],
[0.0, -1.0, -1.0],
];
const CUBOCTA_SCALE: f32 = 1.0 / 1.414_213_6;
const CUBOCTA_EDGES: [(usize, usize); 24] = [
(0, 4),
(0, 5),
(0, 8),
(0, 9),
(1, 4),
(1, 5),
(1, 10),
(1, 11),
(2, 6),
(2, 7),
(2, 8),
(2, 9),
(3, 6),
(3, 7),
(3, 10),
(3, 11),
(4, 8),
(4, 10),
(5, 9),
(5, 11),
(6, 8),
(6, 10),
(7, 9),
(7, 11),
];
struct Cuboctahedron;
impl ProgressStyle for Cuboctahedron {
fn name(&self) -> &str {
"cuboctahedron"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Cuboctahedron — vector equilibrium / Metatron's core: 12 vertices at edge-midpoints of a cube, 8 triangles and 6 squares"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, CUBOCTA_SCALE);
let ax = ctx.time * 0.31;
let ay = ctx.time * 0.49;
let pts = project_verts(&CUBOCTA_VERTS, ax, ay, cx, cy, scale);
let n_show = (ctx.eased * CUBOCTA_EDGES.len() as f32).ceil() as usize;
draw_edges_partial(grid, &pts, &CUBOCTA_EDGES, n_show);
Ok(())
}
}
struct NestedSolids;
impl ProgressStyle for NestedSolids {
fn name(&self) -> &str {
"nested-solids"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Nested duals: a cube spinning inside its dual octahedron — both rotating at different rates, revealed in two waves"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, 1.0);
let ax_oct = ctx.time * 0.27;
let ay_oct = ctx.time * 0.41;
let pts_oct = project_verts(&OCTA_VERTS, ax_oct, ay_oct, cx, cy, scale);
let cube_inner_scale = scale * 0.577_350_3; let ax_cube = ctx.time * 0.55;
let ay_cube = ctx.time * 0.71;
let pts_cube = project_verts(&CUBE_VERTS, ax_cube, ay_cube, cx, cy, cube_inner_scale);
let n_show = (ctx.eased * (OCTA_EDGES.len() + CUBE_EDGES.len()) as f32).ceil() as usize;
let n_oct = n_show.min(OCTA_EDGES.len());
let n_cube = n_show.saturating_sub(OCTA_EDGES.len());
draw_edges_partial(grid, &pts_oct, &OCTA_EDGES, n_oct);
draw_edges_partial(grid, &pts_cube, &CUBE_EDGES, n_cube);
Ok(())
}
}
struct StellatedDodecahedron;
impl ProgressStyle for StellatedDodecahedron {
fn name(&self) -> &str {
"stellated-dodecahedron"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Great stellated dodecahedron: dodecahedron with 12 pentagonal star-pyramids erupting from each face — a cosmic sea-urchin"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, DODECA_SCALE * 0.72);
let ax = ctx.time * 0.25;
let ay = ctx.time * 0.43;
let pts_dodeca = project_verts(&DODECA_VERTS, ax, ay, cx, cy, scale);
let spike_scale = scale * PHI * ICOSA_SCALE;
let pts_spikes = project_verts(&ICOSA_VERTS, ax, ay, cx, cy, spike_scale);
let spike_fans: [(usize, [usize; 5]); 12] = [
(0, [0, 8, 10, 4, 2]), (1, [1, 9, 11, 5, 3]), (2, [0, 12, 14, 4, 16]), (3, [1, 12, 14, 5, 17]), (4, [0, 8, 12, 16, 2]),
(5, [6, 10, 15, 7, 18]),
(6, [3, 11, 13, 15, 7]),
(7, [3, 13, 17, 11, 19]),
(8, [4, 14, 5, 19, 18]),
(9, [2, 16, 17, 3, 13]),
(10, [6, 18, 19, 7, 15]),
(11, [1, 9, 11, 19, 5]),
];
let n_base = DODECA_EDGES.len();
let n_spokes = 12 * 5; let total = n_base + n_spokes;
let n_show = (ctx.eased * total as f32).ceil() as usize;
let n_d = n_show.min(n_base);
let n_s = n_show.saturating_sub(n_base);
draw_edges_partial(grid, &pts_dodeca, &DODECA_EDGES, n_d);
let mut spoke_drawn = 0usize;
'outer: for &(si, verts_around) in &spike_fans {
for &di in &verts_around {
if spoke_drawn >= n_s {
break 'outer;
}
if si < pts_spikes.len() && di < pts_dodeca.len() {
let (x0, y0) = pts_spikes[si];
let (x1, y1) = pts_dodeca[di];
draw_edge(grid, x0, y0, x1, y1);
}
spoke_drawn += 1;
}
}
Ok(())
}
}
struct UnfoldingNet;
impl ProgressStyle for UnfoldingNet {
fn name(&self) -> &str {
"unfolding-net"
}
fn theme(&self) -> &str {
"platonic"
}
fn describe(&self) -> &str {
"Cube net: a cross-shaped flat net folds progressively into a 3-D cube as progress advances — geometry becoming solid"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cx, cy, scale) = grid_centre_scale(grid, 0.9);
let ax = ctx.time * 0.30;
let ay = ctx.time * 0.48;
let t = ctx.eased;
let half = 0.5_f32;
let net: [[[f32; 3]; 4]; 6] = [
[
[-half, -half, 0.0],
[half, -half, 0.0],
[half, half, 0.0],
[-half, half, 0.0],
],
[
[half, -half, 0.0],
[3.0 * half, -half, 0.0],
[3.0 * half, half, 0.0],
[half, half, 0.0],
],
[
[-3.0 * half, -half, 0.0],
[-half, -half, 0.0],
[-half, half, 0.0],
[-3.0 * half, half, 0.0],
],
[
[3.0 * half, -half, 0.0],
[5.0 * half, -half, 0.0],
[5.0 * half, half, 0.0],
[3.0 * half, half, 0.0],
],
[
[-half, half, 0.0],
[half, half, 0.0],
[half, 3.0 * half, 0.0],
[-half, 3.0 * half, 0.0],
],
[
[-half, -3.0 * half, 0.0],
[half, -3.0 * half, 0.0],
[half, -half, 0.0],
[-half, -half, 0.0],
],
];
let cube: [[[f32; 3]; 4]; 6] = [
[
[-half, -half, half],
[half, -half, half],
[half, half, half],
[-half, half, half],
],
[
[half, -half, half],
[half, -half, -half],
[half, half, -half],
[half, half, half],
],
[
[-half, -half, -half],
[-half, -half, half],
[-half, half, half],
[-half, half, -half],
],
[
[half, -half, -half],
[-half, -half, -half],
[-half, half, -half],
[half, half, -half],
],
[
[-half, half, half],
[half, half, half],
[half, half, -half],
[-half, half, -half],
],
[
[-half, -half, -half],
[half, -half, -half],
[half, -half, half],
[-half, -half, half],
],
];
let net_s = scale / 3.0;
let cube_s = scale;
for fi in 0..6usize {
let corners: Vec<(i32, i32)> = (0..4)
.map(|ci| {
let [nx, ny, nz] = net[fi][ci];
let [cx2, cy2, cz] = cube[fi][ci];
let x = nx + (cx2 - nx) * t;
let y = ny + (cy2 - ny) * t;
let z = nz + (cz - nz) * t;
let s = net_s + (cube_s - net_s) * t;
project(x, y, z, ax, ay, cx, cy, s)
})
.collect();
for ei in 0..4usize {
let (x0, y0) = corners[ei];
let (x1, y1) = corners[(ei + 1) % 4];
draw_edge(grid, x0, y0, x1, y1);
}
}
Ok(())
}
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(Tetrahedron),
Box::new(Cube),
Box::new(Octahedron),
Box::new(Dodecahedron),
Box::new(Icosahedron),
Box::new(Merkaba),
Box::new(StarOctangulum),
Box::new(Cuboctahedron),
Box::new(NestedSolids),
Box::new(StellatedDodecahedron),
Box::new(UnfoldingNet),
]
}