use std::path::Path;
use brodo::field::{AnyField, Field};
use brodo::meta::{Bounds, SDF_KEY};
use brodo::slice::{Axis, ColorMap, Image, Slice, render};
use crate::error::MestoloError;
use crate::info::{Parts, extremes};
use crate::load::Grid;
pub const MOST_PIXELS: i64 = 1 << 26;
pub fn slice(g: &Grid, axis: Axis, at: i32, path: &Path) -> Result<(), MestoloError> {
let image = match &g.field {
AnyField::F32(f) => draw(g, f, axis, at)?,
AnyField::F64(f) => draw(g, f, axis, at)?,
AnyField::U8(f) => draw(g, f, axis, at)?,
AnyField::U32(f) => draw(g, f, axis, at)?,
AnyField::F32x3(_) | AnyField::F64x3(_) => {
return Err(MestoloError::NotScalar(g.field.value_type()));
}
_ => return Err(MestoloError::ValueType(g.field.value_type())),
};
Ok(brodo::slice::write(path, &image)?)
}
fn draw<T>(g: &Grid, f: &Field<T>, axis: Axis, at: i32) -> Result<Image, MestoloError>
where
T: Parts + Into<f64>,
T::Part: Into<f64>,
{
let Bounds::Box { min: [x0, y0, z0], max: [x1, y1, z1] } = Bounds::of(&g.topology)? else {
return Err(MestoloError::EmptyGrid(g.meta.name.clone()));
};
let (min, max, span) = match axis {
Axis::X => ([y0, z0], [y1, z1], (x0, x1)),
Axis::Y => ([x0, z0], [x1, z1], (y0, y1)),
Axis::Z => ([x0, y0], [x1, y1], (z0, z1)),
};
if at < span.0 || at > span.1 {
let grid = g.meta.name.clone();
return Err(MestoloError::OutsideGrid { grid, axis, at, min: span.0, max: span.1 });
}
size(min, max)?;
Ok(render(&g.topology, f, Slice::new(axis, at, min, max), colors(g, f)?)?)
}
fn size(min: [i32; 2], max: [i32; 2]) -> Result<(), MestoloError> {
let [[u0, v0], [u1, v1]] = [min, max];
let width = i64::from(u1) - i64::from(u0) + 1;
let height = i64::from(v1) - i64::from(v0) + 1;
if width.saturating_mul(height) > MOST_PIXELS {
return Err(MestoloError::ImageTooLarge { width, height, most: MOST_PIXELS });
}
Ok(())
}
fn colors<T>(g: &Grid, f: &Field<T>) -> Result<ColorMap, MestoloError>
where
T: Parts + Into<f64>,
T::Part: Into<f64>,
{
let extra = &g.meta.extra;
let level_set =
extra.contains_key(SDF_KEY) || extra.get("class").is_some_and(|c| c == "level set");
let (least, most) = extremes(&g.topology, f)?;
let first = |xs: Vec<Option<T::Part>>| xs.into_iter().flatten().next().map(Into::into);
Ok(color_map(level_set, f.background().into(), first(least), first(most)))
}
fn color_map(level_set: bool, background: f64, least: Option<f64>, most: Option<f64>) -> ColorMap {
let band = background.abs();
if level_set && band.is_finite() && band > 0.0 {
return ColorMap::Diverging { limit: band };
}
let (lo, hi) = (least.unwrap_or(0.0), most.unwrap_or(0.0));
if lo < hi {
ColorMap::Sequential { min: lo, max: hi }
} else {
ColorMap::Sequential { min: lo - 0.5, max: lo + 0.5 }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_slice_of_the_most_pixels_is_drawn_and_one_more_row_refused() {
assert!(size([-4096, -4096], [4095, 4095]).is_ok());
assert!(matches!(
size([-4096, -4096], [4095, 4096]),
Err(MestoloError::ImageTooLarge { width: 8192, height: 8193, most: MOST_PIXELS })
));
assert!(matches!(
size([0, -8192], [8191, 0]),
Err(MestoloError::ImageTooLarge { width: 8192, height: 8193, .. })
));
}
#[test]
fn a_level_set_diverges_at_its_band_and_another_grid_runs_over_its_values() {
let seq = |min, max| ColorMap::Sequential { min, max };
assert_eq!(
color_map(true, -3.0, Some(-3.0), Some(3.0)),
ColorMap::Diverging { limit: 3.0 }
);
assert_eq!(color_map(false, 3.0, Some(-1.0), Some(2.0)), seq(-1.0, 2.0));
assert_eq!(color_map(true, 0.0, Some(-1.0), Some(2.0)), seq(-1.0, 2.0));
assert_eq!(color_map(true, f64::INFINITY, Some(-1.0), Some(2.0)), seq(-1.0, 2.0));
assert_eq!(color_map(false, 0.0, Some(4.0), Some(4.0)), seq(3.5, 4.5));
assert_eq!(color_map(false, 0.0, None, None), seq(-0.5, 0.5));
}
}