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}