vtk-pure-rs 0.2.0

Pure Rust visualization toolkit — data structures, filters, I/O, rendering
Documentation
use crate::data::{DataSetAttributes, FieldData, Points};
use crate::types::{BoundingBox, VtkError};

/// An explicit structured grid with blanking support.
///
/// Like `StructuredGrid`, this has a structured i x j x k topology,
/// but additionally supports cell blanking (hiding individual cells)
/// via a visibility array. This is analogous to VTK's `vtkExplicitStructuredGrid`.
///
/// Points are stored explicitly (not implicit like ImageData).
/// Cells can be individually blanked (hidden) for AMR-like hole cutting.
#[derive(Debug, Clone)]
pub struct ExplicitStructuredGrid {
    /// Grid dimensions [ni, nj, nk] (number of points along each axis).
    dimensions: [usize; 3],
    /// Explicit point coordinates.
    pub points: Points<f64>,
    /// Cell blanking: true = visible, false = blanked. Length = (ni-1)*(nj-1)*(nk-1).
    cell_visibility: Vec<bool>,
    /// Point data attributes.
    point_data: DataSetAttributes,
    /// Cell data attributes.
    cell_data: DataSetAttributes,
    /// Field data (metadata).
    field_data: FieldData,
}

impl ExplicitStructuredGrid {
    /// Create a new explicit structured grid with the given dimensions.
    ///
    /// `dims` is [ni, nj, nk] where ni, nj, nk >= 2.
    /// Points must be provided separately.
    pub fn new(dims: [usize; 3]) -> Self {
        let num_cells = (dims[0].saturating_sub(1))
            * (dims[1].saturating_sub(1))
            * (dims[2].saturating_sub(1));
        Self {
            dimensions: dims,
            points: Points::new(),
            cell_visibility: vec![true; num_cells],
            point_data: DataSetAttributes::new(),
            cell_data: DataSetAttributes::new(),
            field_data: FieldData::new(),
        }
    }

    /// Create from dimensions and points.
    pub fn from_points(dims: [usize; 3], points: Points<f64>) -> Result<Self, VtkError> {
        let expected = dims[0] * dims[1] * dims[2];
        if points.len() != expected {
            return Err(VtkError::InvalidData(format!(
                "expected {} points for {:?} grid, got {}",
                expected, dims, points.len()
            )));
        }
        let mut grid = Self::new(dims);
        grid.points = points;
        Ok(grid)
    }

    /// Grid dimensions [ni, nj, nk].
    pub fn dimensions(&self) -> [usize; 3] {
        self.dimensions
    }

    /// Number of points.
    pub fn num_points(&self) -> usize {
        self.dimensions[0] * self.dimensions[1] * self.dimensions[2]
    }

    /// Number of cells (including blanked).
    pub fn num_cells(&self) -> usize {
        (self.dimensions[0].saturating_sub(1))
            * (self.dimensions[1].saturating_sub(1))
            * (self.dimensions[2].saturating_sub(1))
    }

    /// Number of visible (non-blanked) cells.
    pub fn num_visible_cells(&self) -> usize {
        self.cell_visibility.iter().filter(|&&v| v).count()
    }

    /// Blank (hide) a cell by its structured index (i, j, k).
    pub fn blank_cell(&mut self, i: usize, j: usize, k: usize) {
        let idx = self.cell_index(i, j, k);
        if idx < self.cell_visibility.len() {
            self.cell_visibility[idx] = false;
        }
    }

    /// Unblank (show) a cell.
    pub fn unblank_cell(&mut self, i: usize, j: usize, k: usize) {
        let idx = self.cell_index(i, j, k);
        if idx < self.cell_visibility.len() {
            self.cell_visibility[idx] = true;
        }
    }

    /// Check if a cell is visible.
    pub fn is_cell_visible(&self, i: usize, j: usize, k: usize) -> bool {
        let idx = self.cell_index(i, j, k);
        idx < self.cell_visibility.len() && self.cell_visibility[idx]
    }

    /// Get cell visibility array.
    pub fn cell_visibility(&self) -> &[bool] {
        &self.cell_visibility
    }

    /// Set cell visibility array.
    pub fn set_cell_visibility(&mut self, vis: Vec<bool>) {
        self.cell_visibility = vis;
    }

