use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
#[inline]
fn hash(n: u32) -> u32 {
let mut x = n.wrapping_mul(2_654_435_761);
x ^= x >> 15;
x.wrapping_mul(2_246_822_519)
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(MandelbrotEscape),
Box::new(JuliaSet),
Box::new(SierpinskiTriangle),
Box::new(KochCurve),
Box::new(BarnsleyFern),
Box::new(DragonCurve),
Box::new(SierpinskiCarpet),
Box::new(BurningShip),
Box::new(PythagorasTree),
Box::new(NewtonFractal),
Box::new(CantorDust),
Box::new(LyapunovBar),
]
}
struct MandelbrotEscape;
impl ProgressStyle for MandelbrotEscape {
fn name(&self) -> &str {
"mandelbrot-escape"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Mandelbrot set rendered as braille: escape-time threshold rises with progress, \
viewport pans and zooms 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 max_iter = (2.0 + ctx.eased * 46.0) as u32;
let zoom = 2.5 / (1.0 + ctx.time * 0.05);
let cx_center = -0.7269 - ctx.time * 0.002;
let cy_center = 0.1889;
let aspect = dw as f32 / dh as f32;
for py in 0..dh {
for px in 0..dw {
let cr = cx_center + (px as f32 / dw as f32 - 0.5) * zoom * aspect;
let ci = cy_center + (py as f32 / dh as f32 - 0.5) * zoom;
let mut zr = 0.0f32;
let mut zi = 0.0f32;
let mut escaped = false;
for _ in 0..max_iter {
let zr2 = zr * zr;
let zi2 = zi * zi;
if zr2 + zi2 > 4.0 {
escaped = true;
break;
}
let new_zr = zr2 - zi2 + cr;
zi = 2.0 * zr * zi + ci;
zr = new_zr;
}
if escaped {
draw::dot_i(grid, px as i32, py as i32);
}
}
}
let (cw, ch) = grid.dimensions();
let filled_cells = (ctx.eased * cw as f32) as usize;
for cy in 0..ch {
for cx in 0..filled_cells.min(cw) {
let t = if cw <= 1 {
0.5
} else {
cx as f32 / (cw - 1) as f32
};
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct JuliaSet;
impl ProgressStyle for JuliaSet {
fn name(&self) -> &str {
"julia-set"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Julia set with c=0.7885·e^(i·θ), θ animated by time; resolution and iteration \
depth grow 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 theta = ctx.time * 0.4;
let cr = 0.7885 * theta.cos();
let ci = 0.7885 * theta.sin();
let max_iter = (4.0 + ctx.eased * 44.0) as u32;
let scale = 1.5;
for py in 0..dh {
for px in 0..dw {
let mut zr = (px as f32 / dw as f32 - 0.5) * scale * 2.0;
let mut zi = (py as f32 / dh as f32 - 0.5) * scale * 2.0;
let mut iters = 0u32;
while iters < max_iter && zr * zr + zi * zi <= 4.0 {
let new_zr = zr * zr - zi * zi + cr;
zi = 2.0 * zr * zi + ci;
zr = new_zr;
iters += 1;
}
if iters < max_iter {
draw::dot_i(grid, px as i32, py as i32);
}
}
}
let (cw, ch) = grid.dimensions();
for cy in 0..ch {
for cx in 0..cw {
let t = (cx as f32 / cw as f32 + ctx.time * 0.1).fract();
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct SierpinskiTriangle;
impl ProgressStyle for SierpinskiTriangle {
fn name(&self) -> &str {
"sierpinski-triangle"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Sierpinski triangle via the chaos game (random midpoint IFS); point count \
grows with progress, triangle rotates 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 n_points = (10.0 + ctx.eased * 2990.0) as u32;
let rot = ctx.time * 0.3;
let vertices: [(f32, f32); 3] = [
(0.5 + 0.45 * (rot).cos(), 0.5 + 0.45 * (rot).sin()),
(
0.5 + 0.45 * (rot + 2.094).cos(),
0.5 + 0.45 * (rot + 2.094).sin(),
),
(
0.5 + 0.45 * (rot + 4.189).cos(),
0.5 + 0.45 * (rot + 4.189).sin(),
),
];
let mut px = 0.5f32;
let mut py = 0.5f32;
for i in 0..n_points + 20 {
let v = (hash(i.wrapping_mul(1_000_003)) % 3) as usize;
px = (px + vertices[v].0) * 0.5;
py = (py + vertices[v].1) * 0.5;
if i >= 20 {
let dx = (px * dw as f32) as i32;
let dy = (py * dh as f32) as i32;
draw::dot_i(grid, dx, dy);
}
}
let (cw, ch) = grid.dimensions();
for cy in 0..ch {
let t = cy as f32 / ch.max(1) as f32;
draw::tint_row(grid, cy, 0, cw.saturating_sub(1), ctx.palette.sample(t));
}
Ok(())
}
}
struct KochCurve;
impl ProgressStyle for KochCurve {
fn name(&self) -> &str {
"koch-curve"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Koch snowflake drawn as a dot polyline; L-system recursion depth grows \
with progress (0..=4), rotated and scaled by 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 depth = (ctx.eased * 4.0).floor() as u32;
fn koch_points(ax: f32, ay: f32, bx: f32, by: f32, depth: u32, pts: &mut Vec<(f32, f32)>) {
if depth == 0 {
pts.push((bx, by));
return;
}
let dx = bx - ax;
let dy = by - ay;
let p1x = ax + dx / 3.0;
let p1y = ay + dy / 3.0;
let p3x = ax + 2.0 * dx / 3.0;
let p3y = ay + 2.0 * dy / 3.0;
let p2x = p1x + (dx / 3.0) * 0.5 - (dy / 3.0) * 0.866;
let p2y = p1y + (dy / 3.0) * 0.5 + (dx / 3.0) * 0.866;
koch_points(ax, ay, p1x, p1y, depth - 1, pts);
koch_points(p1x, p1y, p2x, p2y, depth - 1, pts);
koch_points(p2x, p2y, p3x, p3y, depth - 1, pts);
koch_points(p3x, p3y, bx, by, depth - 1, pts);
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (dw.min(dh * 2) as f32 * 0.42).max(1.0);
let rot = ctx.time * 0.2;
let tri: Vec<(f32, f32)> = (0..3)
.map(|i| {
let a = rot + i as f32 * 2.0 * PI / 3.0 - PI / 2.0;
(cx + r * a.cos(), cy + r * a.sin())
})
.collect();
for side in 0..3 {
let (ax, ay) = tri[side];
let (bx, by) = tri[(side + 1) % 3];
let mut pts = vec![(ax, ay)];
koch_points(ax, ay, bx, by, depth, &mut pts);
for w in pts.windows(2) {
let (x0, y0) = w[0];
let (x1, y1) = w[1];
let steps = ((x1 - x0).abs() + (y1 - y0).abs()).ceil() as u32 + 1;
let steps = steps.max(1);
for s in 0..=steps {
let t = s as f32 / steps as f32;
let px = x0 + t * (x1 - x0);
let py = y0 + t * (y1 - y0);
draw::dot_i(grid, px as i32, py as i32);
}
}
}
let (cw, ch) = grid.dimensions();
for cy_cell in 0..ch {
let t = cy_cell as f32 / ch.max(1) as f32;
draw::tint_row(
grid,
cy_cell,
0,
cw.saturating_sub(1),
ctx.palette.sample(t),
);
}
Ok(())
}
}
struct BarnsleyFern;
impl ProgressStyle for BarnsleyFern {
fn name(&self) -> &str {
"barnsley-fern"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Barnsley fern IFS (4 affine maps, exact original coefficients); point count \
and detail scale with progress; sways 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 n_pts = (20.0 + ctx.eased * 2980.0) as u32;
let maps: [[f32; 6]; 4] = [
[0.0, 0.0, 0.0, 0.16, 0.0, 0.0], [0.85, 0.04, -0.04, 0.85, 0.0, 1.6], [0.20, -0.26, 0.23, 0.22, 0.0, 1.6], [-0.15, 0.28, 0.26, 0.24, 0.0, 0.44], ];
let thresholds = [10u32, 860, 930, 1000];
let sway = (ctx.time * 0.5).sin() * 0.04;
let mut x = 0.0f32;
let mut y = 0.0f32;
for i in 0..n_pts + 20 {
let r = hash(i.wrapping_mul(999_983)) % 1000;
let map_idx = if r < thresholds[0] {
0
} else if r < thresholds[1] {
1
} else if r < thresholds[2] {
2
} else {
3
};
let m = maps[map_idx];
let new_x = m[0] * x + m[1] * y + m[4] + if map_idx >= 2 { sway } else { 0.0 };
let new_y = m[2] * x + m[3] * y + m[5];
x = new_x;
y = new_y;
if i >= 20 {
let px = ((x + 2.182) / 4.8378 * dw as f32) as i32;
let py = ((1.0 - y / 9.9983) * dh as f32) as i32;
draw::dot_i(grid, px, py);
}
}
let (cw, ch) = grid.dimensions();
for cy in 0..ch {
let t = cy as f32 / ch.max(1) as f32;
draw::tint_row(
grid,
cy,
0,
cw.saturating_sub(1),
ctx.palette.sample(1.0 - t),
);
}
Ok(())
}
}
struct DragonCurve;
impl ProgressStyle for DragonCurve {
fn name(&self) -> &str {
"dragon-curve"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Dragon curve drawn by paper-folding construction; reveal length grows with \
progress (up to 12 folds), rotates 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 max_folds: u32 = 12;
let folds = (ctx.eased * max_folds as f32).floor() as u32;
let folds = folds.min(max_folds);
let n_segs = 1u32 << folds;
fn dragon_turn(k: u32) -> bool {
let tz = k.trailing_zeros();
((k >> tz) >> 1) & 1 == 0
}
let rot_offset = (ctx.time * 0.25) as i32; let start_dir: i32 = rot_offset.rem_euclid(4);
let step = 1i32;
let dx = [step, 0, -step, 0];
let dy = [0, -step, 0, step];
let mut cx_i: i32 = 0;
let mut cy_i: i32 = 0;
let mut dir: i32 = start_dir;
let mut pts: Vec<(i32, i32)> = Vec::with_capacity((n_segs + 1) as usize);
pts.push((cx_i, cy_i));
for k in 1..=n_segs {
cx_i += dx[dir as usize];
cy_i += dy[dir as usize];
pts.push((cx_i, cy_i));
if k < n_segs {
let turn = dragon_turn(k);
dir = if turn {
(dir + 1).rem_euclid(4)
} else {
(dir + 3).rem_euclid(4)
};
}
}
let min_x = pts.iter().map(|p| p.0).min().unwrap_or(0);
let max_x = pts.iter().map(|p| p.0).max().unwrap_or(0);
let min_y = pts.iter().map(|p| p.1).min().unwrap_or(0);
let max_y = pts.iter().map(|p| p.1).max().unwrap_or(0);
let span_x = (max_x - min_x).max(1);
let span_y = (max_y - min_y).max(1);
for p in &pts {
let px = (p.0 - min_x) as f32 / span_x as f32 * (dw as f32 - 1.0);
let py = (p.1 - min_y) as f32 / span_y as f32 * (dh as f32 - 1.0);
draw::dot_i(grid, px as i32, py as i32);
}
let (cw, ch) = grid.dimensions();
for cy in 0..ch {
let t = cy as f32 / ch.max(1) as f32;
draw::tint_row(grid, cy, 0, cw.saturating_sub(1), ctx.palette.sample(t));
}
Ok(())
}
}
struct SierpinskiCarpet;
impl ProgressStyle for SierpinskiCarpet {
fn name(&self) -> &str {
"sierpinski-carpet"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Sierpinski carpet: each dot is tested via the 9-ary digit rule (x&y in base 3 \
for any digit == 1 → hole); depth driven by 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 max_depth = (ctx.eased * 5.0).ceil() as u32;
let max_depth = max_depth.max(1);
let pow3 = 3u32.pow(max_depth);
let off_x = (ctx.time * 0.4).sin() * 0.08;
let off_y = (ctx.time * 0.3).cos() * 0.08;
for py in 0..dh {
for px in 0..dw {
let fx = ((px as f32 / dw as f32 + off_x).rem_euclid(1.0) * pow3 as f32) as u32;
let fy = ((py as f32 / dh as f32 + off_y).rem_euclid(1.0) * pow3 as f32) as u32;
let mut in_hole = false;
let mut rx = fx;
let mut ry = fy;
for _ in 0..max_depth {
if rx % 3 == 1 && ry % 3 == 1 {
in_hole = true;
break;
}
rx /= 3;
ry /= 3;
}
if !in_hole {
draw::dot_i(grid, px as i32, py as i32);
}
}
}
let (cw, ch) = grid.dimensions();
let filled_cells = (ctx.eased * cw as f32) as usize;
for cy in 0..ch {
for cx in 0..filled_cells.min(cw) {
let t = cx as f32 / cw.max(1) as f32;
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct BurningShip;
impl ProgressStyle for BurningShip {
fn name(&self) -> &str {
"burning-ship"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Burning Ship fractal (z → (|Re z|+i|Im z|)² + c); its jagged, ship-like \
silhouette burns across the bar 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 max_iter = (3.0 + ctx.eased * 45.0) as u32;
let drift = ctx.time * 0.015;
let re_min = -2.5 + drift.sin() * 0.2;
let re_max = 1.5 + drift.cos() * 0.1;
let im_min = -2.0 + (drift * 0.7).sin() * 0.15;
let im_max = 0.5 + (drift * 0.5).cos() * 0.1;
for py in 0..dh {
for px in 0..dw {
let cr = re_min + (px as f32 / dw as f32) * (re_max - re_min);
let ci = im_min + (py as f32 / dh as f32) * (im_max - im_min);
let mut zr = 0.0f32;
let mut zi = 0.0f32;
let mut escaped = false;
for _ in 0..max_iter {
let zr2 = zr * zr;
let zi2 = zi * zi;
if zr2 + zi2 > 4.0 {
escaped = true;
break;
}
let new_zr = zr2 - zi2 + cr;
zi = 2.0 * zr.abs() * zi.abs() + ci;
zr = new_zr;
}
if escaped {
draw::dot_i(grid, px as i32, py as i32);
}
}
}
let (cw, ch) = grid.dimensions();
for cy in 0..ch {
let t = cy as f32 / ch.max(1) as f32;
draw::tint_row(grid, cy, 0, cw.saturating_sub(1), ctx.palette.sample(t));
}
Ok(())
}
}
struct PythagorasTree;
impl ProgressStyle for PythagorasTree {
fn name(&self) -> &str {
"pythagoras-tree"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Pythagoras tree: a square sprouts two smaller squares at a time-animated \
split angle; branch count grows with progress (depth 0..=7)."
}
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 max_depth = (ctx.eased * 7.0).floor() as u32;
let alpha = 0.349 + (ctx.time * 0.2).sin().abs() * 0.524;
fn draw_square(grid: &mut BrailleGrid, x1: f32, y1: f32, x2: f32, y2: f32) {
let ex = x2 - x1;
let ey = y2 - y1;
let x3 = x2 - ey;
let y3 = y2 + ex;
let x4 = x1 - ey;
let y4 = y1 + ex;
let corners = [(x1, y1), (x2, y2), (x3, y3), (x4, y4), (x1, y1)];
for w in corners.windows(2) {
let (ax, ay) = w[0];
let (bx, by) = w[1];
let steps = ((bx - ax).abs() + (by - ay).abs()).ceil() as i32 + 1;
let steps = steps.max(1);
for s in 0..=steps {
let t = s as f32 / steps as f32;
let px = ax + t * (bx - ax);
let py = ay + t * (by - ay);
draw::dot_i(grid, px as i32, py as i32);
}
}
}
fn tree(
grid: &mut BrailleGrid,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
alpha: f32,
depth: u32,
) {
if depth == 0 {
return;
}
draw_square(grid, x1, y1, x2, y2);
let ex = x2 - x1;
let ey = y2 - y1;
let tx1 = x1 - ey;
let ty1 = y1 + ex;
let tx2 = x2 - ey;
let ty2 = y2 + ex;
let ca = alpha.cos();
let sa = alpha.sin();
let ex2 = tx2 - tx1;
let ey2 = ty2 - ty1;
let len = (ex2 * ex2 + ey2 * ey2).sqrt().max(1e-6);
let ux = ex2 / len;
let uy = ey2 / len;
let left_size = len * ca;
let apex_x = tx1 + ux * left_size * ca - uy * left_size * sa;
let apex_y = ty1 + uy * left_size * ca + ux * left_size * sa;
tree(grid, tx1, ty1, apex_x, apex_y, alpha, depth - 1);
tree(grid, apex_x, apex_y, tx2, ty2, alpha, depth - 1);
}
let sq_w = (dw as f32 * 0.28).max(2.0);
let base_y = dh as f32 - 1.0;
let base_x = dw as f32 / 2.0 - sq_w / 2.0;
tree(
grid,
base_x,
base_y,
base_x + sq_w,
base_y,
alpha,
max_depth + 1,
);
let (cw, ch) = grid.dimensions();
for cy in 0..ch {
let t = 1.0 - cy as f32 / ch.max(1) as f32; draw::tint_row(grid, cy, 0, cw.saturating_sub(1), ctx.palette.sample(t));
}
Ok(())
}
}
struct NewtonFractal;
impl ProgressStyle for NewtonFractal {
fn name(&self) -> &str {
"newton-fractal"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Newton fractal for z³−1=0: three root basins painted with palette colors; \
iteration count threshold rises with progress, domain rotates 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 max_iter = (2.0 + ctx.eased * 30.0) as u32;
let tol2 = 1e-4f32;
let roots = [(1.0f32, 0.0f32), (-0.5, 0.866_025_4), (-0.5, -0.866_025_4)];
let scale = 2.0f32 / (1.0 + ctx.time * 0.04);
let theta = ctx.time * 0.15;
let cos_t = theta.cos();
let sin_t = theta.sin();
for py in 0..dh {
for px in 0..dw {
let raw_r = (px as f32 / dw as f32 - 0.5) * scale * 2.0;
let raw_i = (py as f32 / dh as f32 - 0.5) * scale * 2.0;
let mut zr = raw_r * cos_t - raw_i * sin_t;
let mut zi = raw_r * sin_t + raw_i * cos_t;
let mut root_id = 0usize;
let mut converged = false;
for _ in 0..max_iter {
let zr2 = zr * zr;
let zi2 = zi * zi;
let z3r = (zr2 - zi2) * zr - 2.0 * zr * zi * zi;
let z3i = (zr2 - zi2) * zi + 2.0 * zr * zi * zr;
let d3r = 3.0 * (zr2 - zi2);
let d3i = 6.0 * zr * zi;
let d_mag2 = d3r * d3r + d3i * d3i;
if d_mag2 < 1e-10 {
break;
}
let nr = z3r - 1.0;
let ni = z3i;
let qr = (nr * d3r + ni * d3i) / d_mag2;
let qi = (ni * d3r - nr * d3i) / d_mag2;
zr -= qr;
zi -= qi;
for (rid, &(rr, ri)) in roots.iter().enumerate() {
let dr = zr - rr;
let di = zi - ri;
if dr * dr + di * di < tol2 {
root_id = rid;
converged = true;
break;
}
}
if converged {
break;
}
}
if converged {
let t = root_id as f32 / 2.0;
let (cw, ch) = grid.dimensions();
let cell_x = (px / 2).min(cw.saturating_sub(1));
let cell_y = (py / 4).min(ch.saturating_sub(1));
draw::dot_i(grid, px as i32, py as i32);
draw::tint_row(grid, cell_y, cell_x, cell_x, ctx.palette.sample(t));
}
}
}
Ok(())
}
}
struct CantorDust;
impl ProgressStyle for CantorDust {
fn name(&self) -> &str {
"cantor-dust"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Cantor dust: 2-D middle-third removal across rows; each row is a deeper \
level of the Cantor set, revealing from top to bottom 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 revealed_rows = ((ctx.eased * dh as f32).ceil() as usize).min(dh);
for py in 0..revealed_rows {
let level = (py as f32 / dh as f32 * 7.0).floor() as u32;
let pow3 = 3u32.pow(level.min(7));
let drift = (ctx.time * 0.3 + py as f32 * 0.05).sin() * 0.1;
for px in 0..dw {
let fx = ((px as f32 / dw as f32 + drift).rem_euclid(1.0) * pow3 as f32) as u32;
let mut in_cantor = true;
let mut rx = fx;
for _ in 0..level {
if rx % 3 == 1 {
in_cantor = false;
break;
}
rx /= 3;
}
if in_cantor {
draw::dot_i(grid, px as i32, py as i32);
}
}
}
let (cw, ch) = grid.dimensions();
for cy in 0..ch {
let t = cy as f32 / ch.max(1) as f32;
draw::tint_row(grid, cy, 0, cw.saturating_sub(1), ctx.palette.sample(t));
}
Ok(())
}
}
struct LyapunovBar;
impl ProgressStyle for LyapunovBar {
fn name(&self) -> &str {
"lyapunov-bar"
}
fn theme(&self) -> &str {
"fractal"
}
fn describe(&self) -> &str {
"Lyapunov exponent landscape for the logistic map sequence AABB…; each column \
is a parameter r swept across [2.5, 4.0], lit where the exponent is negative \
(stable). Progress raises the iteration count; time scrolls the AB sequence phase."
}
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 n_iter = (8.0 + ctx.eased * 92.0) as u32;
let warmup = 20u32;
let phase = (ctx.time * 0.5) as u32;
for px in 0..dw {
let t_col = px as f32 / dw as f32;
let ra = 2.5 + t_col * 1.5;
let rb = 2.5 + (1.0 - t_col) * 1.5;
let mut x = 0.5f32;
let mut lam = 0.0f32;
let seq_len = 4u32;
for i in 0..warmup {
let r = if (i + phase) % seq_len < 2 { ra } else { rb };
x = r * x * (1.0 - x);
}
for i in 0..n_iter {
let r = if (i + phase) % seq_len < 2 { ra } else { rb };
x = r * x * (1.0 - x);
let deriv = (r * (1.0 - 2.0 * x)).abs().max(1e-10);
lam += deriv.ln();
}
lam /= n_iter as f32;
let col_fill_frac = if lam < 0.0 {
1.0f32 } else {
(1.0 - (lam / 2.0).min(1.0)).max(0.0) };
let col_h = (col_fill_frac * dh as f32).round() as usize;
let y0 = dh.saturating_sub(col_h);
for py in y0..dh {
draw::dot_i(grid, px as i32, py as i32);
}
}
let (cw, ch) = grid.dimensions();
for cy in 0..ch {
for cx in 0..cw {
let t = cx as f32 / cw.max(1) as f32;
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}