Skip to main content

vtk_pure_rs/data/
structured_grid.rs

1use crate::types::BoundingBox;
2
3use crate::data::{DataSetAttributes, FieldData, Points};
4use crate::data::traits::{DataObject, DataSet};
5
6/// Curvilinear grid with explicit point coordinates.
7///
8/// Analogous to VTK's `vtkStructuredGrid`. Points are stored explicitly
9/// (unlike ImageData/RectilinearGrid), but the topology is implicitly
10/// structured as an i×j×k grid.
11#[derive(Debug, Clone)]
12pub struct StructuredGrid {
13    dimensions: [usize; 3],
14    pub points: Points<f64>,
15    point_data: DataSetAttributes,
16    cell_data: DataSetAttributes,
17    field_data: FieldData,
18}
19
20impl Default for StructuredGrid {
21    fn default() -> Self {
22        Self {
23            dimensions: [1, 1, 1],
24            points: Points::new(),
25            point_data: DataSetAttributes::new(),
26            cell_data: DataSetAttributes::new(),
27            field_data: FieldData::new(),
28        }
29    }
30}
31
32impl StructuredGrid {
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Create a StructuredGrid with given dimensions and points.
38    /// The number of points must equal `nx * ny * nz`.
39    pub fn from_dimensions_and_points(
40        dimensions: [usize; 3],
41        points: Points<f64>,
42    ) -> Self {
43        assert_eq!(
44            points.len(),
45            dimensions[0] * dimensions[1] * dimensions[2],
46            "points count must match dimensions product"
47        );
48        Self {
49            dimensions,
50            points,
51            ..Default::default()
52        }
53    }
54
55    pub fn dimensions(&self) -> [usize; 3] {
56        self.dimensions
57    }
58
59    pub fn set_dimensions(&mut self, dims: [usize; 3]) {
60        self.dimensions = dims;
61    }
62
63    /// Convert a flat point index to (i, j, k).
64    pub fn ijk_from_index(&self, idx: usize) -> (usize, usize, usize) {
65        let k = idx / (self.dimensions[0] * self.dimensions[1]);
66        let remainder = idx % (self.dimensions[0] * self.dimensions[1]);
67        let j = remainder / self.dimensions[0];
68        let i = remainder % self.dimensions[0];
69        (i, j, k)
70    }
71
72    /// Convert (i, j, k) to a flat point index.
73    pub fn index_from_ijk(&self, i: usize, j: usize, k: usize) -> usize {
74        k * self.dimensions[0] * self.dimensions[1] + j * self.dimensions[0] + i
75    }
76
77    pub fn point_data(&self) -> &DataSetAttributes {
78        &self.point_data
79    }
80
81    pub fn point_data_mut(&mut self) -> &mut DataSetAttributes {
82        &mut self.point_data
83    }
84
85    pub fn cell_data(&self) -> &DataSetAttributes {
86        &self.cell_data
87    }
88
89    pub fn cell_data_mut(&mut self) -> &mut DataSetAttributes {
90        &mut self.cell_data
91    }
92
93    /// Create a structured grid from a function that maps (i,j,k) to position.
94    pub fn from_function(
95        dims: [usize; 3],
96        f: impl Fn(usize, usize, usize) -> [f64; 3],
97    ) -> Self {
98        let mut pts = Points::new();
99        for k in 0..dims[2] {
100            for j in 0..dims[1] {
101                for i in 0..dims[0] {
102                    pts.push(f(i, j, k));
103                }
104            }
105        }
106        Self::from_dimensions_and_points(dims, pts)
107    }
108
109    /// Create a uniform structured grid (same as ImageData geometry but explicit).
110    pub fn uniform(dims: [usize; 3], spacing: [f64; 3], origin: [f64; 3]) -> Self {
111        Self::from_function(dims, |i, j, k| [
112            origin[0] + i as f64 * spacing[0],
113            origin[1] + j as f64 * spacing[1],
114            origin[2] + k as f64 * spacing[2],
115        ])
116    }
117
118    /// Builder: add point data.
119    pub fn with_point_array(mut self, array: crate::data::AnyDataArray) -> Self {
120        let name = array.name().to_string();
121        self.point_data.add_array(array);
122        if self.point_data.scalars().is_none() {
123            self.point_data.set_active_scalars(&name);
124        }
125        self
126    }
127}
128
129impl std::fmt::Display for StructuredGrid {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        let d = self.dimensions;
132        write!(f, "StructuredGrid: {}x{}x{}, {} points, {} point arrays",
133            d[0], d[1], d[2], self.points.len(), self.point_data.num_arrays())
134    }
135}
136
137impl DataObject for StructuredGrid {
138    fn field_data(&self) -> &FieldData {
139        &self.field_data
140    }
141
142    fn field_data_mut(&mut self) -> &mut FieldData {
143        &mut self.field_data
144    }
145}
146
147impl DataSet for StructuredGrid {
148    fn num_points(&self) -> usize {
149        self.dimensions[0] * self.dimensions[1] * self.dimensions[2]
150    }
151
152    fn num_cells(&self) -> usize {
153        let d = self.dimensions;
154        let cx = d[0].saturating_sub(1);
155        let cy = d[1].saturating_sub(1);
156        let cz = d[2].saturating_sub(1);
157        cx.max(1) * cy.max(1) * cz.max(1)
158    }
159
160    fn point(&self, idx: usize) -> [f64; 3] {
161        self.points.get(idx)
162    }
163
164    fn bounds(&self) -> BoundingBox {
165        self.points.bounds()
166    }
167
168    fn point_data(&self) -> &DataSetAttributes {
169        &self.point_data
170    }
171
172    fn point_data_mut(&mut self) -> &mut DataSetAttributes {
173        &mut self.point_data
174    }
175
176    fn cell_data(&self) -> &DataSetAttributes {
177        &self.cell_data
178    }
179
180    fn cell_data_mut(&mut self) -> &mut DataSetAttributes {
181        &mut self.cell_data
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn basic_structured_grid() {
191        let mut pts = Points::new();
192        // 3x2 grid with some curvature
193        for j in 0..2 {
194            for i in 0..3 {
195                let x = i as f64;
196                let y = j as f64 + 0.1 * (i as f64).sin();
197                pts.push([x, y, 0.0]);
198            }
199        }
200        let grid = StructuredGrid::from_dimensions_and_points([3, 2, 1], pts);
201        assert_eq!(grid.num_points(), 6);
202        assert_eq!(grid.num_cells(), 2);
203        assert_eq!(grid.dimensions(), [3, 2, 1]);
204    }
205
206    #[test]
207    fn index_roundtrip() {
208        let mut pts = Points::new();
209        for _ in 0..24 { pts.push([0.0, 0.0, 0.0]); }
210        let grid = StructuredGrid::from_dimensions_and_points([4, 3, 2], pts);
211        for idx in 0..24 {
212            let (i, j, k) = grid.ijk_from_index(idx);
213            assert_eq!(grid.index_from_ijk(i, j, k), idx);
214        }
215    }
216
217    #[test]
218    fn bounds_from_points() {
219        let mut pts = Points::new();
220        pts.push([0.0, 0.0, 0.0]);
221        pts.push([10.0, 5.0, 3.0]);
222        let grid = StructuredGrid::from_dimensions_and_points([2, 1, 1], pts);
223        let bb = grid.bounds();
224        assert_eq!(bb.x_max, 10.0);
225        assert_eq!(bb.y_max, 5.0);
226    }
227}