tile.rs

6.4 kB · rust · 222 lines

1use mrlycore::errors::{value_error, Result};2use mrlycore::state::randint;3use mrlycore::tensor::Tensor;4use mrlycore::tile::{Design, Group, Source, Tile};56use super::designs;7use super::geometry;8use super::models::Cell3d;9use crate::dim::tile as spec;1011/// The tile configuration in three dimensions.12pub type Config = spec::ConfigNd<3>;1314fn rotation(_source: Source) -> usize {15    randint(0, 23) as usize16}1718/// Draws a tile from the config with random cube orientations.19pub fn create(config: &Config) -> Result<Tile> {20    spec::create(config, rotation)21}2223/// Draws a random tile up to the given size.24pub fn random_tile(max_size: usize) -> Result<Tile> {25    spec::random_tile::<3>(max_size, rotation)26}2728fn design_cell(design: Design, number: usize, level: usize) -> Result<Cell3d> {29    match design {30        Design::Carpet => designs::carpet(number, level),31        Design::Net => designs::net(number, level),32        Design::Xtree => designs::xtree(number, level),33        Design::Ytree => designs::ytree(number, level),34        Design::Ztree => designs::ztree(number, level),35        Design::Void => designs::void(number, level),36        Design::Point => designs::point(number, level),37        Design::Dust => designs::dust(number, level),38        Design::Xline => designs::xline(number, level),39        Design::Yline => designs::yline(number, level),40        Design::Zline => designs::zline(number, level),41        Design::Star => designs::star(number, level),42        other => value_error(format!("design {} is not 3d.", other.name())),43    }44}4546fn source_cell(source: Source, number: usize, level: usize, rotation: usize) -> Result<Cell3d> {47    let mut c = match source {48        Source::Classic(design) => design_cell(design, number, level)?,49        Source::Code(code) => designs::create(code, number, level, 2)?,50    };51    if rotation != 0 {52        c = c.orient(rotation)?;53    }54    Ok(c)55}5657fn cell(tile: &Tile, i: usize, level: usize) -> Result<Cell3d> {58    let mut c = source_cell(tile.sources[i], tile.numbers[i], level, tile.rotations[i])?;59    if tile.anti.get(i).copied().unwrap_or(false) {60        c = c.anti();61    }62    Ok(c)63}6465fn orient_mask(n: usize, fill: u8) -> Result<Tensor> {66    let line = designs::xtree(n, 1)?;67    let t = line.types();68    let data: Vec<u8> = t69        .bytes()70        .iter()71        .map(|&v| if v == 1 { fill } else { 0 })72        .collect();73    Ok(Tensor::of(data, t.shape.clone()))74}7576fn index_mask(n: usize) -> Result<Tensor> {77    let x = designs::xtree(n, 1)?;78    let y = designs::ytree(n, 1)?;79    let z = designs::ztree(n, 1)?;80    let (xt, yt, zt) = (x.types(), y.types(), z.types());81    let mut data = vec![0u8; xt.size()];82    for (flat, item) in data.iter_mut().enumerate() {83        *item = if zt.bytes()[flat] == 1 {84            285        } else if yt.bytes()[flat] == 1 {86            187        } else {88            089        };90    }91    Ok(Tensor::of(data, xt.shape.clone()))92}9394fn build_general(tile: &Tile) -> Result<Cell3d> {95    cell(tile, 0, 1)96}9798fn build_fractal(tile: &Tile) -> Result<Cell3d> {99    cell(tile, 0, tile.levels[0])100}101102fn build_magic(tile: &Tile) -> Result<Cell3d> {103    let cells: Result<Vec<Cell3d>> = (0..tile.sources.len()).map(|i| cell(tile, i, 1)).collect();104    geometry::magic(&cells?)105}106107fn build_special(tile: &Tile) -> Result<Cell3d> {108    let cell = designs::xtree(tile.numbers[0], 1)?;109    let fill = if tile.flip {110        0111    } else {112        tile.rotations[0].max(1) as u8113    };114    let mask = orient_mask(tile.factor, fill)?;115    geometry::special(&mask, &cell)116}117118fn build_mosaic(tile: &Tile) -> Result<Cell3d> {119    let mask = index_mask(tile.factor)?;120    let cells: Result<Vec<Cell3d>> = (0..3).map(|i| cell(tile, i, 1)).collect();121    geometry::mosaic(&mask, &cells?)122}123124fn builder(group: Group) -> fn(&Tile) -> Result<Cell3d> {125    match group {126        Group::General => build_general,127        Group::Fractal => build_fractal,128        Group::Magic => build_magic,129        Group::Special => build_special,130        Group::Mosaic => build_mosaic,131    }132}133134/// Builds a tile's cube through its group's builder, inverting on request.135pub fn build(tile: &Tile) -> Result<Cell3d> {136    let mut c = builder(tile.group)(tile)?;137    if tile.invert {138        c = c.invert();139    }140    Ok(c)141}142143#[cfg(test)]144mod tests {145    use super::*;146    use mrlycore::state::{guard as rng_lock, seed};147    use mrlycore::tile::Catalog;148    fn config() -> Config {149        Config {150            min_size: 3,151            max_size: 27,152            anti: Some(false),153            ..Config::default()154        }155    }156    #[test]157    fn built_size_matches_unit_size() {158        let _guard = rng_lock();159        let config = config();160        for s in 0..200 {161            seed(s);162            let tile = create(&config).unwrap();163            let cell = build(&tile).unwrap();164            assert_eq!(165                cell.width(),166                tile.width,167                "width seed {} {:?}",168                s,169                tile.group170            );171            assert_eq!(172                cell.height(),173                tile.height,174                "height seed {} {:?}",175                s,176                tile.group177            );178            assert_eq!(cell.depth(), tile.width, "depth cubic seed {}", s);179        }180    }181    #[test]182    fn create_is_seeded() {183        let _guard = rng_lock();184        seed(321);185        let a = create(&config()).unwrap();186        seed(321);187        let b = create(&config()).unwrap();188        assert_eq!(a, b);189    }190    #[test]191    fn classics_use_named_designs() {192        let _guard = rng_lock();193        let config = config();194        for s in 0..50 {195            seed(s);196            let tile = create(&config).unwrap();197            for source in &tile.sources {198                assert!(matches!(source, Source::Classic(_)));199            }200        }201    }202    #[test]203    fn universe_builds_from_codes() {204        let _guard = rng_lock();205        let config = Config {206            catalog: Catalog::Universe,207            min_size: 3,208            max_size: 9,209            anti: Some(false),210            ..Config::default()211        };212        for s in 0..60 {213            seed(s);214            let tile = create(&config).unwrap();215            let cell = build(&tile).unwrap();216            assert_eq!(cell.width(), tile.width, "universe width seed {}", s);217            for source in &tile.sources {218                assert!(matches!(source, Source::Code(_)));219            }220        }221    }222}