geometry.rs

5.9 kB · rust · 171 lines

1use super::models::Cell2d;2use crate::three::Cell3d;3use mrlycore::cell::{remap, Cell};4use mrlycore::errors::{value_error, Result};5use mrlycore::tensor::{Dtype, Tensor};67pub use crate::dim::geometry::{magic, mosaic, perforate};89fn widest(cells: &[Cell2d]) -> Option<Dtype> {10    cells11        .iter()12        .filter_map(|c| c.cell.tags.as_ref())13        .map(|t| t.dtype())14        .max_by_key(|d| d.max())15}1617fn stacked(cells: &[Cell2d]) -> Cell {18    let inner = cells[0].types().size();19    let shape = vec![cells.len() * inner];20    let mut types = Tensor::typed(shape.clone(), cells[0].types().dtype());21    let mut colors = vec![[0u8; 4]; cells.len() * inner];22    let mut tags = widest(cells).map(|dtype| Tensor::typed(shape, dtype));23    for (block, cell) in cells.iter().enumerate() {24        let base = block * inner;25        for local in 0..inner {26            types.put(base + local, cell.types().at(local));27            if let Some(source) = &cell.cell.colors {28                colors[base + local] = source[local];29            }30            if let (Some(layer), Some(source)) = (tags.as_mut(), &cell.cell.tags) {31                layer.put(base + local, source.at(local));32            }33        }34    }35    Cell {36        types,37        colors: cells38            .iter()39            .any(|c| c.cell.colors.is_some())40            .then_some(colors),41        tags,42    }43}4445/// Merges same-shaped cells into one block of the given width and height in cells, colors and tags kept.46pub fn merge(cells: &[Cell2d], width: usize, height: usize) -> Result<Cell2d> {47    if cells.is_empty() {48        return value_error("Cannot merge an empty list of cells.");49    }50    let count = width * height;51    if cells.len() != count {52        return value_error(format!("Expected {count} cells, got {}", cells.len()));53    }54    let (inner_w, inner_h) = (cells[0].width(), cells[0].height());55    if cells56        .iter()57        .any(|c| c.types().shape != cells[0].types().shape)58    {59        return value_error("All cells in a merge operation must have the same dimensions.");60    }61    let shape = vec![inner_h * height, inner_w * width];62    let inner = inner_w * inner_h;63    let mut map = Vec::with_capacity(shape[0] * shape[1]);64    for y in 0..shape[0] {65        for x in 0..shape[1] {66            let block = (y / inner_h) * width + x / inner_w;67            map.push(block * inner + (y % inner_h) * inner_w + x % inner_w);68        }69    }70    Ok(Cell2d {71        cell: remap(&stacked(cells), &map, &shape),72    })73}7475/// Tiles quarter-turned copies of the cell as the 2d mask directs, or an error at a rotation past 3.76pub fn special(mask: &Tensor, cell: &Cell2d) -> Result<Cell2d> {77    if mask.shape.len() != 2 {78        return value_error("special mask must be 2d.");79    }80    if mask.bytes().iter().any(|&v| v > 3) {81        return value_error("Invalid rotation value. Must be 0, 1, 2, or 3.");82    }83    let rotated: Vec<Cell2d> = mask84        .bytes()85        .iter()86        .map(|&k| cell.clone().rotate(k as usize))87        .collect();88    merge(&rotated, mask.shape[1], mask.shape[0])89}9091/// Lifts the flat cell into a cube one site deep, colors and tags with it.92///93/// ```94/// let flat = mrlymath::two::carpet(3, 2).unwrap();95/// let solid = mrlymath::two::to_3d(&flat);96/// assert_eq!(mrlymath::three::slice(&solid, 2, 0).unwrap(), flat);97/// ```98pub fn to_3d(cell: &Cell2d) -> Cell3d {99    crate::three::extrude(cell, 2, 1).expect("a one-deep lift on the last axis always holds")100}101102#[cfg(test)]103mod tests {104    use super::*;105    use crate::two::designs;106    use mrlycore::cell::mapping;107    use mrlycore::enums::Mode;108    #[test]109    fn special_rotations_preserve_sum() {110        let tree = designs::htree(3, 1).unwrap();111        let mask = Tensor::of(vec![0, 1, 3, 2], vec![2, 2]);112        let s = special(&mask, &tree).unwrap();113        assert_eq!(s.width(), 6);114        assert_eq!(s.types().sum(), 4 * tree.types().sum());115        assert!(special(&Tensor::of(vec![4], vec![1, 1]), &tree).is_err());116    }117    #[test]118    fn special_identity_mask_is_tile() {119        let c = designs::carpet(3, 1).unwrap();120        let mask = Tensor::new(vec![2, 3]);121        let s = special(&mask, &c).unwrap();122        assert_eq!(s, c.clone().tile(3, 2));123    }124    #[test]125    fn merge_carries_colors_and_tags() {126        let painted = designs::carpet(3, 1)127            .unwrap()128            .layers()129            .paint(&mapping(), Mode::Type);130        let plain = designs::void(3, 1).unwrap();131        let block = merge(132            &[painted.clone(), plain, painted.clone(), painted.clone()],133            2,134            2,135        )136        .unwrap();137        assert_eq!(block.width(), 6);138        let colors = block.cell.colors.as_ref().unwrap();139        let source = painted.cell.colors.as_ref().unwrap();140        assert_eq!(colors.len(), 36);141        assert_eq!(colors[0], source[0]);142        assert_eq!(colors[3], [0, 0, 0, 0]);143        assert_eq!(colors[18], source[0]);144        assert_eq!(colors[21], source[0]);145        let tags = block.cell.tags.as_ref().unwrap();146        let seed = painted.cell.tags.as_ref().unwrap();147        assert_eq!(tags.shape, vec![6, 6]);148        assert_eq!(tags.at(7), seed.at(4));149        assert_eq!(tags.at(10), 0);150    }151    #[test]152    fn merge_without_paint_stays_bare() {153        let c = designs::carpet(3, 1).unwrap();154        let block = merge(&[c.clone(), c.clone(), c.clone(), c], 2, 2).unwrap();155        assert!(block.cell.colors.is_none());156        assert!(block.cell.tags.is_none());157        assert!(merge(&[], 1, 1).is_err());158    }159    #[test]160    fn to_3d_is_a_one_deep_lift() {161        let flat = designs::htree(5, 1)162            .unwrap()163            .layers()164            .paint(&mapping(), Mode::Index);165        let solid = to_3d(&flat);166        assert_eq!(solid.types().shape, vec![5, 5, 1]);167        assert_eq!(solid.types().sum(), flat.types().sum());168        assert_eq!(solid.cell.colors, flat.cell.colors);169        assert_eq!(crate::three::slice(&solid, 2, 0).unwrap(), flat);170    }171}