    /// Convert structured cell (i,j,k) to flat cell index.
    pub fn cell_index(&self, i: usize, j: usize, k: usize) -> usize {
        let ni = self.dimensions[0].saturating_sub(1);
        let nj = self.dimensions[1].saturating_sub(1);
        k * ni * nj + j * ni + i
    }

    /// Convert structured point (i,j,k) to flat point index.
    pub fn point_index(&self, i: usize, j: usize, k: usize) -> usize {
        let ni = self.dimensions[0];
        let nj = self.dimensions[1];
        k * ni * nj + j * ni + i
    }

    /// Get the 8 point indices for a hexahedral cell at (i,j,k).
    pub fn cell_point_ids(&self, i: usize, j: usize, k: usize) -> [usize; 8] {
        [
            self.point_index(i, j, k),
            self.point_index(i + 1, j, k),
            self.point_index(i + 1, j + 1, k),
            self.point_index(i, j + 1, k),
            self.point_index(i, j, k + 1),
            self.point_index(i + 1, j, k + 1),
            self.point_index(i + 1, j + 1, k + 1),
            self.point_index(i, j + 1, k + 1),
        ]
    }

    /// Compute axis-aligned bounding box.
    pub fn bounds(&self) -> BoundingBox {
        let mut bb = BoundingBox {
            x_min: f64::INFINITY,
            x_max: f64::NEG_INFINITY,
            y_min: f64::INFINITY,
            y_max: f64::NEG_INFINITY,
            z_min: f64::INFINITY,
            z_max: f64::NEG_INFINITY,
        };
        for i in 0..self.points.len() {
            let p = self.points.get(i);
            bb.x_min = bb.x_min.min(p[0]);
            bb.x_max = bb.x_max.max(p[0]);
            bb.y_min = bb.y_min.min(p[1]);
            bb.y_max = bb.y_max.max(p[1]);
            bb.z_min = bb.z_min.min(p[2]);
            bb.z_max = bb.z_max.max(p[2]);
        }
        bb
    }

    pub fn point_data(&self) -> &DataSetAttributes {
        &self.point_data
    }

    pub fn point_data_mut(&mut self) -> &mut DataSetAttributes {
        &mut self.point_data
    }

    pub fn cell_data(&self) -> &DataSetAttributes {
        &self.cell_data
    }

    pub fn cell_data_mut(&mut self) -> &mut DataSetAttributes {
        &mut self.cell_data
    }

    pub fn field_data(&self) -> &FieldData {
        &self.field_data
    }

    pub fn field_data_mut(&mut self) -> &mut FieldData {
        &mut self.field_data
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_grid_2x2x2() -> ExplicitStructuredGrid {
        let dims = [2, 2, 2];
        let mut pts = Points::new();
        for k in 0..2 {
            for j in 0..2 {
                for i in 0..2 {
                    pts.push([i as f64, j as f64, k as f64]);
                }
            }
        }
        ExplicitStructuredGrid::from_points(dims, pts).unwrap()
    }

    #[test]
    fn basic_grid() {
        let g = make_grid_2x2x2();
        assert_eq!(g.num_points(), 8);
        assert_eq!(g.num_cells(), 1);
        assert_eq!(g.num_visible_cells(), 1);
    }

    #[test]
    fn blanking() {
        let mut g = ExplicitStructuredGrid::new([3, 3, 3]);
        assert_eq!(g.num_cells(), 8);
        assert_eq!(g.num_visible_cells(), 8);
        g.blank_cell(0, 0, 0);
        assert_eq!(g.num_visible_cells(), 7);
        assert!(!g.is_cell_visible(0, 0, 0));
        assert!(g.is_cell_visible(1, 0, 0));
        g.unblank_cell(0, 0, 0);
        assert_eq!(g.num_visible_cells(), 8);
    }

    #[test]
    fn cell_point_ids() {
        let g = ExplicitStructuredGrid::new([3, 3, 2]);
        let ids = g.cell_point_ids(0, 0, 0);
        assert_eq!(ids[0], 0);
        assert_eq!(ids[1], 1);
    }

    #[test]
    fn point_count_mismatch() {
        let pts = Points::new();
        let result = ExplicitStructuredGrid::from_points([3, 3, 3], pts);
        assert!(result.is_err());
    }

    #[test]
    fn bounds() {
        let g = make_grid_2x2x2();
        let bb = g.bounds();
        assert_eq!(bb.x_min, 0.0);
        assert_eq!(bb.x_max, 1.0);
    }
}