research-hexagon.rs

5.0 kB · rust · 160 lines

1use mrlycore::errors::Result;2use mrlycore::tile::Design;3use mrlyfig::board::Board;4use mrlyfig::ink::Ramp;5use mrlyfig::{hex, ink, save};67const MESH: usize = 110;8const DEEPEST: i64 = 55;9const GRAIN: usize = 8;1011struct Rule {12    corners: [bool; 8],13}1415impl Rule {16    fn new(design: Design) -> Result<Rule> {17        let cube = mrlymath::three::named(design, 2, 1)?;18        let mut corners = [false; 8];19        for (slot, corner) in corners.iter_mut().enumerate() {20            *corner = cube.types().get(&[slot >> 2, (slot >> 1) & 1, slot & 1]) == 1;21        }22        Ok(Rule { corners })23    }2425    fn filled(&self, x: i64, y: i64, z: i64) -> bool {26        let bit = |c: i64| (c.div_euclid(4) & 1) as usize;27        self.corners[bit(x) << 2 | bit(y) << 1 | bit(z)]28    }2930    fn at(&self, n: i64, x: i64, z: i64) -> Option<bool> {31        let y = 6 * n - 2 - x - z;32        (0..4 * n).contains(&y).then(|| self.filled(x, y, z))33    }3435    fn sample(&self, n: i64, x: f64, z: f64) -> Option<bool> {36        if !(0.0..1.0).contains(&x) || !(0.0..1.0).contains(&z) {37            return None;38        }39        let column = ((x * 4.0 * n as f64).floor() as i64).min(4 * n - 1);40        let row = (2 * (z * 2.0 * n as f64).floor() as i64).min(4 * n - 2);41        self.at(n, column, row)42    }43}4445fn layers() -> impl Iterator<Item = i64> {46    (3..=DEEPEST).step_by(2)47}4849fn stack(rule: &Rule, x: f64, z: f64) -> Option<f64> {50    let mut filled = 0usize;51    let mut seen = 0usize;52    for n in layers() {53        if let Some(cell) = rule.sample(n, x, z) {54            seen += 1;55            filled += usize::from(cell);56        }57    }58    (seen > 0).then(|| filled as f64 / seen as f64)59}6061fn unit(across: f64, down: f64) -> (f64, f64) {62    let span = 2.0 * MESH as f64;63    let z = 1.0 - down / span;64    (0.25 + across / span - 0.5 * z, z)65}6667fn corners(row: usize, col: usize) -> [(f64, f64); 3] {68    let up = row < MESH;69    let reach = if up { row } else { 2 * MESH - 1 - row };70    let (long, short) = (MESH + reach + 1, MESH + reach);71    let (top_len, bot_len) = if up { (short, long) } else { (long, short) };72    let step = (col / 2) as f64;73    let (down, top) = (row as f64, row as f64 + 1.0);74    if col.is_multiple_of(2) == up {75        let edge = (2 * MESH - bot_len) as f64 / 2.0 + step;76        [77            unit(edge, top),78            unit(edge + 1.0, top),79            unit(edge + 0.5, down),80        ]81    } else {82        let edge = (2 * MESH - top_len) as f64 / 2.0 + step;83        [84            unit(edge, down),85            unit(edge + 1.0, down),86            unit(edge + 0.5, top),87        ]88    }89}9091fn shade(rule: &Rule, tri: [(f64, f64); 3]) -> Option<f64> {92    let mut total = 0.0;93    let mut taken = 0usize;94    for i in 0..GRAIN {95        for j in 0..GRAIN - i {96            let a = (i as f64 + 1.0 / 3.0) / GRAIN as f64;97            let b = (j as f64 + 1.0 / 3.0) / GRAIN as f64;98            let c = 1.0 - a - b;99            let x = a * tri[0].0 + b * tri[1].0 + c * tri[2].0;100            let z = a * tri[0].1 + b * tri[1].1 + c * tri[2].1;101            if let Some(value) = stack(rule, x, z) {102                total += value;103                taken += 1;104            }105        }106    }107    (taken > 0).then(|| total / taken as f64)108}109110fn ink_law(rule: &Rule, n: i64) -> bool {111    let chi = if (3 * n - 1) / 2 % 2 == 0 { 1 } else { -1 };112    let (mut filled, mut inside) = (0i64, 0i64);113    for x in 0..4 * n {114        for step in 0..2 * n {115            match rule.at(n, x, 2 * step) {116                Some(cell) => {117                    inside += 1;118                    filled += i64::from(cell);119                }120                None => continue,121            }122        }123    }124    filled * 8 * n * n == inside * (4 * n * n + chi * n * n + 4 * n - chi)125}126127fn main() -> Result<()> {128    let carpet = Rule::new(Design::Carpet)?;129    assert_eq!(layers().count(), 27);130    assert!(layers().all(|n| ink_law(&carpet, n)));131132    let mut field: Vec<Vec<Option<f64>>> = Vec::with_capacity(2 * MESH);133    for row in 0..2 * MESH {134        let mut line = Vec::with_capacity(hex::row_len(MESH, row));135        for col in 0..hex::row_len(MESH, row) {136            line.push(shade(&carpet, corners(row, col)));137        }138        field.push(line);139    }140    assert_eq!(field.iter().map(Vec::len).sum::<usize>(), hex::count(MESH));141    assert!(layers().all(|n| carpet.sample(n, 0.5, 0.5).is_some()));142143    let drawn: Vec<f64> = field.iter().flatten().flatten().copied().collect();144    let background = drawn.iter().sum::<f64>() / drawn.len() as f64;145    let mut spread: Vec<f64> = drawn146        .iter()147        .map(|value| (value - background).abs())148        .collect();149    spread.sort_by(|a, b| a.total_cmp(b));150    let reach = spread[spread.len() * 9 / 10];151152    let ramp = Ramp::new(vec![ink::blue(), ink::panel(), ink::yellow()]);153    let mut board = Board::square();154    let frame = board.frame(0.08);155    hex::hexagon(&mut board, frame, MESH, 0.0, |row, col, _| {156        field[row][col].map(|value| ramp.at(0.5 + (value - background) / (2.0 * reach)))157    });158    save("research-hexagon", &board)?;159    Ok(())160}