minkowski.rs
3.9 kB · rust · 98 lines
1use crate::Fault;2use mrlyrs::core::json;3use mrlyrs::math::three::sponge;4use wasm_bindgen::prelude::*;56const WIDEST: usize = 729;7const PERIODS: usize = 12;8const STEPS: usize = 256;910/// Reads the distance from every pixel centre of the plane `z = height` to the Menger sponge, `side` by `side`, row-major.11///12/// Each distance is `mrlyrs::math::three::sponge::distance`, exact: the digits descend to the13/// plus a point sits in, then the twelve edges of its centre cube or the four carpets walling its14/// arm. At the midplane the centre pixel of an odd side reads `sqrt(2)/6`. The side is refused15/// past 729.16#[wasm_bindgen]17pub fn minkowski_slice(height: f64, side: usize) -> Result<Vec<f32>, Fault> {18 if side == 0 || side > WIDEST {19 return Err(Fault::new(format!(20 "the slice runs from 1 to {WIDEST} pixels a side, not {side}."21 )));22 }23 if !(0.0..=1.0).contains(&height) {24 return Err(Fault::new("the plane must cut the unit cube."));25 }26 let scale = side as f64;27 let mut out = Vec::with_capacity(side * side);28 for row in 0..side {29 for col in 0..side {30 let point = [31 (col as f64 + 0.5) / scale,32 (row as f64 + 0.5) / scale,33 height,34 ];35 out.push(sponge::distance(point) as f32);36 }37 }38 Ok(out)39}4041/// Reads the sponge at one radius as JSON: the tube `T` inside the plus, the volume inside the cube, the Minkowski reading and the periodic profile `p`, each `null` where no closed form reaches.42#[wasm_bindgen]43pub fn minkowski_read(radius: f64) -> String {44 json!({45 "radius": radius,46 "tube": sponge::tube(radius),47 "volume": sponge::volume(radius),48 "reading": sponge::reading(radius),49 "profile": sponge::profile(radius),50 })51 .to_string()52}5354/// Walks the Minkowski reading and the profile down `periods` factors of 3 from `sqrt(2)/6`, `steps` radii a period, as JSON.55///56/// The radii run evenly in `u = ln(1/r)`, each at the middle of its step, so none lands on the57/// edge of a window; `reading` and `profile` are `null` on the phases58/// `(1/6, sqrt(2)/6]` of every period, where the tube has no closed form. `low`, `high` and59/// `swing` are the extremes of `p` over the walked phases and their gap in percent of `low`,60/// and `dimension`, `edge` and `cover` are `log(20)/log(3)`, `1/6` and `sqrt(2)/6`.61#[wasm_bindgen]62pub fn minkowski_walk(periods: usize, steps: usize) -> Result<String, Fault> {63 if !(1..=PERIODS).contains(&periods) || !(2..=STEPS).contains(&steps) {64 return Err(Fault::new(format!(65 "the walk takes 1 to {PERIODS} periods of 2 to {STEPS} steps."66 )));67 }68 let start = (1.0 / sponge::COVER).ln();69 let period = 3f64.ln();70 let at = |i: usize| start + period * (i as f64 + 0.5) / steps as f64;71 let once: Vec<Option<f64>> = (0..steps)72 .map(|i| sponge::profile((-at(i)).exp()))73 .collect();74 let edge = sponge::profile(sponge::EDGE);75 let seen: Vec<f64> = once.iter().chain([&edge]).flatten().copied().collect();76 let low = seen.iter().copied().fold(f64::INFINITY, f64::min);77 let high = seen.iter().copied().fold(f64::NEG_INFINITY, f64::max);78 let count = periods * steps;79 let u: Vec<f64> = (0..count).map(at).collect();80 let radius: Vec<f64> = u.iter().map(|v| (-v).exp()).collect();81 let reading: Vec<Option<f64>> = radius.iter().map(|&r| sponge::reading(r)).collect();82 let profile: Vec<Option<f64>> = (0..count).map(|i| once[i % steps]).collect();83 Ok(json!({84 "dimension": sponge::dimension(),85 "edge": sponge::EDGE,86 "cover": sponge::COVER,87 "start": start,88 "period": period,89 "u": u,90 "radius": radius,91 "reading": reading,92 "profile": profile,93 "low": low,94 "high": high,95 "swing": 100.0 * (high - low) / low,96 })97 .to_string())98}