Skip to main content

terrain_generator/
grid.rs

1//! A dense 2D grid backed by a flat, row-major `Vec`.
2//!
3//! Every per-cell buffer in this crate — heightmap, flow, river and lake
4//! masks — is a [`Grid`]. It keeps the `(x, y)` ↔ `y * width + x` mapping
5//! in one place instead of scattering index arithmetic across modules,
6//! while still exposing the flat buffer for zero-copy hand-off to FFI,
7//! GPUs or file formats.
8
9use std::ops::{Index, IndexMut};
10
11/// A `width × height` grid of `T`, stored row-major.
12///
13/// Indexing is `(x, y)`: `grid[(x, y)]` reads the cell at column `x`,
14/// row `y`.
15#[derive(Clone, Debug, PartialEq)]
16pub struct Grid<T> {
17    width: usize,
18    height: usize,
19    data: Vec<T>,
20}
21
22impl<T: Clone> Grid<T> {
23    /// Creates a grid filled with `value`.
24    ///
25    /// # Panics
26    /// Panics if either dimension is `0`.
27    pub fn new(width: usize, height: usize, value: T) -> Self {
28        assert!(width > 0 && height > 0, "grid dimensions must be positive");
29        Self {
30            width,
31            height,
32            data: vec![value; width * height],
33        }
34    }
35}
36
37impl<T> Grid<T> {
38    /// Wraps an existing row-major buffer.
39    ///
40    /// # Panics
41    /// Panics if `data.len() != width * height` or either dimension is `0`.
42    pub fn from_vec(width: usize, height: usize, data: Vec<T>) -> Self {
43        assert!(width > 0 && height > 0, "grid dimensions must be positive");
44        assert_eq!(
45            data.len(),
46            width * height,
47            "buffer length must equal width * height"
48        );
49        Self {
50            width,
51            height,
52            data,
53        }
54    }
55
56    /// Grid width in cells.
57    #[inline]
58    pub fn width(&self) -> usize {
59        self.width
60    }
61
62    /// Grid height in cells.
63    #[inline]
64    pub fn height(&self) -> usize {
65        self.height
66    }
67
68    /// Total number of cells (`width * height`).
69    #[inline]
70    pub fn len(&self) -> usize {
71        self.data.len()
72    }
73
74    /// Always `false`: grids have positive dimensions by construction.
75    #[inline]
76    pub fn is_empty(&self) -> bool {
77        false
78    }
79
80    /// Flat index of `(x, y)`.
81    ///
82    /// Callers holding flat indices (e.g. from [`Self::as_slice`]) can map
83    /// back with [`Self::coords`].
84    #[inline]
85    pub fn idx(&self, x: usize, y: usize) -> usize {
86        debug_assert!(x < self.width && y < self.height);
87        y * self.width + x
88    }
89
90    /// `(x, y)` coordinates of a flat index.
91    #[inline]
92    pub fn coords(&self, idx: usize) -> (usize, usize) {
93        (idx % self.width, idx / self.width)
94    }
95
96    /// The underlying row-major buffer.
97    #[inline]
98    pub fn as_slice(&self) -> &[T] {
99        &self.data
100    }
101
102    /// The underlying row-major buffer, mutably.
103    #[inline]
104    pub fn as_mut_slice(&mut self) -> &mut [T] {
105        &mut self.data
106    }
107
108    /// Consumes the grid, returning the row-major buffer.
109    pub fn into_vec(self) -> Vec<T> {
110        self.data
111    }
112
113    /// Iterates over all cells in row-major order.
114    pub fn iter(&self) -> std::slice::Iter<'_, T> {
115        self.data.iter()
116    }
117
118    /// Iterates mutably over all cells in row-major order.
119    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> {
120        self.data.iter_mut()
121    }
122}
123
124impl<T> Index<(usize, usize)> for Grid<T> {
125    type Output = T;
126
127    #[inline]
128    fn index(&self, (x, y): (usize, usize)) -> &T {
129        &self.data[self.idx(x, y)]
130    }
131}
132
133impl<T> IndexMut<(usize, usize)> for Grid<T> {
134    #[inline]
135    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut T {
136        let idx = self.idx(x, y);
137        &mut self.data[idx]
138    }
139}
140
141impl<'a, T> IntoIterator for &'a Grid<T> {
142    type Item = &'a T;
143    type IntoIter = std::slice::Iter<'a, T>;
144
145    fn into_iter(self) -> Self::IntoIter {
146        self.data.iter()
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn round_trips_between_coords_and_flat_index() {
156        let grid = Grid::new(7, 5, 0u8);
157        for y in 0..5 {
158            for x in 0..7 {
159                assert_eq!(grid.coords(grid.idx(x, y)), (x, y));
160            }
161        }
162    }
163
164    #[test]
165    fn indexing_is_row_major() {
166        let mut grid = Grid::new(3, 2, 0i32);
167        grid[(2, 1)] = 42;
168        assert_eq!(grid.as_slice(), &[0, 0, 0, 0, 0, 42]);
169    }
170
171    #[test]
172    #[should_panic]
173    fn from_vec_rejects_wrong_length() {
174        let _ = Grid::from_vec(2, 2, vec![0u8; 3]);
175    }
176
177    #[test]
178    #[should_panic]
179    fn zero_dimensions_are_rejected() {
180        let _ = Grid::new(0, 4, 0u8);
181    }
182}