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}