demo-apollonian.rs

3.8 kB · rust · 119 lines

1use mrlycore::errors::Result;2use mrlyfig::{ink, save, Board, Color, Frame};3use mrlynum::apollonian::{grow, is_ford, on_line, shadow, touches, Circle};4use mrlynum::lattice::farey;56const TOP: i64 = 2048;7const DEEP: usize = 32;8const CIRCLES: usize = 2448;9const RESTING: usize = 323;10const BRIGHT: u128 = 528;11const SIDE: f64 = 640.0;12const BAND: f64 = 232.0;13const OVER: f64 = 26.0;14const TICK: f64 = 3.0;15const RULE: f64 = 2.0;1617fn octave(k: i64) -> usize {18    k.ilog2() as usize % 619}2021fn ring(board: &mut Board, at: (f64, f64), r: f64, thick: f64, span: (f64, f64), color: Color) {22    let half = thick / 2.0;23    let reach = r + half + 1.0;24    let x0 = (at.0 - reach).max(span.0 - 1.0).max(0.0).floor() as usize;25    let x1 = (at.0 + reach).min(span.1 + 1.0).max(0.0).ceil() as usize;26    let y0 = (at.1 - reach).max(0.0).floor() as usize;27    let y1 = (at.1 + reach).max(0.0).ceil() as usize;28    for py in y0..y1.min(board.height) {29        for px in x0..x1.min(board.width) {30            let (fx, fy) = (px as f64 + 0.5, py as f64 + 0.5);31            let edge = (((fx - at.0).powi(2) + (fy - at.1).powi(2)).sqrt() - r).abs() - half;32            let cut = (span.0 - fx).max(fx - span.1);33            board.blend(px, py, color, 0.5 - edge.max(cut));34        }35    }36}3738fn main() -> Result<()> {39    let p = grow("strip", TOP)?;40    let marks = touches(&p);41    let read = shadow(&p, DEEP)?;42    let stack: Vec<(i64, i64, u64)> = farey(DEEP)43        .iter()44        .filter(|n| n.num > 0 && n.num < n.den)45        .map(|n| (n.num as i64, n.den as i64, n.brightness))46        .collect();4748    assert_eq!(p.circles.len(), CIRCLES);49    assert_eq!((p.broken, p.strayed), (0, 0));50    assert!(p.strip);51    assert_eq!(marks.len(), RESTING);52    assert!(p53        .circles54        .iter()55        .filter(|c| on_line(**c))56        .all(|&c| is_ford(c)));57    assert!(read.covered);58    assert_eq!(59        (read.nodes, read.touched, read.missed),60        (RESTING, RESTING, 0)61    );62    assert_eq!((read.bright, read.want), (BRIGHT, BRIGHT));63    assert_eq!(stack.len(), RESTING);64    let mut peak = 0u64;65    for (node, mark) in stack.iter().zip(marks.iter()) {66        assert_eq!((node.0, node.1), (mark.num, mark.den));67        assert_eq!(mark.k, 2 * mark.den * mark.den);68        assert_eq!(node.2, DEEP as u64 / mark.den as u64);69        peak = peak.max(node.2);70    }71    assert_eq!(peak, DEEP as u64 / 2);7273    let mut board = Board::square();74    let left = (board.width as f64 - SIDE) / 2.0;75    let head = (board.height as f64 - (SIDE + BAND)) / 2.0;76    let frame = Frame::new(left, head, SIDE, SIDE);77    let span = (frame.x, frame.x + frame.w);78    let foot = frame.y + frame.h;79    let six = ink::inks();80    let place = |c: &Circle| {81        let (x, y) = c.centre().unwrap_or((0.0, 0.0));82        (83            (frame.x + frame.w * x, foot - frame.h * y),84            frame.w / c.k as f64,85        )86    };8788    for rule in [frame.y, foot] {89        board.segment(90            (span.0 - OVER, rule),91            (span.1 + OVER, rule),92            RULE,93            ink::dim(),94        );95    }96    for c in p97        .circles98        .iter()99        .chain(p.root.iter())100        .filter(|c| !c.is_line())101    {102        let (at, r) = place(c);103        let hair = (r / 12.0).clamp(0.65, 1.8);104        let thick = if is_ford(*c) { hair * 1.5 } else { hair };105        ring(&mut board, at, r, thick, span, six[octave(c.k)]);106    }107    for (num, den, bright) in &stack {108        let at = frame.x + frame.w * *num as f64 / *den as f64;109        let lit = *bright as f64 / peak as f64;110        board.segment(111            (at, foot - TICK),112            (at, foot + BAND * lit),113            0.75 + 2.0 * lit,114            ink::fade(ink::blue(), 0.45 + 0.55 * lit),115        );116    }117    save("demo-apollonian", &board)?;118    Ok(())119}