use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
const PHI: f32 = 1.6180339887;
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(PenroseP3),
Box::new(PenroseP2),
Box::new(SunPattern),
Box::new(GirihTiles),
Box::new(AmmannBars),
Box::new(DeBruijnPentagrid),
Box::new(DecagonFractal),
Box::new(QuasicrystalDiffraction),
Box::new(PinwheelTiling),
Box::new(TruchetQuasi),
]
}
#[inline]
fn center(dw: usize, dh: usize) -> (f32, f32) {
(dw as f32 * 0.5, dh as f32 * 0.5)
}
#[inline]
fn fit_scale(dw: usize, dh: usize) -> f32 {
let hw = (dw as f32 * 0.5 - 1.0).max(1.0);
let hh = (dh as f32 * 0.5 - 1.0).max(1.0);
hw.min(hh)
}
fn bresenham(grid: &mut BrailleGrid, x0: i32, y0: i32, x1: i32, y1: i32) {
let mut x = x0;
let mut y = 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, x, y);
if x == x1 && y == y1 {
break;
}
steps += 1;
if steps > max_steps {
break;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x += sx;
}
if e2 <= dx {
err += dx;
y += sy;
}
}
}
fn draw_poly(grid: &mut BrailleGrid, pts: &[(i32, i32)]) {
let n = pts.len();
if n < 2 {
return;
}
for i in 0..n {
let (x0, y0) = pts[i];
let (x1, y1) = pts[(i + 1) % n];
bresenham(grid, x0, y0, x1, y1);
}
}
#[inline]
fn to_dot(cx: f32, cy: f32, scale: f32, ux: f32, uy: f32, rot: f32) -> (i32, i32) {
let rx = ux * rot.cos() - uy * rot.sin();
let ry = ux * rot.sin() + uy * rot.cos();
(
(cx + rx * scale).round() as i32,
(cy - ry * scale).round() as i32,
)
}
struct PenroseP3;
impl ProgressStyle for PenroseP3 {
fn name(&self) -> &str {
"penrose-p3"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"Penrose P3 rhombus tiling: Robinson-triangle deflation reveals fat (72°) \
and thin (36°) rhombi generation by generation as progress rises; the \
whole pattern rotates slowly with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let rot = ctx.time * 0.25;
let depth = (ctx.eased * 4.0).floor() as usize;
let reveal_frac = (ctx.eased * 4.0).fract();
let mut tris: Vec<(bool, [f32; 2], [f32; 2], [f32; 2])> = Vec::new();
for k in 0..10usize {
let a1 = (k as f32 * 36.0) * PI / 180.0;
let a2 = (k as f32 * 36.0 + 36.0) * PI / 180.0;
let p = [0.0f32, 0.0];
let q = [a1.cos(), a1.sin()];
let r = [a2.cos(), a2.sin()];
let is_acute = k % 2 == 0;
tris.push((is_acute, p, q, r));
}
for _step in 0..depth.min(4) {
tris = deflate_p3(tris);
if tris.len() > 3000 {
break;
} }
let n_total = tris.len();
let n_draw = if depth < 4 {
(reveal_frac * n_total as f32).round() as usize
} else {
n_total
};
for tri in tris.iter().take(n_draw) {
let (_is_acute, p, q, r) = tri;
let pd = to_dot(cx, cy, scale, p[0], p[1], rot);
let qd = to_dot(cx, cy, scale, q[0], q[1], rot);
let rd = to_dot(cx, cy, scale, r[0], r[1], rot);
draw_poly(grid, &[pd, qd, rd]);
}
Ok(())
}
}
fn deflate_p3(
tris: Vec<(bool, [f32; 2], [f32; 2], [f32; 2])>,
) -> Vec<(bool, [f32; 2], [f32; 2], [f32; 2])> {
let mut out = Vec::with_capacity(tris.len() * 2);
for (is_acute, p, q, r) in tris {
if is_acute {
let s = lerp2(p, q, 1.0 / PHI);
out.push((true, q, s, r));
out.push((false, p, s, r));
} else {
let s = lerp2(r, p, 1.0 / PHI);
out.push((false, q, s, p));
out.push((true, q, r, s));
}
}
out
}
#[inline]
fn lerp2(a: [f32; 2], b: [f32; 2], t: f32) -> [f32; 2] {
[a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]
}
struct PenroseP2;
impl ProgressStyle for PenroseP2 {
fn name(&self) -> &str {
"penrose-p2"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"Penrose P2 kite & dart tiling: golden-triangle / golden-gnomon subdivision \
reveals the kite-and-dart mosaic generation by generation; time animates \
a gentle shimmer across revealed tiles"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let rot = ctx.time * 0.18;
let depth = (ctx.eased * 4.0).floor() as usize;
let reveal_frac = (ctx.eased * 4.0).fract();
let mut tris: Vec<(bool, [f32; 2], [f32; 2], [f32; 2])> = Vec::new();
for k in 0..5usize {
let a_mid = (k as f32 * 72.0 + 90.0) * PI / 180.0;
let a_lo = (k as f32 * 72.0 + 90.0 - 36.0) * PI / 180.0;
let a_hi = (k as f32 * 72.0 + 90.0 + 36.0) * PI / 180.0;
let p = [0.0f32, 0.0];
let q = [a_lo.cos(), a_lo.sin()];
let r = [a_hi.cos(), a_hi.sin()];
let _ = a_mid;
tris.push((true, p, q, r)); }
for _step in 0..depth.min(4) {
tris = deflate_p2(tris);
if tris.len() > 3000 {
break;
}
}
let n_total = tris.len();
let n_draw = if depth < 4 {
(reveal_frac * n_total as f32).round() as usize
} else {
n_total
};
for tri in tris.iter().take(n_draw) {
let (_gt, p, q, r) = tri;
let pd = to_dot(cx, cy, scale, p[0], p[1], rot);
let qd = to_dot(cx, cy, scale, q[0], q[1], rot);
let rd = to_dot(cx, cy, scale, r[0], r[1], rot);
draw_poly(grid, &[pd, qd, rd]);
}
Ok(())
}
}
fn deflate_p2(
tris: Vec<(bool, [f32; 2], [f32; 2], [f32; 2])>,
) -> Vec<(bool, [f32; 2], [f32; 2], [f32; 2])> {
let mut out = Vec::with_capacity(tris.len() * 2);
for (is_gt, p, q, r) in tris {
if is_gt {
let s = lerp2(p, r, 1.0 / PHI);
out.push((true, p, q, s));
out.push((false, s, q, r));
} else {
let s = lerp2(q, p, 1.0 / PHI);
out.push((true, r, q, s));
out.push((false, p, r, s));
}
}
out
}
struct SunPattern;
impl ProgressStyle for SunPattern {
fn name(&self) -> &str {
"sun-pattern"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"Penrose sun: five kites sharing the central vertex, then the surrounding \
dart ring, then outer kite rings — each concentric generation appears as \
progress rises, pulsing gently with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let rot = ctx.time * 0.15;
let rings = ((ctx.eased * 3.0) as usize).max(1).min(3);
for ring in 0..rings {
let ring_scale = scale / (1.0 + ring as f32 * 0.6);
let pulse = (ctx.time * (1.0 + ring as f32 * 0.5)).sin() * 0.5 + 0.5;
if ring > 0 && pulse < 0.3 {
continue;
}
for k in 0..5usize {
let a = k as f32 * 72.0 * PI / 180.0 + rot + ring as f32 * 36.0 * PI / 180.0;
let tip = [a.cos(), a.sin()];
let lw = [(a + PI / 5.0).cos() / PHI, (a + PI / 5.0).sin() / PHI];
let rw = [(a - PI / 5.0).cos() / PHI, (a - PI / 5.0).sin() / PHI];
let ctr = [0.0f32, 0.0];
let pd = to_dot(cx, cy, ring_scale, ctr[0], ctr[1], 0.0);
let qd = to_dot(cx, cy, ring_scale, lw[0], lw[1], 0.0);
let rd = to_dot(cx, cy, ring_scale, tip[0], tip[1], 0.0);
let sd = to_dot(cx, cy, ring_scale, rw[0], rw[1], 0.0);
draw_poly(grid, &[pd, qd, rd, sd]);
}
}
Ok(())
}
}
struct GirihTiles;
impl ProgressStyle for GirihTiles {
fn name(&self) -> &str {
"girih-tiles"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"Islamic girih tile strapwork: a central 10-gon surrounded by pentagons \
and bowties; interior strap lines reveal with progress, the whole pattern \
rotating with time like a medieval mosque decoration"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh) * 0.45;
let rot = ctx.time * 0.12;
let dec = regular_ngon(10, cx, cy, scale, rot);
draw_poly(grid, &dec);
let n_straps = (ctx.eased * 10.0).round() as usize;
for i in 0..n_straps.min(10) {
let j = (i + 2) % 10;
bresenham(grid, dec[i].0, dec[i].1, dec[j].0, dec[j].1);
}
let n_pent = (ctx.eased * 10.0 * 2.0 - 10.0).round() as usize; for k in 0..n_pent.min(10) {
let a = k as f32 * 36.0 * PI / 180.0 + rot + 18.0 * PI / 180.0;
let dist = scale * (1.0 + 1.0 / (2.0 * (PI / 10.0).tan()));
let pcx = cx + dist * a.cos();
let pcy = cy - dist * a.sin();
let side = scale * 2.0 * (PI / 10.0).sin();
let pent = regular_ngon(5, pcx, pcy, side * 0.5, rot + a);
draw_poly(grid, &pent);
}
Ok(())
}
}
fn regular_ngon(n: usize, cx: f32, cy: f32, radius: f32, offset: f32) -> Vec<(i32, i32)> {
(0..n)
.map(|k| {
let a = 2.0 * PI * k as f32 / n as f32 + offset;
(
(cx + radius * a.cos()).round() as i32,
(cy - radius * a.sin()).round() as i32,
)
})
.collect()
}
struct AmmannBars;
impl ProgressStyle for AmmannBars {
fn name(&self) -> &str {
"ammann-bars"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"Ammann bars: the five quasiperiodic stripe families that decorate every \
Penrose P3 rhombus tiling, with long-L and short-S spacings in ratio PHI; \
families reveal one by one as progress rises"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let rot = ctx.time * 0.08;
let n_families = (ctx.eased * 5.0).ceil() as usize;
let family_frac = (ctx.eased * 5.0).fract();
for fam in 0..n_families.min(5) {
let angle = fam as f32 * 36.0 * PI / 180.0 + rot;
let perp_x = angle.cos();
let perp_y = -angle.sin();
let line_x = -angle.sin();
let line_y = -angle.cos();
let max_lines = 20usize;
let mut offsets: Vec<f32> = Vec::with_capacity(max_lines * 2 + 1);
offsets.push(0.0);
let mut pos = 0.0f32;
let l_step = scale * PHI / (PHI + 1.0);
let s_step = scale * 1.0 / (PHI + 1.0);
let mut fib_a = 1usize;
let mut fib_b = 1usize;
for _i in 0..max_lines {
let use_long = fib_a > fib_b;
let step = if use_long { l_step } else { s_step };
let old_a = fib_a;
fib_a = fib_a + fib_b;
fib_b = old_a;
let fib_a_c = fib_a;
let fib_b_c = fib_b;
let _ = (fib_a_c, fib_b_c);
pos += step;
offsets.push(pos);
offsets.push(-pos);
}
let n_lines = if fam + 1 == n_families {
(family_frac * offsets.len() as f32) as usize
} else {
offsets.len()
};
for &off in offsets.iter().take(n_lines) {
let base_x = cx + perp_x * off;
let base_y = cy + perp_y * off;
let half = scale * 1.5;
let x0 = (base_x + line_x * half).round() as i32;
let y0 = (base_y + line_y * half).round() as i32;
let x1 = (base_x - line_x * half).round() as i32;
let y1 = (base_y - line_y * half).round() as i32;
bresenham(grid, x0, y0, x1, y1);
}
}
Ok(())
}
}
struct DeBruijnPentagrid;
impl ProgressStyle for DeBruijnPentagrid {
fn name(&self) -> &str {
"debruijn-pentagrid"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"de Bruijn pentagrid: five families of quasiperiodic parallel lines at 72° \
intervals whose dual gives a Penrose tiling; the grid weaves in with \
progress and the dual rhombus vertices twinkle with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let rot = ctx.time * 0.07;
let gammas: [f32; 5] = [0.1, 0.2, -0.15, 0.05, -0.08]; let n_families = (ctx.eased * 5.0).ceil() as usize;
for fam in 0..n_families.min(5) {
let angle = fam as f32 * 72.0 * PI / 180.0 + rot;
let perp_x = angle.cos();
let perp_y = -angle.sin();
let line_x = -angle.sin();
let line_y = -angle.cos();
let gamma = gammas[fam];
let lines = 9i32;
for m in -lines / 2..=lines / 2 {
let off = (m as f32 + gamma) * scale * 0.4;
let base_x = cx + perp_x * off;
let base_y = cy + perp_y * off;
let half = scale * 1.5;
let x0 = (base_x + line_x * half).round() as i32;
let y0 = (base_y + line_y * half).round() as i32;
let x1 = (base_x - line_x * half).round() as i32;
let y1 = (base_y - line_y * half).round() as i32;
bresenham(grid, x0, y0, x1, y1);
}
}
if ctx.eased > 0.7 {
let dual_frac = (ctx.eased - 0.7) / 0.3;
let step = scale * 0.4;
let mut pts: Vec<(i32, i32)> = Vec::new();
for fj in 0..5usize {
for fk in (fj + 1)..5usize {
let aj = fj as f32 * 72.0 * PI / 180.0 + rot;
let ak = fk as f32 * 72.0 * PI / 180.0 + rot;
for mj in -4i32..=4 {
for mk in -4i32..=4 {
let bj_x = (mj as f32 + gammas[fj]) * step * aj.cos();
let bj_y = (mj as f32 + gammas[fj]) * step * (-aj.sin());
let bk_x = (mk as f32 + gammas[fk]) * step * ak.cos();
let bk_y = (mk as f32 + gammas[fk]) * step * (-ak.sin());
let dj_x = -aj.sin();
let dj_y = -aj.cos();
let dk_x = -ak.sin();
let dk_y = -ak.cos();
let det = dj_x * dk_y - dj_y * dk_x;
if det.abs() < 1e-6 {
continue;
}
let dx = bk_x - bj_x;
let dy = bk_y - bj_y;
let t = (dx * dk_y - dy * dk_x) / det;
let ix = cx + bj_x + t * dj_x;
let iy = cy + bj_y + t * dj_y;
pts.push((ix.round() as i32, iy.round() as i32));
}
}
}
}
let n_pts = (dual_frac * pts.len() as f32).round() as usize;
for &(px, py) in pts.iter().take(n_pts) {
draw::dot_i(grid, px, py);
draw::dot_i(grid, px + 1, py);
draw::dot_i(grid, px - 1, py);
draw::dot_i(grid, px, py + 1);
draw::dot_i(grid, px, py - 1);
}
}
Ok(())
}
}
struct DecagonFractal;
impl ProgressStyle for DecagonFractal {
fn name(&self) -> &str {
"decagon-fractal"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"Decagon fractal: a central 10-gon filled with rings of smaller 10-gons \
at each recursion level, forming a self-similar quasicrystalline snowflake \
that unfolds as progress rises"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh) * 0.9;
let rot = ctx.time * 0.1;
let depth = (ctx.eased * 3.0).floor() as usize;
let reveal_frac = (ctx.eased * 3.0).fract();
let mut decagons: Vec<(f32, f32, f32, f32)> = vec![(cx, cy, scale, rot)];
for _d in 0..depth.min(3) {
let mut next = Vec::new();
for &(dx, dy, r, phase) in &decagons {
let sub_r = r / (1.0 + PHI);
let dist = r - sub_r + sub_r * 0.1; for k in 0..10usize {
let a = k as f32 * 36.0 * PI / 180.0 + phase;
let nx = dx + dist * a.cos();
let ny = dy - dist * a.sin();
next.push((nx, ny, sub_r, phase + a));
}
}
for &(dx, dy, r, phase) in &decagons {
let pts = regular_ngon(10, dx, dy, r, phase);
draw_poly(grid, &pts);
}
decagons = next;
if decagons.len() > 500 {
break;
}
}
let n_draw = (reveal_frac * decagons.len() as f32).round() as usize;
for &(dx, dy, r, phase) in decagons.iter().take(n_draw) {
let pts = regular_ngon(10, dx, dy, r, phase);
draw_poly(grid, &pts);
}
Ok(())
}
}
struct QuasicrystalDiffraction;
impl ProgressStyle for QuasicrystalDiffraction {
fn name(&self) -> &str {
"quasicrystal-diffraction"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"Quasicrystal interference: sum of 5 plane waves at 72° intervals produces \
a 10-fold diffraction pattern; a moving threshold cuts through the field as \
progress rises, the whole pattern rotating slowly with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh).max(1.0);
let freq = 3.0 / scale;
let phase = ctx.time * 0.4;
let threshold = 5.0 - ctx.eased * 10.0;
for dy in 0..dh {
for dx in 0..dw {
let ux = (dx as f32 - cx) / scale;
let uy = (dy as f32 - cy) / scale;
let mut f = 0.0f32;
for k in 0..5usize {
let angle = k as f32 * 72.0 * PI / 180.0 + phase;
f += (2.0 * PI * freq * (ux * angle.cos() + uy * angle.sin()) * scale).cos();
}
if f > threshold {
draw::dot(grid, dx, dy);
}
}
}
Ok(())
}
}
struct PinwheelTiling;
impl ProgressStyle for PinwheelTiling {
fn name(&self) -> &str {
"pinwheel-tiling"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"Pinwheel tiling: a 1:2 right triangle substitutes into 5 smaller copies \
at irrational rotations, filling the plane with triangles at every angle; \
deflation generations bloom outward as progress rises"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh) * 0.8;
let rot = ctx.time * 0.12;
let depth = (ctx.eased * 3.0).floor() as usize;
let reveal_frac = (ctx.eased * 3.0).fract();
let seed_scale = 1.0 / 5.0f32.sqrt(); type Tri = [[f32; 2]; 3];
let seed: Tri = [[0.0, 0.0], [seed_scale, 0.0], [0.0, seed_scale * 2.0]];
let mut tris: Vec<Tri> = vec![seed];
for _step in 0..depth.min(3) {
tris = pinwheel_deflate(tris);
if tris.len() > 2000 {
break;
}
}
let n_total = tris.len();
let n_draw = if depth < 3 {
(reveal_frac * n_total as f32).round() as usize
} else {
n_total
};
for tri in tris.iter().take(n_draw) {
let pts: Vec<(i32, i32)> = tri
.iter()
.map(|v| to_dot(cx, cy, scale, v[0], v[1], rot))
.collect();
draw_poly(grid, &pts);
}
Ok(())
}
}
type Tri2 = [[f32; 2]; 3];
fn pinwheel_deflate(tris: Vec<Tri2>) -> Vec<Tri2> {
let mut out = Vec::with_capacity(tris.len() * 5);
for tri in tris {
let [p, q, r] = tri;
let m = lerp2(p, r, 0.5);
let a = lerp2(p, q, 0.2);
let b = lerp2(p, q, 0.4);
let c = lerp2(q, r, 0.5);
let d = lerp2(m, q, 0.5);
out.push([p, a, m]);
out.push([a, b, d]);
out.push([b, q, c]);
out.push([d, c, m]);
out.push([m, c, r]);
}
out
}
struct TruchetQuasi;
impl ProgressStyle for TruchetQuasi {
fn name(&self) -> &str {
"truchet-quasi"
}
fn theme(&self) -> &str {
"penrose"
}
fn describe(&self) -> &str {
"Truchet-quasi: quarter-circle arcs placed in the fat and thin rhombi of a \
Penrose-like quasiperiodic lattice; orientation is deterministically varied \
so the arc flow never repeats, revealed tile by tile with progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (cx, cy) = center(dw, dh);
let scale = fit_scale(dw, dh);
let rot = ctx.time * 0.09;
let spacing = scale * 0.35;
let mut rhombi: Vec<(f32, f32, f32, bool, usize)> = Vec::new();
let n_lines = 5i32;
for fj in 0..5usize {
for fk in (fj + 1)..5usize {
let aj = fj as f32 * 72.0 * PI / 180.0 + rot;
let ak = fk as f32 * 72.0 * PI / 180.0 + rot;
for mj in -n_lines..=n_lines {
for mk in -n_lines..=n_lines {
let bj_x = mj as f32 * spacing * aj.cos();
let bj_y = mj as f32 * spacing * (-aj.sin());
let bk_x = mk as f32 * spacing * ak.cos();
let bk_y = mk as f32 * spacing * (-ak.sin());
let dj_x = -aj.sin();
let dj_y = -aj.cos();
let dk_x = -ak.sin();
let dk_y = -ak.cos();
let det = dj_x * dk_y - dj_y * dk_x;
if det.abs() < 1e-6 {
continue;
}
let dx = bk_x - bj_x;
let dy = bk_y - bj_y;
let t = (dx * dk_y - dy * dk_x) / det;
let rx = cx + bj_x + t * dj_x;
let ry = cy + bj_y + t * dj_y;
if rx < 0.0 || ry < 0.0 || rx >= dw as f32 || ry >= dh as f32 {
continue;
}
let diff = (fk - fj) % 5;
let is_fat = diff == 1 || diff == 4;
let idx = (mj.unsigned_abs() as usize * 7
+ mk.unsigned_abs() as usize * 13
+ fj * 3
+ fk * 5)
& 1; rhombi.push((rx, ry, aj, is_fat, idx));
}
}
}
}
let n_total = rhombi.len();
let n_draw = (ctx.eased * n_total as f32).round() as usize;
for &(rx, ry, angle, _is_fat, flip) in rhombi.iter().take(n_draw) {
let r = spacing * 0.4;
let arc_steps = 8usize;
let corner_a = angle + if flip == 0 { 0.0 } else { PI };
let corner_b = corner_a + PI * 0.5;
let arc_cx = rx + r * corner_a.cos();
let arc_cy = ry - r * corner_a.sin();
for s in 0..=arc_steps {
let a = corner_b + (corner_a - corner_b) * s as f32 / arc_steps as f32;
let px = (arc_cx + r * a.cos()).round() as i32;
let py = (arc_cy - r * a.sin()).round() as i32;
draw::dot_i(grid, px, py);
}
let corner_c = corner_a + PI;
let corner_d = corner_c + PI * 0.5;
let arc_cx2 = rx + r * corner_c.cos();
let arc_cy2 = ry - r * corner_c.sin();
for s in 0..=arc_steps {
let a = corner_d + (corner_c - corner_d) * s as f32 / arc_steps as f32;
let px = (arc_cx2 + r * a.cos()).round() as i32;
let py = (arc_cy2 - r * a.sin()).round() as i32;
draw::dot_i(grid, px, py);
}
}
Ok(())
}
}