three.rs
5.8 kB · rust · 190 lines
1use crate::space::Pack;2use crate::{code_of, Fault};3use mrlyrs::core::json;4use mrlyrs::math::bang::Code;5use mrlyrs::math::counts;6use mrlyrs::math::three::{self, Cell3d};7use wasm_bindgen::prelude::*;89fn cell(code: &str, number: usize, level: usize, base: usize) -> Result<Cell3d, Fault> {10 Ok(three::create(11 Code::from(code_of(code)?),12 number,13 level,14 base,15 )?)16}1718/// Packs the exposed faces of the cube the code names: two section lengths, then six floats per vertex, position and normal, in the unit box.19#[wasm_bindgen]20pub fn three_faces(21 code: &str,22 number: usize,23 level: usize,24 base: usize,25) -> Result<Vec<f32>, Fault> {26 let mut pack = Pack::new();27 for quad in three::quads(&cell(code, number, level, base)?) {28 pack.quad(quad.verts, quad.normal);29 }30 Ok(pack.buffer())31}3233/// Lists the filled sites of the cube the code names as x, y, z triples.34#[wasm_bindgen]35pub fn three_cells(36 code: &str,37 number: usize,38 level: usize,39 base: usize,40) -> Result<Vec<u32>, Fault> {41 let cell = cell(code, number, level, base)?;42 let grid = cell.types();43 let mut out = Vec::new();44 for (flat, &site) in grid.bytes()?.iter().enumerate() {45 if site != 0 {46 let (i, rest) = (47 flat / (grid.shape[1] * grid.shape[2]),48 flat % (grid.shape[1] * grid.shape[2]),49 );50 out.extend([51 i as u32,52 (rest / grid.shape[2]) as u32,53 (rest % grid.shape[2]) as u32,54 ]);55 }56 }57 Ok(out)58}5960/// Tallies the cube: fills, voids, surface, vertices, edges, faces and Euler number, as JSON.61#[wasm_bindgen]62pub fn three_census(code: &str, number: usize, level: usize, base: usize) -> Result<String, Fault> {63 let tally = three::census(&cell(code, number, level, base)?)?;64 Ok(json!({65 "fills": tally.fills,66 "voids": tally.voids,67 "surface": tally.surface.to_string(),68 "vertices": tally.vertices,69 "edges": tally.edges,70 "faces": tally.faces,71 "euler": tally.euler,72 })73 .to_string())74}7576/// Counts the exposed faces of the cube at the level by exact recurrence, without building it.77#[wasm_bindgen]78pub fn three_surface(code: &str, number: usize, level: u32, base: usize) -> Result<String, Fault> {79 Ok(counts::surface(Code::from(code_of(code)?), number, level, base)?.to_string())80}8182// DIAGONAL8384fn depth(level: usize) -> Result<(), Fault> {85 if !(1..=40).contains(&level) {86 return Err(Fault::new("level must be between 1 and 40."));87 }88 Ok(())89}9091/// Profiles the diagonal cut of the cube: the support, its central pair of heights, the count on every height inside it, the extremes and whether the cut is constant, as JSON.92#[wasm_bindgen]93pub fn diagonal_profile(94 code: &str,95 number: usize,96 level: usize,97 base: usize,98) -> Result<String, Fault> {99 depth(level)?;100 let counts = three::profile(Code::from(code_of(code)?), number, level, base)?;101 let (low, high) = three::support(&counts)102 .ok_or_else(|| Fault::new(format!("code {code} fills no cell, so it has no cut.")))?;103 let span = &counts[low..=high];104 let live: Vec<u128> = span.iter().copied().filter(|&count| count > 0).collect();105 let least = *live.iter().min().unwrap();106 let most = *live.iter().max().unwrap();107 let mid = (low + high) / 2;108 Ok(json!({109 "side": number.pow(level as u32),110 "support": [low, high],111 "central": [mid, high.min(mid + 1)],112 "counts": span.iter().map(|count| count.to_string()).collect::<Vec<String>>(),113 "nonempty": live.len(),114 "heights": span.len(),115 "min": least.to_string(),116 "max": most.to_string(),117 "constant": live.len() == span.len() && least == most,118 })119 .to_string())120}121122/// Counts the filled cells on one diagonal plane of the cube, without building it.123#[wasm_bindgen]124pub fn diagonal_count(125 code: &str,126 number: usize,127 level: usize,128 base: usize,129 height: usize,130) -> Result<String, Fault> {131 depth(level)?;132 let counts = three::profile(Code::from(code_of(code)?), number, level, base)?;133 Ok(counts.get(height).copied().unwrap_or(0).to_string())134}135136/// Spells the height's offset above the cut's support in binary, the digits that say which corners each scale may use.137#[wasm_bindgen]138pub fn diagonal_digits(139 code: &str,140 number: usize,141 level: usize,142 base: usize,143 height: usize,144) -> Result<String, Fault> {145 depth(level)?;146 let counts = three::profile(Code::from(code_of(code)?), number, level, base)?;147 let (low, _) = three::support(&counts)148 .ok_or_else(|| Fault::new(format!("code {code} fills no cell, so it has no cut.")))?;149 Ok(format!("{:b}", height.saturating_sub(low)))150}151152/// Counts the filled cells on the named diagonal planes together, one per circle the drawing holds, as a decimal string.153#[wasm_bindgen]154pub fn diagonal_total(155 code: &str,156 number: usize,157 level: usize,158 base: usize,159 heights: Vec<u32>,160) -> Result<String, Fault> {161 depth(level)?;162 let counts = three::profile(Code::from(code_of(code)?), number, level, base)?;163 let total: u128 = heights164 .iter()165 .map(|&height| counts.get(height as usize).copied().unwrap_or(0))166 .sum();167 Ok(total.to_string())168}169170/// Draws the named diagonal slices of the cube down the `(1,1,1)` axis, one circle per cell, as SVG.171#[wasm_bindgen]172pub fn diagonal_svg(173 code: &str,174 number: usize,175 level: usize,176 base: usize,177 heights: Vec<u32>,178 scale: usize,179) -> Result<String, Fault> {180 depth(level)?;181 let heights: Vec<usize> = heights.iter().map(|&height| height as usize).collect();182 Ok(three::diagonal_svg(183 Code::from(code_of(code)?),184 number,185 level,186 base,187 &heights,188 scale,189 )?)190}