wiki-euler-characteristic.rs

4.7 kB · rust · 138 lines

1use mrlycore::errors::Result;2use mrlyfig::{ink, iso, save, Board, Frame, Grid};3use mrlymath::two::{census, designs};45const SIDE: usize = 9;67fn corner(i: usize) -> (f64, f64, f64) {8    ((i & 1) as f64, ((i >> 1) & 1) as f64, ((i >> 2) & 1) as f64)9}1011fn cube(board: &mut Board, frame: Frame) -> (usize, usize, usize) {12    let flat: Vec<(f64, f64)> = (0..8)13        .map(|i| {14            let (x, y, z) = corner(i);15            iso::project(x, y, z)16        })17        .collect();18    let mut lo = (f64::MAX, f64::MAX);19    let mut hi = (f64::MIN, f64::MIN);20    for p in &flat {21        lo.0 = lo.0.min(p.0);22        lo.1 = lo.1.min(p.1);23        hi.0 = hi.0.max(p.0);24        hi.1 = hi.1.max(p.1);25    }26    let span = (hi.0 - lo.0).max(hi.1 - lo.1);27    let scale = frame.w.min(frame.h) * 0.95 / span;28    let (cx, cy) = frame.center();29    let place = |p: (f64, f64)| {30        (31            cx + (p.0 - (lo.0 + hi.0) / 2.0) * scale,32            cy + (p.1 - (lo.1 + hi.1) / 2.0) * scale,33        )34    };3536    let faces = [[4usize, 5, 7, 6], [1, 3, 7, 5], [2, 3, 7, 6]];37    let tones = [38        ink::blue(),39        ink::mix(ink::blue(), ink::ground(), 0.3),40        ink::mix(ink::blue(), ink::ground(), 0.55),41    ];42    for (quad, tone) in faces.iter().zip(tones) {43        let pts: Vec<(f64, f64)> = quad.iter().map(|i| place(flat[*i])).collect();44        board.polygon(&pts, tone);45    }4647    let mut edges = 0usize;48    for a in 0..8usize {49        for b in a + 1..8usize {50            if (a ^ b).count_ones() != 1 {51                continue;52            }53            let hidden = a == 0 || b == 0;54            board.segment(55                place(flat[a]),56                place(flat[b]),57                if hidden { 4.0 } else { 7.0 },58                if hidden { ink::dim() } else { ink::fg() },59            );60            edges += 1;61        }62    }63    for p in &flat {64        let (x, y) = place(*p);65        board.disc(x, y, 15.0, ink::yellow());66    }67    (8, edges, faces.len() * 2)68}6970fn holes(board: &mut Board, frame: Frame) -> Result<usize> {71    let carpet = designs::carpet(3, 2)?;72    assert_eq!(carpet.width(), SIDE);73    let grid = Grid::new(frame, SIDE, SIDE, 0.0);74    grid.paint(board, &carpet, |kind| {75        (kind != 0).then_some(ink::blue())76    });7778    let mut seen = [[false; SIDE]; SIDE];79    let mut found = 0usize;80    for row in 0..SIDE {81        for col in 0..SIDE {82            if seen[row][col] || carpet.types().get(&[row, col]) != 0 {83                continue;84            }85            let mut stack = vec![(row, col)];86            let mut cells = Vec::new();87            seen[row][col] = true;88            while let Some((r, c)) = stack.pop() {89                cells.push((r, c));90                let step = |r: usize, c: usize, stack: &mut Vec<(usize, usize)>, seen: &mut [[bool; SIDE]; SIDE]| {91                    if !seen[r][c] && carpet.types().get(&[r, c]) == 0 {92                        seen[r][c] = true;93                        stack.push((r, c));94                    }95                };96                if r > 0 {97                    step(r - 1, c, &mut stack, &mut seen);98                }99                if r + 1 < SIDE {100                    step(r + 1, c, &mut stack, &mut seen);101                }102                if c > 0 {103                    step(r, c - 1, &mut stack, &mut seen);104                }105                if c + 1 < SIDE {106                    step(r, c + 1, &mut stack, &mut seen);107                }108            }109            let rows: Vec<usize> = cells.iter().map(|(r, _)| *r).collect();110            let cols: Vec<usize> = cells.iter().map(|(_, c)| *c).collect();111            let (r0, r1) = (*rows.iter().min().unwrap(), *rows.iter().max().unwrap());112            let (c0, c1) = (*cols.iter().min().unwrap(), *cols.iter().max().unwrap());113            let (x0, y0, _, _) = grid.cell(c0, r0);114            let (x1, y1, w, h) = grid.cell(c1, r1);115            let cx = (x0 + x1 + w) / 2.0;116            let cy = (y0 + y1 + h) / 2.0;117            let radius = ((x1 + w - x0).min(y1 + h - y0)) * 0.62;118            board.ring(cx, cy, radius, 6.0, ink::orange());119            found += 1;120        }121    }122    Ok(found)123}124125fn main() -> Result<()> {126    let mut board = Board::square();127    let area = board.frame(0.06);128    let panes = area.cols(2);129    let (vertices, edges, faces) = cube(&mut board, panes[0].square().inset(18.0));130    assert_eq!((vertices, edges, faces), (8, 12, 6));131    assert_eq!(vertices as i64 - edges as i64 + faces as i64, 2);132    let found = holes(&mut board, panes[1].square().inset(18.0))?;133    assert_eq!(found, 9);134    assert_eq!(census::euler(&designs::carpet(3, 2)?)?, 1 - found as i64);135    assert_eq!(census::euler(&designs::carpet(3, 3)?)?, -72);136    save("wiki-euler-characteristic", &board)?;137    Ok(())138}