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}