tile.rs
10.8 kB · rust · 362 lines
1#![allow(clippy::too_many_arguments)]23use crate::{code_of, Fault, Grid};4use mrlyrs::core::json;5use mrlyrs::math::bang::Code;6use mrlyrs::math::six::{self, Cell6d};7use mrlyrs::math::three::{self, Cell3d};8use mrlyrs::math::two::{self, Cell2d};9use wasm_bindgen::prelude::*;1011const PLANE_CELLS: usize = 262_144;12const SOLID_CELLS: usize = 1_000_000;13const SOLID_FILLS: usize = 150_000;14const HEX_TRIANGLES: usize = 200_000;15const HEX_SIDE: usize = 81;16const WALK_CELLS: usize = 150_000;17const WALK_TRIANGLES: usize = 80_000;1819fn rep(value: u32) -> Result<usize, Fault> {20 match (1..=32).contains(&value) {21 true => Ok(value as usize),22 false => Err(Fault::new(23 "a repetition count runs from one to thirty two.",24 )),25 }26}2728fn reps_of(reps: &[u32], axes: usize) -> Result<Vec<usize>, Fault> {29 if reps.len() != axes {30 return Err(Fault::new(format!(31 "a {axes}-axis tiling wants {axes} repetition counts."32 )));33 }34 reps.iter().map(|&value| rep(value)).collect()35}3637fn budget(count: usize, limit: usize, what: &str) -> Result<(), Fault> {38 match count > limit {39 true => Err(Fault::new(format!(40 "{count} {what} is more than this page draws; lower the level or the repeats."41 ))),42 false => Ok(()),43 }44}4546fn side_of(number: usize, level: usize) -> Result<usize, Fault> {47 number48 .checked_pow(level as u32)49 .ok_or_else(|| Fault::new("that side is past what a page draws."))50}5152// PLANE5354fn plane(55 code: &str,56 number: usize,57 level: usize,58 base: usize,59 reps: &[usize],60) -> Result<(Cell2d, Cell2d), Fault> {61 let cell = two::create(Code::from(code_of(code)?), number, level, 0, base)?;62 budget(63 cell.width() * reps[0] * cell.height() * reps[1],64 PLANE_CELLS,65 "cells",66 )?;67 let sheet = cell.clone().tile(reps[0], reps[1])?;68 Ok((cell, sheet))69}7071/// Builds the flat design the code names repeated into a wide-by-high array of copies, as a byte grid.72#[wasm_bindgen]73pub fn tile_grid(74 code: &str,75 number: usize,76 level: usize,77 base: usize,78 wide: u32,79 high: u32,80) -> Result<Grid, Fault> {81 let reps = reps_of(&[wide, high], 2)?;82 let (_, sheet) = plane(code, number, level, base, &reps)?;83 Ok(Grid {84 width: sheet.width() as u32,85 height: sheet.height() as u32,86 types: sheet.types().bytes()?.to_vec(),87 })88}8990// SOLID9192fn solid(93 code: &str,94 number: usize,95 level: usize,96 base: usize,97 reps: &[usize],98) -> Result<(Cell3d, Cell3d), Fault> {99 let cell = three::create(Code::from(code_of(code)?), number, level, base)?;100 budget(101 cell.width() * reps[0] * cell.height() * reps[1] * cell.depth() * reps[2],102 SOLID_CELLS,103 "cells",104 )?;105 let sheet = cell.clone().tile(reps[0], reps[1], reps[2])?;106 Ok((cell, sheet))107}108109/// Lists the filled sites of the cube design repeated into a wide-by-high-by-deep array of copies, as x, y, z triples.110#[wasm_bindgen]111pub fn tile_cells(112 code: &str,113 number: usize,114 level: usize,115 base: usize,116 wide: u32,117 high: u32,118 deep: u32,119) -> Result<Vec<u32>, Fault> {120 let reps = reps_of(&[wide, high, deep], 3)?;121 let (_, sheet) = solid(code, number, level, base, &reps)?;122 let grid = sheet.types();123 let (cols, deep) = (grid.shape[1], grid.shape[2]);124 budget(three::fills(&sheet), SOLID_FILLS, "cubes")?;125 let mut out = Vec::new();126 for (flat, &site) in grid.bytes()?.iter().enumerate() {127 if site != 0 {128 out.extend([129 (flat / (cols * deep)) as u32,130 (flat / deep % cols) as u32,131 (flat % deep) as u32,132 ]);133 }134 }135 Ok(out)136}137138// HEXAGON139140fn hexagon(141 code: &str,142 number: usize,143 level: usize,144 base: usize,145 projection: &str,146) -> Result<Cell6d, Fault> {147 let code = code_of(code)?;148 budget(side_of(number, level)?, HEX_SIDE, "cells to a side")?;149 Ok(match projection {150 "pro" => six::pro_design(Code::from(code), number, level, base)?,151 "cut" => six::cut_design(Code::from(code), number, level, base)?,152 _ => six::iso_design(Code::from(code), number, level, base)?,153 })154}155156fn hex_sheet(157 code: &str,158 number: usize,159 level: usize,160 base: usize,161 projection: &str,162 reps: &[usize],163 crop: bool,164) -> Result<(Cell6d, Cell6d), Fault> {165 let hex = hexagon(code, number, level, base, projection)?;166 budget(167 hex.width() * reps[0] * hex.height() * reps[1],168 HEX_TRIANGLES,169 "triangles",170 )?;171 if crop && (reps[0] < 2 || reps[1] < 2) {172 return Err(Fault::new(173 "the interlocking crop eats a sheet under two copies on an axis.",174 ));175 }176 let sheet = six::tile_cell(&hex, reps[0], reps[1], crop)?;177 if six::census(&six::skin(&sheet), false).triangles == 0 {178 return Err(Fault::new("that crop leaves no triangle to draw."));179 }180 Ok((hex, sheet))181}182183/// Renders the hexagonal projection of the design tessellated into an interlocking sheet of copies, as SVG.184#[wasm_bindgen]185pub fn tile_svg(186 code: &str,187 number: usize,188 level: usize,189 base: usize,190 projection: &str,191 wide: u32,192 high: u32,193 crop: bool,194 scale: usize,195) -> Result<String, Fault> {196 let reps = reps_of(&[wide, high], 2)?;197 let (_, sheet) = hex_sheet(code, number, level, base, projection, &reps, crop)?;198 Ok(six::svg(&six::framed(&sheet)?, scale, None, 0)?)199}200201// CENSUS202203fn readings(204 fills: u128,205 voids: u128,206 exposed: u128,207 tile_fills: u128,208 tile_exposed: u128,209 copies: u128,210) -> mrlyrs::core::Json {211 json!({212 "copies": copies.to_string(),213 "fills": fills.to_string(),214 "voids": voids.to_string(),215 "exposed": exposed.to_string(),216 "tile_fills": tile_fills.to_string(),217 "tile_exposed": tile_exposed.to_string(),218 "buried": (copies * tile_exposed - exposed).to_string(),219 "ratio": fills as f64 / (fills + voids).max(1) as f64,220 })221}222223fn plane_census(224 code: &str,225 number: usize,226 level: usize,227 base: usize,228 reps: &[usize],229) -> Result<mrlyrs::core::Json, Fault> {230 let (cell, sheet) = plane(code, number, level, base, reps)?;231 let copies = (reps[0] * reps[1]) as u128;232 let fills = two::census::fills(&sheet) as u128;233 let voids = two::census::voids(&sheet) as u128;234 let exposed = two::census::perimeter(&sheet);235 let mut out = readings(236 fills,237 voids,238 exposed,239 two::census::fills(&cell) as u128,240 two::census::perimeter(&cell),241 copies,242 );243 out["tile"] = json!([cell.width(), cell.height()]);244 out["sheet"] = json!([sheet.width(), sheet.height()]);245 out["cells"] = json!((sheet.width() * sheet.height()).to_string());246 if sheet.width() * sheet.height() <= WALK_CELLS {247 let tally = two::census::census(&sheet)?;248 out["vertices"] = json!(tally.vertices);249 out["edges"] = json!(tally.edges);250 out["euler"] = json!(tally.euler);251 out["walked"] = json!(true);252 }253 Ok(out)254}255256fn solid_census(257 code: &str,258 number: usize,259 level: usize,260 base: usize,261 reps: &[usize],262) -> Result<mrlyrs::core::Json, Fault> {263 let (cell, sheet) = solid(code, number, level, base, reps)?;264 let copies = (reps[0] * reps[1] * reps[2]) as u128;265 let fills = three::census::fills(&sheet) as u128;266 let voids = three::census::voids(&sheet) as u128;267 let exposed = three::census::surface(&sheet);268 let mut out = readings(269 fills,270 voids,271 exposed,272 three::census::fills(&cell) as u128,273 three::census::surface(&cell),274 copies,275 );276 out["tile"] = json!([cell.width(), cell.height(), cell.depth()]);277 out["sheet"] = json!([sheet.width(), sheet.height(), sheet.depth()]);278 out["cells"] = json!((sheet.width() * sheet.height() * sheet.depth()).to_string());279 if sheet.width() * sheet.height() * sheet.depth() <= WALK_CELLS {280 let tally = three::census::census(&sheet)?;281 out["vertices"] = json!(tally.vertices);282 out["edges"] = json!(tally.edges);283 out["faces"] = json!(tally.faces);284 out["euler"] = json!(tally.euler);285 out["walked"] = json!(true);286 }287 Ok(out)288}289290fn hex_census(291 code: &str,292 number: usize,293 level: usize,294 base: usize,295 projection: &str,296 reps: &[usize],297 crop: bool,298) -> Result<mrlyrs::core::Json, Fault> {299 let (hex, sheet) = hex_sheet(code, number, level, base, projection, reps, crop)?;300 let (hex, sheet) = (six::skin(&hex), six::skin(&sheet));301 let one = six::census(&hex, false);302 let tally = six::census(&sheet, false);303 let rim = six::census::fills_only(&sheet);304 let mut out = readings(305 tally.fills as u128,306 tally.voids as u128,307 rim.boundary_edges as u128,308 one.fills as u128,309 six::census::fills_only(&hex).boundary_edges as u128,310 (reps[0] * reps[1]) as u128,311 );312 out["tile"] = json!([hex.width(), hex.height()]);313 out["sheet"] = json!([sheet.width(), sheet.height()]);314 out["cells"] = json!(tally.triangles.to_string());315 out["triangles"] = json!(tally.triangles);316 out["boundary"] = json!(tally.boundary_edges);317 out["projection"] = json!(projection);318 if tally.triangles <= WALK_TRIANGLES {319 out["vertices"] = json!(tally.vertices);320 out["edges"] = json!(tally.edges);321 out["euler"] = json!(tally.euler);322 out["walked"] = json!(true);323 }324 Ok(out)325}326327/// Tallies a tiled design in the plane, the cube or the hexagon: the tile and sheet shapes, the copies, the fills, the voids, the exposed faces of the sheet and of one copy, and the faces the tiling buries, as JSON.328///329/// The exposed count is the perimeter in the plane, the surface in the cube and the boundary edges330/// of the filled sub-mesh on the hexagon. Corners, edges and the Euler number ride along under the331/// walk budget, flagged by `walked`.332#[wasm_bindgen]333pub fn tile_census(334 code: &str,335 number: usize,336 level: usize,337 base: usize,338 dimension: usize,339 projection: &str,340 reps: Vec<u32>,341 crop: bool,342) -> Result<String, Fault> {343 let mut out = match dimension {344 2 => plane_census(code, number, level, base, &reps_of(&reps, 2)?)?,345 3 => solid_census(code, number, level, base, &reps_of(&reps, 3)?)?,346 6 => hex_census(347 code,348 number,349 level,350 base,351 projection,352 &reps_of(&reps, 2)?,353 crop,354 )?,355 _ => return Err(Fault::new("a design tiles in dimension 2, 3 or 6.")),356 };357 out["dimension"] = json!(dimension);358 out["side"] = json!(side_of(number, level)?);359 out["reps"] = json!(reps.iter().map(|&r| r as usize).collect::<Vec<usize>>());360 out["crop"] = json!(crop);361 Ok(out.to_string())362}