volume.rs

5.1 kB · rust · 156 lines

1use crate::spin::ramp_of;2use crate::{code_of, Fault, Pixels};3use mrlycore::json;4use mrlylab::moire::{self, Combine, Spec, Volume};5use mrlymath::space::Pack;6use mrlymath::three::{self, Cell3d};7use wasm_bindgen::prelude::*;89fn combine_of(name: &str) -> Result<Combine, Fault> {10    match name {11        "sum" => Ok(Combine::Sum),12        "xor" => Ok(Combine::Xor),13        "and" => Ok(Combine::And),14        _ => Err(Fault::new(format!(15            "combine {name:?} is not sum, xor or and."16        ))),17    }18}1920fn odds(limit: usize) -> Vec<usize> {21    (1..=limit.max(1)).step_by(2).collect()22}2324fn volume_of(data: &[f32], size: usize) -> Result<Volume, Fault> {25    Ok(Volume::from_data(data.to_vec(), size)?)26}2728/// Stacks the cube design the code names at the odd side numbers up to the limit into a volume of the side: the samples, x-major.29#[wasm_bindgen]30pub fn volume(31    code: &str,32    base: usize,33    limit: usize,34    combine: &str,35    level: usize,36    size: usize,37) -> Result<Vec<f32>, Fault> {38    let spec = Spec::new(code_of(code)?, base, 3);39    Ok(moire::volume(spec, &odds(limit), combine_of(combine)?, level, size)?.data)40}4142/// Reads a volume: its smallest, largest and mean sample, as JSON.43#[wasm_bindgen]44pub fn volume_stats(data: &[f32], size: usize) -> Result<String, Fault> {45    let v = volume_of(data, size)?;46    let mean = v.data.iter().map(|&x| x as f64).sum::<f64>() / v.data.len().max(1) as f64;47    Ok(json!({ "min": v.min() as f64, "max": v.max() as f64, "mean": mean }).to_string())48}4950/// Counts the voxels at or above the level.51#[wasm_bindgen]52pub fn volume_count(data: &[f32], size: usize, level: f32) -> Result<usize, Fault> {53    Ok(volume_of(data, size)?.count(level))54}5556/// Counts the exposed faces of the voxels at or above the level.57#[wasm_bindgen]58pub fn volume_surface(data: &[f32], size: usize, level: f32) -> Result<usize, Fault> {59    let cell = Cell3d::new(volume_of(data, size)?.solid(level));60    Ok(three::quads(&cell).len())61}6263/// Packs the exposed faces of the voxels at or above the level: two section lengths, then six floats per vertex, position and normal, in the unit box.64#[wasm_bindgen]65pub fn volume_faces(data: &[f32], size: usize, level: f32) -> Result<Vec<f32>, Fault> {66    let cell = Cell3d::new(volume_of(data, size)?.solid(level));67    let mut pack = Pack::new();68    for quad in three::quads(&cell) {69        pack.quad(quad.verts, quad.normal);70    }71    Ok(pack.buffer())72}7374fn normal_of(normal: &[f64]) -> Result<[f64; 3], Fault> {75    match normal {76        [x, y, z] => Ok([*x, *y, *z]),77        _ => Err(Fault::new("the normal needs three components.")),78    }79}8081/// Frames the plane normal to the direction at the offset across the box: its centre, its two axes, its normal and its window width in the unit box, as JSON.82#[wasm_bindgen]83pub fn plane_frame(normal: &[f64], offset: f64) -> Result<String, Fault> {84    let f = moire::frame(normal_of(normal)?, offset)?;85    Ok(json!({86        "centre": f.centre.to_vec(),87        "u": f.u.to_vec(),88        "v": f.v.to_vec(),89        "normal": f.normal.to_vec(),90        "width": f.width,91    })92    .to_string())93}9495/// Samples the section of the volume on the plane normal to the direction at the offset: an out by out field, row by row, NaN outside the cube.96#[wasm_bindgen]97pub fn plane_field(98    data: &[f32],99    size: usize,100    normal: &[f64],101    offset: f64,102    out: usize,103) -> Result<Vec<f32>, Fault> {104    if out == 0 {105        return Err(Fault::new("out must be at least 1."));106    }107    let v = volume_of(data, size)?;108    let frame = moire::frame(normal_of(normal)?, offset)?;109    let (values, inside) = v.plane(&frame, out);110    Ok(values111        .iter()112        .zip(inside.iter())113        .map(|(&value, &hit)| if hit == 0 { f32::NAN } else { value })114        .collect())115}116117/// Paints a square field through the ramp with the values scaled from low to high into the levels, NaN samples transparent.118#[wasm_bindgen]119pub fn paint_span(120    field: &[f32],121    size: usize,122    low: f32,123    high: f32,124    ramp: &str,125    levels: usize,126    invert: bool,127) -> Result<Pixels, Fault> {128    if size == 0 || field.len() != size * size {129        return Err(Fault::new(130            "the field must be size by size with size at least 1.",131        ));132    }133    let colorizer = ramp_of(ramp);134    let levels = levels.max(2);135    let span = (high - low).max(f32::EPSILON);136    let colors = field137        .iter()138        .map(|&value| {139            if value.is_nan() {140                return [0, 0, 0, 0];141            }142            let t = ((value - low) / span).clamp(0.0, 1.0);143            let t = if invert { 1.0 - t } else { t };144            let bucket = ((t * (levels - 1) as f32).round() as usize).min(levels - 1);145            let c = colorizer.color(bucket + 1, levels);146            [c.r, c.g, c.b, 255]147        })148        .collect();149    Ok(Pixels::of(size, size, colors))150}151152/// Shapes a stack: the layers the odd scales up to the limit give and the voxels of a cube of the size, as JSON.153#[wasm_bindgen]154pub fn volume_shape(limit: usize, size: usize) -> String {155    json!({ "layers": odds(limit).len(), "voxels": size * size * size }).to_string()156}