use mrlycore::errors::Result; use mrlycore::tile::Design; use mrlyfig::board::Board; use mrlyfig::ink::Ramp; use mrlyfig::{hex, ink, save}; const MESH: usize = 110; const DEEPEST: i64 = 55; const GRAIN: usize = 8; struct Rule { corners: [bool; 8], } impl Rule { fn new(design: Design) -> Result { let cube = mrlymath::three::named(design, 2, 1)?; let mut corners = [false; 8]; for (slot, corner) in corners.iter_mut().enumerate() { *corner = cube.types().get(&[slot >> 2, (slot >> 1) & 1, slot & 1]) == 1; } Ok(Rule { corners }) } fn filled(&self, x: i64, y: i64, z: i64) -> bool { let bit = |c: i64| (c.div_euclid(4) & 1) as usize; self.corners[bit(x) << 2 | bit(y) << 1 | bit(z)] } fn at(&self, n: i64, x: i64, z: i64) -> Option { let y = 6 * n - 2 - x - z; (0..4 * n).contains(&y).then(|| self.filled(x, y, z)) } fn sample(&self, n: i64, x: f64, z: f64) -> Option { if !(0.0..1.0).contains(&x) || !(0.0..1.0).contains(&z) { return None; } let column = ((x * 4.0 * n as f64).floor() as i64).min(4 * n - 1); let row = (2 * (z * 2.0 * n as f64).floor() as i64).min(4 * n - 2); self.at(n, column, row) } } fn layers() -> impl Iterator { (3..=DEEPEST).step_by(2) } fn stack(rule: &Rule, x: f64, z: f64) -> Option { let mut filled = 0usize; let mut seen = 0usize; for n in layers() { if let Some(cell) = rule.sample(n, x, z) { seen += 1; filled += usize::from(cell); } } (seen > 0).then(|| filled as f64 / seen as f64) } fn unit(across: f64, down: f64) -> (f64, f64) { let span = 2.0 * MESH as f64; let z = 1.0 - down / span; (0.25 + across / span - 0.5 * z, z) } fn corners(row: usize, col: usize) -> [(f64, f64); 3] { let up = row < MESH; let reach = if up { row } else { 2 * MESH - 1 - row }; let (long, short) = (MESH + reach + 1, MESH + reach); let (top_len, bot_len) = if up { (short, long) } else { (long, short) }; let step = (col / 2) as f64; let (down, top) = (row as f64, row as f64 + 1.0); if col.is_multiple_of(2) == up { let edge = (2 * MESH - bot_len) as f64 / 2.0 + step; [ unit(edge, top), unit(edge + 1.0, top), unit(edge + 0.5, down), ] } else { let edge = (2 * MESH - top_len) as f64 / 2.0 + step; [ unit(edge, down), unit(edge + 1.0, down), unit(edge + 0.5, top), ] } } fn shade(rule: &Rule, tri: [(f64, f64); 3]) -> Option { let mut total = 0.0; let mut taken = 0usize; for i in 0..GRAIN { for j in 0..GRAIN - i { let a = (i as f64 + 1.0 / 3.0) / GRAIN as f64; let b = (j as f64 + 1.0 / 3.0) / GRAIN as f64; let c = 1.0 - a - b; let x = a * tri[0].0 + b * tri[1].0 + c * tri[2].0; let z = a * tri[0].1 + b * tri[1].1 + c * tri[2].1; if let Some(value) = stack(rule, x, z) { total += value; taken += 1; } } } (taken > 0).then(|| total / taken as f64) } fn ink_law(rule: &Rule, n: i64) -> bool { let chi = if (3 * n - 1) / 2 % 2 == 0 { 1 } else { -1 }; let (mut filled, mut inside) = (0i64, 0i64); for x in 0..4 * n { for step in 0..2 * n { match rule.at(n, x, 2 * step) { Some(cell) => { inside += 1; filled += i64::from(cell); } None => continue, } } } filled * 8 * n * n == inside * (4 * n * n + chi * n * n + 4 * n - chi) } fn main() -> Result<()> { let carpet = Rule::new(Design::Carpet)?; assert_eq!(layers().count(), 27); assert!(layers().all(|n| ink_law(&carpet, n))); let mut field: Vec>> = Vec::with_capacity(2 * MESH); for row in 0..2 * MESH { let mut line = Vec::with_capacity(hex::row_len(MESH, row)); for col in 0..hex::row_len(MESH, row) { line.push(shade(&carpet, corners(row, col))); } field.push(line); } assert_eq!(field.iter().map(Vec::len).sum::(), hex::count(MESH)); assert!(layers().all(|n| carpet.sample(n, 0.5, 0.5).is_some())); let drawn: Vec = field.iter().flatten().flatten().copied().collect(); let background = drawn.iter().sum::() / drawn.len() as f64; let mut spread: Vec = drawn .iter() .map(|value| (value - background).abs()) .collect(); spread.sort_by(|a, b| a.total_cmp(b)); let reach = spread[spread.len() * 9 / 10]; let ramp = Ramp::new(vec![ink::blue(), ink::panel(), ink::yellow()]); let mut board = Board::square(); let frame = board.frame(0.08); hex::hexagon(&mut board, frame, MESH, 0.0, |row, col, _| { field[row][col].map(|value| ramp.at(0.5 + (value - background) / (2.0 * reach))) }); save("research-hexagon", &board)?; Ok(()) }