wiki-substitution-tiling.rs
3.8 kB · rust · 128 lines
1use figures::{ink, save, Board, Frame};2use mrlyrs::core::error::Result;34const DEPTH: usize = 3;5const GAPS: [f64; DEPTH] = [22.0, 11.0, 4.0];67#[derive(Clone, Copy)]8struct Chair {9 x: f64,10 y: f64,11 side: f64,12 turn: usize,13}1415fn spin(p: (f64, f64), centre: (f64, f64), turn: usize) -> (f64, f64) {16 let (mut dx, mut dy) = (p.0 - centre.0, p.1 - centre.1);17 for _ in 0..turn % 4 {18 (dx, dy) = (-dy, dx);19 }20 (centre.0 + dx, centre.1 + dy)21}2223fn outline(c: Chair) -> Vec<(f64, f64)> {24 let (s, h) = (c.side, c.side / 2.0);25 let centre = (c.x + h, c.y + h);26 [(0.0, 0.0), (s, 0.0), (s, h), (h, h), (h, s), (0.0, s)]27 .iter()28 .map(|&(u, v)| spin((c.x + u, c.y + v), centre, c.turn))29 .collect()30}3132fn inflate(c: Chair) -> [Chair; 4] {33 let h = c.side / 2.0;34 let q = c.side / 4.0;35 let centre = (c.x + h, c.y + h);36 let place = |x: f64, y: f64, turn: usize| {37 let mid = spin((c.x + x + q, c.y + y + q), centre, c.turn);38 Chair {39 x: mid.0 - q,40 y: mid.1 - q,41 side: h,42 turn: (c.turn + turn) % 4,43 }44 };45 [46 place(0.0, 0.0, 0),47 place(q, q, 0),48 place(h, 0.0, 1),49 place(0.0, h, 3),50 ]51}5253fn area(c: Chair) -> f64 {54 0.75 * c.side * c.side55}5657fn main() -> Result<()> {58 let mut board = Board::square();59 let frame: Frame = board.frame(0.08);60 let root = Chair {61 x: 0.0,62 y: 0.0,63 side: 1.0,64 turn: 0,65 };66 let mut levels = vec![vec![root]];67 for _ in 0..DEPTH {68 let next: Vec<Chair> = levels69 .last()70 .unwrap()71 .iter()72 .flat_map(|c| inflate(*c))73 .collect();74 levels.push(next);75 }76 let counts: Vec<usize> = levels.iter().map(Vec::len).collect();77 assert_eq!(counts, vec![1, 4, 16, 64]);78 let leaves = levels.last().unwrap();79 let total: f64 = leaves.iter().map(|c| area(*c)).sum();80 assert!((total - area(root)).abs() < 1e-12);81 let mut cells = std::collections::HashSet::new();82 let grain = 2.0 / leaves[0].side;83 for c in leaves {84 let h = c.side / 2.0;85 for (u, v) in [(0.25, 0.25), (0.75, 0.25), (0.25, 0.75), (0.75, 0.75)] {86 let p = spin(87 (c.x + u * c.side, c.y + v * c.side),88 (c.x + h, c.y + h),89 c.turn,90 );91 let inside = !(u > 0.5 && v > 0.5);92 if inside {93 assert!(cells.insert(((p.0 * grain).floor() as i64, (p.1 * grain).floor() as i64)));94 }95 }96 }97 assert_eq!(cells.len(), 3 * 64);98 assert!(cells99 .iter()100 .all(|&(i, j)| (0..16).contains(&i) && (0..16).contains(&j) && (i < 8 || j < 8)));101 let even = leaves.iter().filter(|c| c.turn.is_multiple_of(2)).count();102 assert_eq!((even, leaves.len() - even), (32, 32));103104 let map = |p: (f64, f64)| (frame.x + p.0 * frame.w, frame.y + (1.0 - p.1) * frame.h);105 for c in leaves {106 let pts: Vec<(f64, f64)> = outline(*c).into_iter().map(map).collect();107 let color = if c.turn.is_multiple_of(2) {108 ink::blue()109 } else {110 ink::yellow()111 };112 board.polygon(&pts, color);113 }114 for depth in (1..=DEPTH).rev() {115 for c in &levels[depth] {116 let pts: Vec<(f64, f64)> = outline(*c).into_iter().map(map).collect();117 let gap = GAPS[depth - 1];118 for (i, a) in pts.iter().enumerate() {119 let b = pts[(i + 1) % pts.len()];120 let (x, y) = (a.0.min(b.0) - gap / 2.0, a.1.min(b.1) - gap / 2.0);121 let (w, h) = ((a.0 - b.0).abs() + gap, (a.1 - b.1).abs() + gap);122 board.rect(x, y, w, h, ink::ground());123 }124 }125 }126 save("wiki-substitution-tiling", &board)?;127 Ok(())128}