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}