grid.rs
4.8 kB · rust · 140 lines
1use crate::board::{Board, Frame};2use mrlycore::Color;3use mrlymath::two::Cell2d;45// GRID67/// A lattice of cells laid over a frame, each cell drawn inside its own gap.8#[derive(Clone, Copy, Debug, PartialEq)]9pub struct Grid {10 /// The frame the lattice covers.11 pub frame: Frame,12 /// The number of columns.13 pub cols: usize,14 /// The number of rows.15 pub rows: usize,16 /// The share of a cell left empty between one cell and the next.17 pub gap: f64,18}1920impl Grid {21 /// Lays a lattice of cols by rows over the frame, keeping a gap of that fraction of a cell.22 pub fn new(frame: Frame, cols: usize, rows: usize, gap: f64) -> Grid {23 Grid {24 frame,25 cols: cols.max(1),26 rows: rows.max(1),27 gap,28 }29 }30 /// Returns the drawn box of one cell as its corner and its size; at gap zero the edges snap to whole pixels so neighbours meet without a seam.31 pub fn cell(&self, col: usize, row: usize) -> (f64, f64, f64, f64) {32 let w = self.frame.w / self.cols as f64;33 let h = self.frame.h / self.rows as f64;34 if self.gap <= 0.0 {35 let x0 = (self.frame.x + col as f64 * w).round();36 let x1 = (self.frame.x + (col + 1) as f64 * w).round();37 let y0 = (self.frame.y + row as f64 * h).round();38 let y1 = (self.frame.y + (row + 1) as f64 * h).round();39 return (x0, y0, x1 - x0, y1 - y0);40 }41 let pad = self.gap * w.min(h) / 2.0;42 (43 self.frame.x + col as f64 * w + pad,44 self.frame.y + row as f64 * h + pad,45 w - 2.0 * pad,46 h - 2.0 * pad,47 )48 }49 /// Fills one cell.50 pub fn fill(&self, board: &mut Board, col: usize, row: usize, color: Color) {51 let (x, y, w, h) = self.cell(col, row);52 board.rect(x, y, w, h, color);53 }54 /// Fills every cell of a flat design whose type byte the ink maps to a color.55 pub fn paint(&self, board: &mut Board, cells: &Cell2d, ink: impl Fn(u8) -> Option<Color>) {56 let types = cells.types();57 let (height, width) = (cells.height(), cells.width());58 for row in 0..self.rows.min(height) {59 for col in 0..self.cols.min(width) {60 if let Some(color) = ink(types.get(&[row, col])) {61 self.fill(board, col, row, color);62 }63 }64 }65 }66 /// Fills every true cell of a mask.67 pub fn carpet(&self, board: &mut Board, mask: &[Vec<bool>], color: Color) {68 for (row, line) in mask.iter().enumerate().take(self.rows) {69 for (col, on) in line.iter().enumerate().take(self.cols) {70 if *on {71 self.fill(board, col, row, color);72 }73 }74 }75 }76}7778// MASKS7980/// The MrlyProd logo, the five by five seed the mark grows from.81pub const LOGO: [&str; 5] = ["11111", "10101", "11111", "10101", "11111"];8283/// Grows a 0/1 string mask by Kronecker substitution, level one being the seed itself.84pub fn mask(rows: &[&str], level: usize) -> Vec<Vec<bool>> {85 let seed: Vec<Vec<bool>> = rows86 .iter()87 .map(|row| row.chars().map(|c| c == '1').collect())88 .collect();89 let mut out = seed.clone();90 for _ in 1..level.max(1) {91 let width = seed.first().map_or(0, |row| row.len());92 let mut next = Vec::with_capacity(out.len() * seed.len());93 for row in &out {94 for inner in &seed {95 let mut line = Vec::with_capacity(row.len() * width);96 for on in row {97 if *on {98 line.extend_from_slice(inner);99 } else {100 line.extend(std::iter::repeat_n(false, width));101 }102 }103 next.push(line);104 }105 }106 out = next;107 }108 out109}110111/// Fills every true cell of a mask laid over a frame.112pub fn carpet(board: &mut Board, frame: Frame, mask: &[Vec<bool>], gap: f64, color: Color) {113 let rows = mask.len();114 let cols = mask.first().map_or(0, |row| row.len());115 if rows == 0 || cols == 0 {116 return;117 }118 Grid::new(frame, cols, rows, gap).carpet(board, mask, color);119}120121#[cfg(test)]122mod tests {123 use super::*;124 #[test]125 fn the_logo_at_level_two_is_twenty_five_wide_and_squares_its_ones() {126 let rows = mask(&LOGO, 2);127 let ones: usize = rows.iter().flatten().filter(|on| **on).count();128 assert_eq!(rows.len(), 25);129 assert_eq!(rows[0].len(), 25);130 assert_eq!(ones, 21 * 21);131 }132 #[test]133 fn at_gap_zero_neighbouring_cells_meet_on_a_whole_pixel() {134 let lattice = Grid::new(Frame::new(3.3, 0.0, 389.12, 389.12), 5, 5, 0.0);135 let (x0, _, w0, _) = lattice.cell(0, 0);136 let (x1, _, _, _) = lattice.cell(1, 0);137 assert_eq!(x0 + w0, x1);138 assert_eq!(x1, x1.round());139 }140}