paper-divisor-avatars.rs

4.3 kB · rust · 145 lines

1use mrlycore::errors::Result;2use mrlyfig::{ink, iso, save, Board, Frame};3use mrlymath::three::designs;4use mrlymath::three::faces::quads;56const SIDE: usize = 3;7const HALF: f64 = SIDE as f64 / 2.0;89enum Mark {10    Face(Vec<(f64, f64)>, usize),11    Cage(Vec<[(f64, f64); 2]>),12}1314fn even(c: &[u8]) -> usize {15    c.iter().filter(|&&v| v == 1).count()16}1718fn corners() -> Vec<Vec<u8>> {19    let mut out = Vec::new();20    for i in 0..SIDE {21        for j in 0..SIDE {22            for k in 0..SIDE {23                out.push(vec![i as u8, j as u8, k as u8]);24            }25        }26    }27    out28}2930fn node(i: f64, j: f64, k: f64) -> (f64, f64) {31    iso::project((i - HALF) / HALF, (j - HALF) / HALF, (k - HALF) / HALF)32}3334fn cage(c: &[u8]) -> Vec<[(f64, f64); 2]> {35    let (x, y, z) = (c[0] as f64, c[1] as f64, c[2] as f64);36    let corner = |b: usize| {37        node(38            x + (b & 1) as f64,39            y + ((b >> 1) & 1) as f64,40            z + ((b >> 2) & 1) as f64,41        )42    };43    let mut out = Vec::new();44    for a in 0usize..8 {45        for b in (a + 1)..8 {46            if (a ^ b).count_ones() == 1 {47                out.push([corner(a), corner(b)]);48            }49        }50    }51    out52}5354fn place(marks: &[(f64, Mark)], frame: Frame) -> impl Fn((f64, f64)) -> (f64, f64) {55    let mut lo = (f64::MAX, f64::MAX);56    let mut hi = (f64::MIN, f64::MIN);57    for (_, mark) in marks {58        if let Mark::Face(pts, _) = mark {59            for p in pts {60                lo.0 = lo.0.min(p.0);61                lo.1 = lo.1.min(p.1);62                hi.0 = hi.0.max(p.0);63                hi.1 = hi.1.max(p.1);64            }65        }66    }67    let (sx, sy) = (hi.0 - lo.0, hi.1 - lo.1);68    let scale = (frame.w / sx).min(frame.h / sy);69    let ox = frame.x + (frame.w - sx * scale) / 2.0;70    let oy = frame.y + (frame.h - sy * scale) / 2.0;71    move |p: (f64, f64)| (ox + (p.0 - lo.0) * scale, oy + (p.1 - lo.1) * scale)72}7374fn main() -> Result<()> {75    let mut board = Board::square();76    let frame = board.frame(0.08);77    let all = corners();78    let kept: Vec<Vec<u8>> = all.iter().filter(|c| even(c) <= 1).cloned().collect();79    let drilled: Vec<Vec<u8>> = all.iter().filter(|c| even(c) >= 2).cloned().collect();80    assert_eq!(kept.len(), 20);81    assert_eq!(drilled.len(), 7);82    let solid = designs::from_corners(&kept, SIDE, 1, SIDE)?;83    assert_eq!(solid.types().sum(), 20);84    let mut marks: Vec<(f64, Mark)> = Vec::new();85    for quad in quads(&solid) {86        let n = (87            quad.normal.x as f64,88            quad.normal.y as f64,89            quad.normal.z as f64,90        );91        if n.0 + n.1 + n.2 <= 0.0 {92            continue;93        }94        let tone = if n.2 > 0.0 {95            096        } else if n.1 > 0.0 {97            198        } else {99            2100        };101        let pts: Vec<(f64, f64)> = quad102            .verts103            .iter()104            .map(|v| iso::project(v.x as f64, v.y as f64, v.z as f64))105            .collect();106        let depth = quad107            .verts108            .iter()109            .map(|v| (v.x + v.y + v.z) as f64)110            .sum::<f64>()111            / 4.0;112        marks.push((depth, Mark::Face(pts, tone)));113    }114    for cell in &drilled {115        let mid = cell.iter().map(|&v| v as f64 + 0.5).sum::<f64>();116        let depth = (mid - 3.0 * HALF) / HALF;117        marks.push((depth, Mark::Cage(cage(cell))));118    }119    marks.sort_by(|a, b| a.0.total_cmp(&b.0));120    let put = place(&marks, frame);121    let gold = [122        ink::yellow(),123        ink::mix(ink::yellow(), ink::ground(), 0.4),124        ink::mix(ink::yellow(), ink::ground(), 0.66),125    ];126    let unit = frame.h / (SIDE as f64 * 2.0);127    for (_, mark) in &marks {128        match mark {129            Mark::Face(pts, tone) => {130                let screen: Vec<(f64, f64)> = pts.iter().map(|p| put(*p)).collect();131                board.polygon(&screen, gold[*tone]);132                let mut ring = screen.clone();133                ring.push(screen[0]);134                board.polyline(&ring, unit / 70.0, ink::line());135            }136            Mark::Cage(edges) => {137                for edge in edges {138                    board.segment(put(edge[0]), put(edge[1]), unit / 34.0, ink::dim());139                }140            }141        }142    }143    save("paper-divisor-avatars", &board)?;144    Ok(())145}