use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
#[inline(always)]
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(LorenzAttractor),
Box::new(RosslerAttractor),
Box::new(CliffordAttractor),
Box::new(DeJongAttractor),
Box::new(HenonMap),
Box::new(LogisticBifurcation),
Box::new(DoublePendulum),
Box::new(StandardMap),
Box::new(Gingerbreadman),
Box::new(DuffingOscillator),
Box::new(TinkerbellMap),
]
}
struct LorenzAttractor;
impl ProgressStyle for LorenzAttractor {
fn name(&self) -> &str {
"lorenz"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Lorenz butterfly attractor: σ=10 ρ=28 β=8/3 — orbit revealed by progress, time rotates projection"
}
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 sigma: f32 = 10.0;
let rho: f32 = 28.0;
let beta: f32 = 8.0 / 3.0;
let dt: f32 = 0.01;
let n_total: usize = 2800;
let n_draw = (ctx.eased * n_total as f32).max(1.0) as usize;
let x_range = (-20.0_f32, 20.0_f32);
let z_range = (0.0_f32, 50.0_f32);
let angle = ctx.time * 0.3;
let cos_a = angle.cos();
let sin_a = angle.sin();
let mut x: f32 = 0.1;
let mut y: f32 = 0.0;
let mut z: f32 = 0.0;
for _ in 0..300 {
let dx = sigma * (y - x);
let dy = x * (rho - z) - y;
let dz = x * y - beta * z;
x += dx * dt;
y += dy * dt;
z += dz * dt;
}
for _ in 0..n_draw.min(n_total) {
let dx = sigma * (y - x);
let dy = x * (rho - z) - y;
let dz = x * y - beta * z;
x += dx * dt;
y += dy * dt;
z += dz * dt;
let proj_x = x * cos_a - y * sin_a;
let norm_x = (proj_x - x_range.0) / (x_range.1 - x_range.0);
let norm_z = (z - z_range.0) / (z_range.1 - z_range.0);
let px = (norm_x.clamp(0.0, 1.0) * (dw - 1) as f32) as i32;
let py = ((1.0 - norm_z.clamp(0.0, 1.0)) * (dh - 1) as f32) as i32;
draw::dot_i(grid, px, py);
}
let filled = (ctx.eased * dw as f32) as usize;
draw::hline(
grid,
0,
filled.saturating_sub(1).min(dw.saturating_sub(1)),
dh - 1,
);
Ok(())
}
}
struct RosslerAttractor;
impl ProgressStyle for RosslerAttractor {
fn name(&self) -> &str {
"rossler"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Rössler spiral band: a=0.2 b=0.2 c=5.7 — single spiralling lobe revealed 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 a: f32 = 0.2;
let b: f32 = 0.2;
let c: f32 = 5.7;
let dt: f32 = 0.025;
let n_total = 2400usize;
let n_draw = (ctx.eased * n_total as f32).max(1.0) as usize;
let xy_min = -13.0_f32;
let xy_max = 13.0_f32;
let angle = ctx.time * 0.2;
let cos_a = angle.cos();
let sin_a = angle.sin();
let mut x: f32 = 1.0;
let mut y: f32 = 0.0;
let mut z: f32 = 0.0;
for _ in 0..200 {
let dx = -y - z;
let dy = x + a * y;
let dz = b + z * (x - c);
x += dx * dt;
y += dy * dt;
z += dz * dt;
}
for _ in 0..n_draw.min(n_total) {
let dx = -y - z;
let dy = x + a * y;
let dz = b + z * (x - c);
x += dx * dt;
y += dy * dt;
z += dz * dt;
let rx = x * cos_a - y * sin_a;
let ry = x * sin_a + y * cos_a;
let norm_x = (rx - xy_min) / (xy_max - xy_min);
let norm_y = (ry - xy_min) / (xy_max - xy_min);
let px = (norm_x.clamp(0.0, 1.0) * (dw - 1) as f32) as i32;
let py = ((1.0 - norm_y.clamp(0.0, 1.0)) * (dh - 1) as f32) as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct CliffordAttractor;
impl ProgressStyle for CliffordAttractor {
fn name(&self) -> &str {
"clifford"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Clifford IFS: sin/cos parameter orbit — params a,b slowly drift 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 a = -1.7 + 0.4 * (ctx.time * 0.17).sin();
let b = 1.8 + 0.3 * (ctx.time * 0.13).cos();
let c = -1.9_f32;
let d = -0.4_f32;
let n_total = 3000usize;
let n_draw = (ctx.eased * n_total as f32).max(1.0) as usize;
let xy_min = -2.5_f32;
let xy_max = 2.5_f32;
let range = xy_max - xy_min;
let mut x: f32 = 0.1;
let mut y: f32 = 0.0;
for _ in 0..n_draw.min(n_total) {
let xn = (a * y).sin() + c * (a * x).cos();
let yn = (b * x).sin() + d * (b * y).cos();
x = xn;
y = yn;
let norm_x = (x - xy_min) / range;
let norm_y = (y - xy_min) / range;
let px = (norm_x.clamp(0.0, 1.0) * (dw - 1) as f32) as i32;
let py = ((1.0 - norm_y.clamp(0.0, 1.0)) * (dh - 1) as f32) as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct DeJongAttractor;
impl ProgressStyle for DeJongAttractor {
fn name(&self) -> &str {
"de-jong"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"De Jong IFS: sin−cos iterated map — all four params drift with time for morphing forms"
}
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 t = ctx.time;
let a = -2.0 + 0.5 * (t * 0.11).sin();
let b = -2.0 + 0.4 * (t * 0.07).cos();
let c = 1.2 + 0.5 * (t * 0.09).sin();
let d = 2.0 + 0.3 * (t * 0.13).cos();
let n_total = 3000usize;
let n_draw = (ctx.eased * n_total as f32).max(1.0) as usize;
let xy_min = -2.5_f32;
let xy_max = 2.5_f32;
let range = xy_max - xy_min;
let mut x: f32 = 0.1;
let mut y: f32 = 0.1;
for _ in 0..n_draw.min(n_total) {
let xn = (a * y).sin() - (b * x).cos();
let yn = (c * x).sin() - (d * y).cos();
x = xn;
y = yn;
let norm_x = (x - xy_min) / range;
let norm_y = (y - xy_min) / range;
let px = (norm_x.clamp(0.0, 1.0) * (dw - 1) as f32) as i32;
let py = ((1.0 - norm_y.clamp(0.0, 1.0)) * (dh - 1) as f32) as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct HenonMap;
impl ProgressStyle for HenonMap {
fn name(&self) -> &str {
"henon"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Hénon map: a=1.4 b=0.3 — banana strange attractor self-similar at every scale"
}
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 a: f32 = 1.4;
let b: f32 = 0.3;
let n_total = 2800usize;
let n_draw = (ctx.eased * n_total as f32).max(1.0) as usize;
let x_min = -1.5_f32;
let x_max = 1.5_f32;
let y_min = -0.5_f32;
let y_max = 0.5_f32;
let mut x: f32 = 0.1;
let mut y: f32 = 0.1;
for _ in 0..50 {
let xn = 1.0 - a * x * x + y;
let yn = b * x;
x = xn;
y = yn;
}
for _ in 0..n_draw.min(n_total) {
let xn = 1.0 - a * x * x + y;
let yn = b * x;
x = xn;
y = yn;
if x.abs() > 10.0 || y.abs() > 10.0 {
break;
}
let norm_x = (x - x_min) / (x_max - x_min);
let norm_y = (y - y_min) / (y_max - y_min);
let px = (norm_x.clamp(0.0, 1.0) * (dw - 1) as f32) as i32;
let py = ((1.0 - norm_y.clamp(0.0, 1.0)) * (dh - 1) as f32) as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct LogisticBifurcation;
impl ProgressStyle for LogisticBifurcation {
fn name(&self) -> &str {
"logistic-bifurcation"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Logistic map bifurcation diagram r∈[2.8,4.0] — period-doubling route to chaos column by column"
}
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 r_min: f32 = 2.8;
let r_max: f32 = 4.0;
let reveal_cols = (ctx.eased * dw as f32).round() as usize;
let x0 = 0.5 + 0.01 * (ctx.time * 0.05).sin();
for col in 0..reveal_cols.min(dw) {
let r = r_min + (col as f32 / dw.saturating_sub(1).max(1) as f32) * (r_max - r_min);
let mut x = x0.clamp(0.01, 0.99);
for _ in 0..300 {
x = r * x * (1.0 - x);
}
for _ in 0..80 {
x = r * x * (1.0 - x);
let row = ((1.0 - x) * (dh - 1) as f32).round() as usize;
if row < dh {
draw::dot(grid, col, row);
}
}
}
Ok(())
}
}
struct DoublePendulum;
impl ProgressStyle for DoublePendulum {
fn name(&self) -> &str {
"double-pendulum"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Double pendulum chaotic trace — RK4 Lagrangian integration, tip path revealed 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 g: f32 = 9.81;
let dt: f32 = 0.02;
let n_total: usize = 2000;
let n_draw = (ctx.eased * n_total as f32).max(1.0) as usize;
let mut th1: f32 = PI / 2.0 + 0.05 * (ctx.time * 0.001).sin();
let mut om1: f32 = 0.0;
let mut th2: f32 = PI + 0.03;
let mut om2: f32 = 0.0;
let scale = 2.0_f32;
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let deriv = |th1: f32, om1: f32, th2: f32, om2: f32| -> (f32, f32, f32, f32) {
let delta = th2 - th1;
let denom = 2.0 - (2.0 * delta).cos(); let denom = if denom.abs() < 1e-6 {
1e-6_f32.copysign(denom)
} else {
denom
};
let dom1 = (-g * 2.0 * th1.sin()
- g * delta.cos() * 2.0 * th2.sin() - 2.0 * delta.sin() * (om2 * om2 + om1 * om1 * delta.cos()))
/ denom;
let dom2 = (2.0
* delta.sin()
* (om1 * om1 * 2.0 + g * 2.0 * th1.cos() + om2 * om2 * delta.cos()))
/ denom;
(om1, dom1, om2, dom2)
};
for i in 0..n_draw.min(n_total) {
let (k1a, k1b, k1c, k1d) = deriv(th1, om1, th2, om2);
let (k2a, k2b, k2c, k2d) = deriv(
th1 + k1a * dt / 2.0,
om1 + k1b * dt / 2.0,
th2 + k1c * dt / 2.0,
om2 + k1d * dt / 2.0,
);
let (k3a, k3b, k3c, k3d) = deriv(
th1 + k2a * dt / 2.0,
om1 + k2b * dt / 2.0,
th2 + k2c * dt / 2.0,
om2 + k2d * dt / 2.0,
);
let (k4a, k4b, k4c, k4d) = deriv(
th1 + k3a * dt,
om1 + k3b * dt,
th2 + k3c * dt,
om2 + k3d * dt,
);
th1 += dt / 6.0 * (k1a + 2.0 * k2a + 2.0 * k3a + k4a);
om1 += dt / 6.0 * (k1b + 2.0 * k2b + 2.0 * k3b + k4b);
th2 += dt / 6.0 * (k1c + 2.0 * k2c + 2.0 * k3c + k4c);
om2 += dt / 6.0 * (k1d + 2.0 * k2d + 2.0 * k3d + k4d);
let tip_x = th1.sin() + th2.sin();
let tip_y = -(th1.cos() + th2.cos());
let _ = i; let px = (cx + tip_x / scale * cx) as i32;
let py = (cy + tip_y / scale * cy) as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct StandardMap;
impl ProgressStyle for StandardMap {
fn name(&self) -> &str {
"standard-map"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Chirikov–Taylor standard map — K grows with progress, KAM tori shatter into chaos"
}
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 k = ctx.eased * 4.0 * PI;
let n_seeds = 8usize;
let n_iter = 200usize;
for si in 0..n_seeds {
for sj in 0..n_seeds {
let mut theta = 2.0 * PI * si as f32 / n_seeds as f32;
let mut p = 2.0 * PI * sj as f32 / n_seeds as f32;
theta += ctx.time * 0.04;
for _ in 0..n_iter {
p = (p + k * theta.sin()).rem_euclid(2.0 * PI);
theta = (theta + p).rem_euclid(2.0 * PI);
let norm_x = theta / (2.0 * PI);
let norm_y = p / (2.0 * PI);
let px = (norm_x * (dw - 1) as f32) as i32;
let py = ((1.0 - norm_y) * (dh - 1) as f32) as i32;
draw::dot_i(grid, px, py);
}
}
}
Ok(())
}
}
struct Gingerbreadman;
impl ProgressStyle for Gingerbreadman {
fn name(&self) -> &str {
"gingerbreadman"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Gingerbreadman map: x←1−y+|x|, y←x — fractal triangular symmetry, orbits seeded 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 n_seeds_max = 12usize;
let n_seeds = (ctx.eased * n_seeds_max as f32).max(1.0) as usize;
let n_iter = 200usize;
let extent = 120.0_f32;
let angle = ctx.time * 0.1;
let cos_a = angle.cos();
let sin_a = angle.sin();
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
for si in 0..n_seeds.min(n_seeds_max) {
let h1 = hash(si as u32 * 17 + 3);
let h2 = hash(si as u32 * 31 + 7);
let mut x = (h1 % 40) as f32 - 20.0;
let mut y = (h2 % 40) as f32 - 20.0;
for _ in 0..n_iter {
let xn = 1.0 - y + x.abs();
let yn = x;
x = xn;
y = yn;
let rx = x * cos_a - y * sin_a;
let ry = x * sin_a + y * cos_a;
let px = (cx + rx / extent * cx) as i32;
let py = (cy + ry / extent * cy) as i32;
draw::dot_i(grid, px, py);
}
}
Ok(())
}
}
struct DuffingOscillator;
impl ProgressStyle for DuffingOscillator {
fn name(&self) -> &str {
"duffing"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Duffing oscillator phase portrait: ẏ=−δy+αx−βx³+γcos(ωt) — chaotic fractal basin boundary"
}
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 delta: f32 = 0.2;
let alpha: f32 = 1.0;
let beta: f32 = 1.0;
let gamma: f32 = 0.3;
let omega: f32 = 1.2;
let dt: f32 = 0.02;
let n_total: usize = 2500;
let n_draw = (ctx.eased * n_total as f32).max(1.0) as usize;
let x_range = 2.0_f32;
let y_range = 2.0_f32;
let mut x: f32 = 1.0;
let mut y: f32 = 0.0;
let mut t_sim: f32 = ctx.time * 0.5;
for _ in 0..n_draw.min(n_total) {
let force = gamma * (omega * t_sim).cos();
let ax = y;
let ay = -delta * y + alpha * x - beta * x * x * x + force;
x += ax * dt;
y += ay * dt;
t_sim += dt;
let norm_x = (x + x_range) / (2.0 * x_range);
let norm_y = (y + y_range) / (2.0 * y_range);
let px = (norm_x.clamp(0.0, 1.0) * (dw - 1) as f32) as i32;
let py = ((1.0 - norm_y.clamp(0.0, 1.0)) * (dh - 1) as f32) as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}
struct TinkerbellMap;
impl ProgressStyle for TinkerbellMap {
fn name(&self) -> &str {
"tinkerbell"
}
fn theme(&self) -> &str {
"chaos"
}
fn describe(&self) -> &str {
"Tinkerbell map: a=0.9 b=−0.6013 c=2.0 d=0.5 — fractal basin boundary fairy-dust scatter"
}
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 a: f32 = 0.9 + 0.05 * (ctx.time * 0.08).sin();
let b: f32 = -0.6013;
let c: f32 = 2.0;
let d: f32 = 0.5;
let n_total = 3000usize;
let n_draw = (ctx.eased * n_total as f32).max(1.0) as usize;
let x_min = -1.5_f32;
let x_max = 1.5_f32;
let y_min = -1.5_f32;
let y_max = 1.5_f32;
let mut x: f32 = -0.72;
let mut y: f32 = -0.64;
for _ in 0..n_draw.min(n_total) {
let xn = x * x - y * y + a * x + b * y;
let yn = 2.0 * x * y + c * x + d * y;
x = xn;
y = yn;
if x.abs() > 5.0 || y.abs() > 5.0 {
break;
}
let norm_x = (x - x_min) / (x_max - x_min);
let norm_y = (y - y_min) / (y_max - y_min);
let px = (norm_x.clamp(0.0, 1.0) * (dw - 1) as f32) as i32;
let py = ((1.0 - norm_y.clamp(0.0, 1.0)) * (dh - 1) as f32) as i32;
draw::dot_i(grid, px, py);
}
Ok(())
}
}