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}