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}