three.rs
5.7 kB · rust · 184 lines
1use crate::{code_of, Fault};2use mrlycore::json;3use mrlymath::formulas;4use mrlymath::space::Pack;5use mrlymath::three::{self, Cell3d};6use wasm_bindgen::prelude::*;78fn cell(code: &str, number: usize, level: usize, base: usize) -> Result<Cell3d, Fault> {9 Ok(three::create(code_of(code)?, number, level, base)?)10}1112/// 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.13#[wasm_bindgen]14pub fn three_faces(15 code: &str,16 number: usize,17 level: usize,18 base: usize,19) -> Result<Vec<f32>, Fault> {20 let mut pack = Pack::new();21 for quad in three::quads(&cell(code, number, level, base)?) {22 pack.quad(quad.verts, quad.normal);23 }24 Ok(pack.buffer())25}2627/// Lists the filled sites of the cube the code names as x, y, z triples.28#[wasm_bindgen]29pub fn three_cells(30 code: &str,31 number: usize,32 level: usize,33 base: usize,34) -> Result<Vec<u32>, Fault> {35 let cell = cell(code, number, level, base)?;36 let grid = cell.types();37 let mut out = Vec::new();38 for (flat, &site) in grid.bytes().iter().enumerate() {39 if site != 0 {40 let (i, rest) = (41 flat / (grid.shape[1] * grid.shape[2]),42 flat % (grid.shape[1] * grid.shape[2]),43 );44 out.extend([45 i as u32,46 (rest / grid.shape[2]) as u32,47 (rest % grid.shape[2]) as u32,48 ]);49 }50 }51 Ok(out)52}5354/// Tallies the cube: fills, voids, surface, vertices, edges, faces and Euler number, as JSON.55#[wasm_bindgen]56pub fn three_census(code: &str, number: usize, level: usize, base: usize) -> Result<String, Fault> {57 let tally = three::census(&cell(code, number, level, base)?)?;58 Ok(json!({59 "fills": tally.fills,60 "voids": tally.voids,61 "surface": tally.surface.to_string(),62 "vertices": tally.vertices,63 "edges": tally.edges,64 "faces": tally.faces,65 "euler": tally.euler,66 })67 .to_string())68}6970/// Counts the exposed faces of the cube at the level by exact recurrence, without building it.71#[wasm_bindgen]72pub fn three_surface(code: &str, number: usize, level: u32, base: usize) -> Result<String, Fault> {73 Ok(formulas::surface(code_of(code)?, number, level, base)?.to_string())74}7576// DIAGONAL7778fn depth(level: usize) -> Result<(), Fault> {79 if !(1..=40).contains(&level) {80 return Err(Fault::new("level must be between 1 and 40."));81 }82 Ok(())83}8485/// 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.86#[wasm_bindgen]87pub fn diagonal_profile(88 code: &str,89 number: usize,90 level: usize,91 base: usize,92) -> Result<String, Fault> {93 depth(level)?;94 let counts = three::profile(code_of(code)?, number, level, base)?;95 let (low, high) = three::support(&counts)96 .ok_or_else(|| Fault::new(format!("code {code} fills no cell, so it has no cut.")))?;97 let span = &counts[low..=high];98 let live: Vec<u128> = span.iter().copied().filter(|&count| count > 0).collect();99 let least = *live.iter().min().unwrap();100 let most = *live.iter().max().unwrap();101 let mid = (low + high) / 2;102 Ok(json!({103 "side": number.pow(level as u32),104 "support": [low, high],105 "central": [mid, high.min(mid + 1)],106 "counts": span.iter().map(|count| count.to_string()).collect::<Vec<String>>(),107 "nonempty": live.len(),108 "heights": span.len(),109 "min": least.to_string(),110 "max": most.to_string(),111 "constant": live.len() == span.len() && least == most,112 })113 .to_string())114}115116/// Counts the filled cells on one diagonal plane of the cube, without building it.117#[wasm_bindgen]118pub fn diagonal_count(119 code: &str,120 number: usize,121 level: usize,122 base: usize,123 height: usize,124) -> Result<String, Fault> {125 depth(level)?;126 let counts = three::profile(code_of(code)?, number, level, base)?;127 Ok(counts.get(height).copied().unwrap_or(0).to_string())128}129130/// Spells the height's offset above the cut's support in binary, the digits that say which corners each scale may use.131#[wasm_bindgen]132pub fn diagonal_digits(133 code: &str,134 number: usize,135 level: usize,136 base: usize,137 height: usize,138) -> Result<String, Fault> {139 depth(level)?;140 let counts = three::profile(code_of(code)?, number, level, base)?;141 let (low, _) = three::support(&counts)142 .ok_or_else(|| Fault::new(format!("code {code} fills no cell, so it has no cut.")))?;143 Ok(format!("{:b}", height.saturating_sub(low)))144}145146/// Counts the filled cells on the named diagonal planes together, one per circle the drawing holds, as a decimal string.147#[wasm_bindgen]148pub fn diagonal_total(149 code: &str,150 number: usize,151 level: usize,152 base: usize,153 heights: Vec<u32>,154) -> Result<String, Fault> {155 depth(level)?;156 let counts = three::profile(code_of(code)?, number, level, base)?;157 let total: u128 = heights158 .iter()159 .map(|&height| counts.get(height as usize).copied().unwrap_or(0))160 .sum();161 Ok(total.to_string())162}163164/// Draws the named diagonal slices of the cube down the `(1,1,1)` axis, one circle per cell, as SVG.165#[wasm_bindgen]166pub fn diagonal_svg(167 code: &str,168 number: usize,169 level: usize,170 base: usize,171 heights: Vec<u32>,172 scale: usize,173) -> Result<String, Fault> {174 depth(level)?;175 let heights: Vec<usize> = heights.iter().map(|&height| height as usize).collect();176 Ok(three::diagonal_svg(177 code_of(code)?,178 number,179 level,180 base,181 &heights,182 scale,183 )?)184}