use super::Animation;
use crate::render::Canvas;
pub struct Sierpinski {
zoom: f64,
}
impl Sierpinski {
#[allow(unused_variables)]
pub fn new(_width: usize, _height: usize, _scale: f64) -> Self {
Sierpinski { zoom: 1.0 }
}
}
impl Animation for Sierpinski {
fn name(&self) -> &str {
"sierpinski"
}
fn preferred_render(&self) -> crate::render::RenderMode {
crate::render::RenderMode::Braille
}
fn update(&mut self, canvas: &mut Canvas, _dt: f64, time: f64) {
let w = canvas.width as f64;
let h = canvas.height as f64;
let cx = w * 0.5;
let cy = h * 0.5;
self.zoom = 1.0 + (time * 0.3).sin().abs() * 4.0;
let color_offset = time * 0.2;
canvas.clear();
let size = w.min(h) * 0.9 * self.zoom;
let half = size * 0.5;
let ax = cx;
let ay = cy - half * 0.866;
let bx = cx - half;
let by = cy + half * 0.433;
let _cx_t = cx + half;
let _cy_t = cy + half * 0.433;
for y in 0..canvas.height {
for x in 0..canvas.width {
let fx = x as f64;
let fy = y as f64;
let rel_x = (fx - (cx - half)) / size;
let rel_y = (fy - (cy - half * 0.866)) / (size * 0.866);
if !(0.0..1.0).contains(&rel_x) || !(0.0..1.0).contains(&rel_y) {
continue;
}
let mut px = rel_x;
let mut py = rel_y;
let mut in_set = true;
let max_depth = 8;
let mut depth = 0;
for d in 0..max_depth {
let sx = px * 2.0;
let sy = py * 2.0;
if sy > 1.0 {
px = sx - 0.5;
py = sy - 1.0;
if !(0.0..=1.0).contains(&px) {
in_set = false;
break;
}
} else if sx < 1.0 {
px = sx;
py = sy;
} else {
px = sx - 1.0;
py = sy;
}
if sy <= 1.0 && (0.5..=1.5).contains(&sx) {
let mid = sx - 0.5;
if mid <= 1.0 && sy < 1.0 - (mid - 0.5).abs() * 2.0 {
if sy > 0.0 {
in_set = false;
break;
}
}
}
depth = d;
}
if in_set {
let hue = ((depth as f64 / max_depth as f64) + color_offset).fract();
let (r, g, b) = hsv_to_rgb(hue, 0.8, 0.9);
let brightness = 0.5 + (depth as f64 / max_depth as f64) * 0.5;
canvas.set_colored(x, y, brightness, r, g, b);
}
}
}
draw_sierpinski_recursive(canvas, ax, ay, bx, by, _cx_t, _cy_t, 6, 0, color_offset);
let _ = self.zoom;
}
}
#[allow(clippy::too_many_arguments)]
fn draw_sierpinski_recursive(
canvas: &mut Canvas,
ax: f64,
ay: f64,
bx: f64,
by: f64,
cx: f64,
cy: f64,
depth: usize,
current_depth: usize,
color_offset: f64,
) {
if depth == 0 {
fill_triangle(canvas, ax, ay, bx, by, cx, cy, current_depth, color_offset);
return;
}
let mab_x = (ax + bx) * 0.5;
let mab_y = (ay + by) * 0.5;
let mbc_x = (bx + cx) * 0.5;
let mbc_y = (by + cy) * 0.5;
let mac_x = (ax + cx) * 0.5;
let mac_y = (ay + cy) * 0.5;
draw_sierpinski_recursive(
canvas,
ax,
ay,
mab_x,
mab_y,
mac_x,
mac_y,
depth - 1,
current_depth + 1,
color_offset,
);
draw_sierpinski_recursive(
canvas,
mab_x,
mab_y,
bx,
by,
mbc_x,
mbc_y,
depth - 1,
current_depth + 1,
color_offset,
);
draw_sierpinski_recursive(
canvas,
mac_x,
mac_y,
mbc_x,
mbc_y,
cx,
cy,
depth - 1,
current_depth + 1,
color_offset,
);
}
#[allow(clippy::too_many_arguments)]
fn fill_triangle(
canvas: &mut Canvas,
ax: f64,
ay: f64,
bx: f64,
by: f64,
cx: f64,
cy: f64,
depth: usize,
color_offset: f64,
) {
let min_x = ax.min(bx).min(cx).max(0.0) as usize;
let max_x = ax.max(bx).max(cx).min(canvas.width as f64 - 1.0) as usize;
let min_y = ay.min(by).min(cy).max(0.0) as usize;
let max_y = ay.max(by).max(cy).min(canvas.height as f64 - 1.0) as usize;
let hue = (depth as f64 * 0.12 + color_offset).fract();
let (r, g, b) = hsv_to_rgb(hue, 0.85, 0.95);
let brightness = 0.6 + (depth as f64 * 0.05).min(0.4);
for y in min_y..=max_y {
for x in min_x..=max_x {
if point_in_triangle(x as f64, y as f64, ax, ay, bx, by, cx, cy) {
canvas.set_colored(x, y, brightness, r, g, b);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn point_in_triangle(
px: f64,
py: f64,
ax: f64,
ay: f64,
bx: f64,
by: f64,
cx: f64,
cy: f64,
) -> bool {
let d1 = sign(px, py, ax, ay, bx, by);
let d2 = sign(px, py, bx, by, cx, cy);
let d3 = sign(px, py, cx, cy, ax, ay);
let has_neg = (d1 < 0.0) || (d2 < 0.0) || (d3 < 0.0);
let has_pos = (d1 > 0.0) || (d2 > 0.0) || (d3 > 0.0);
!(has_neg && has_pos)
}
fn sign(px: f64, py: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
(px - x2) * (y1 - y2) - (x1 - x2) * (py - y2)
}
fn hsv_to_rgb(h: f64, s: f64, v: f64) -> (u8, u8, u8) {
let c = v * s;
let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
let m = v - c;
let (r, g, b) = match (h * 6.0) as u32 {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
_ => (c, 0.0, x),
};
(
((r + m) * 255.0) as u8,
((g + m) * 255.0) as u8,
((b + m) * 255.0) as u8,
)
}