Skip to main content

aeon_tk/geometry/
region.rs

1use std::{
2    array::from_fn,
3    cmp::Ordering,
4    fmt::{Display, Write},
5};
6
7use super::{index::IndexWindow, Split, CartesianIter, Face, IndexSpace};
8
9/// Denotes where the region falls on a certain axis.
10#[repr(u8)]
11#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Side {
13    Left = 0,
14    Middle = 1,
15    Right = 2,
16}
17
18impl Side {
19    pub fn reverse(self) -> Self {
20        match self {
21            Self::Left => Self::Right,
22            Self::Right => Self::Left,
23            Self::Middle => Self::Middle,
24        }
25    }
26
27    pub fn from_value(val: u8) -> Self {
28        assert!(val < 3);
29        // Safety. We have specified the memory representation of the
30        // enum and checked the value, so this should be safe.
31        unsafe { std::mem::transmute(val) }
32    }
33}
34
35#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
36pub struct Region<const N: usize> {
37    #[serde(with = "crate::array")]
38    sides: [Side; N],
39}
40
41impl<const N: usize> Region<N> {
42    /// Number of different regions in a given number of dimensions.
43    pub const COUNT: usize = 3usize.pow(N as u32);
44    /// The default "central" region.
45    pub const CENTRAL: Self = Self::new([Side::Middle; N]);
46
47    // Builds a new region from the given sides.
48    pub const fn new(sides: [Side; N]) -> Self {
49        Self { sides }
50    }
51
52    pub const fn sides(&self) -> [Side; N] {
53        self.sides
54    }
55
56    pub const fn side(&self, axis: usize) -> Side {
57        self.sides[axis]
58    }
59
60    pub fn set_side(&mut self, axis: usize, side: Side) {
61        self.sides[axis] = side
62    }
63
64    /// Reverse every side in the region.
65    pub fn reverse(&self) -> Self {
66        let mut result = [Side::Left; N];
67
68        for axis in 0..N {
69            result[axis] = self.sides[axis].reverse();
70        }
71
72        Self::new(result)
73    }
74
75    /// Returns number of axes that are not `Side::Middle`.
76    pub fn adjacency(&self) -> usize {
77        self.sides
78            .into_iter()
79            .filter(|&s| s != Side::Middle)
80            .count()
81    }
82
83    /// Iterates over all faces one would have to move over to get to the region.
84    pub fn adjacent_faces(&self) -> impl Iterator<Item = Face<N>> + '_ {
85        (0..N)
86            .filter(|&axis| self.side(axis) != Side::Middle)
87            .map(|axis| Face {
88                axis,
89                side: self.side(axis) == Side::Right,
90            })
91    }
92
93    /// Iterates over all splits that can touch this region.
94    pub fn adjacent_splits(self) -> impl Iterator<Item = Split<N>> {
95        let origin: [_; N] = from_fn(|axis| match self.side(axis) {
96            Side::Left | Side::Middle => 0,
97            Side::Right => 1,
98        });
99
100        let size: [_; N] = from_fn(|axis| match self.side(axis) {
101            Side::Middle => 2,
102            _ => 1,
103        });
104
105        IndexWindow::new(origin, size)
106            .iter()
107            .map(|index| Split::pack(from_fn(|axis| index[axis] != 0)))
108    }
109
110    /// Computes a split which touches the given region.
111    pub fn adjacent_split(&self) -> Split<N> {
112        let mut result = Split::empty();
113        for axis in 0..N {
114            result.set_to(axis, self.side(axis) == Side::Right)
115        }
116        result
117    }
118
119    /// Checks whether a given split is adjacent to the region.
120    pub fn is_split_adjacent(&self, split: Split<N>) -> bool {
121        for axis in 0..N {
122            match (self.side(axis), split.is_set(axis)) {
123                (Side::Left, true) => return false,
124                (Side::Right, false) => return false,
125                _ => {}
126            }
127        }
128
129        true
130    }
131
132    // /// Returns an index space with the same size as the region.
133    // pub fn index_space(&self, support: usize, block: [usize; N]) -> IndexSpace<N> {
134    //     let mut size = [0; N];
135
136    //     for axis in 0..N {
137    //         if self.sides[axis] != Side::Middle {
138    //             size[axis] = support;
139    //         } else {
140    //             size[axis] = block[axis]
141    //         }
142    //     }
143
144    //     IndexSpace::new(size)
145    // }
146
147    // /// Iterates nodes in the given region (including ghost nodes and nodes
148    // /// on faces).
149    // pub fn nodes(&self, support: usize, block: [usize; N]) -> RegionNodeIter<N> {
150    //     RegionNodeIter {
151    //         inner: self.index_space(support, block).iter(),
152    //         block,
153    //         sides: self.sides,
154    //     }
155    // }
156
157    // pub fn face_vertices(&self, block: [usize; N]) -> RegionFaceVertexIter<N> {
158    //     let mut size = [0; N];
159
160    //     for axis in 0..N {
161    //         size[axis] = match self.sides[axis] {
162    //             Side::Left | Side::Right => 1,
163    //             Side::Middle => block[axis],
164    //         }
165    //     }
166
167    //     RegionFaceVertexIter {
168    //         inner: IndexSpace::new(size).iter(),
169    //         block,
170    //         sides: self.sides,
171    //     }
172    // }
173
174    // pub fn offset_nodes(&self, support: usize) -> RegionOffsetNodeIter<N> {
175    //     let size = self.sides.map(|side| match side {
176    //         Side::Left | Side::Right => support,
177    //         Side::Middle => 1,
178    //     });
179
180    //     RegionOffsetNodeIter {
181    //         inner: IndexSpace::new(size).iter(),
182    //         sides: self.sides,
183    //     }
184    // }
185
186    // pub fn offset_dir(&self) -> [isize; N] {
187    //     self.sides.map(|side| match side {
188    //         Side::Left => -1,
189    //         Side::Right => 1,
190    //         Side::Middle => 0,
191    //     })
192    // }
193
194    // /// Returns a mask for which a given axis is set if and only if `self.sides[axis] != Side::Middle`.
195    // pub fn to_mask(&self) -> AxisMask<N> {
196    //     let mut result = AxisMask::empty();
197
198    //     for axis in 0..N {
199    //         result.set_to(axis, self.sides[axis] != Side::Middle);
200    //     }
201
202    //     result
203    // }
204
205    // pub fn masked(&self, mask: AxisMask<N>) -> Self {
206    //     let mut sides = self.sides;
207
208    //     for i in 0..N {
209    //         if !mask.is_set(i) {
210    //             sides[i] = Side::Middle;
211    //         }
212    //     }
213
214    //     Self::new(sides)
215    // }
216
217    // pub fn masked_by_split(&self, split: AxisMask<N>) -> Self {
218    //     let mut mask = AxisMask::empty();
219
220    //     for axis in 0..N {
221    //         match self.sides[axis] {
222    //             Side::Left => mask.set_to(axis, !split.is_set(axis)),
223    //             Side::Middle => mask.clear(axis),
224    //             Side::Right => mask.set_to(axis, split.is_set(axis)),
225    //         }
226    //     }
227
228    //     self.masked(mask)
229    // }
230
231    /// Converts the region into an integer value.
232    pub fn to_linear(&self) -> usize {
233        let space = IndexSpace::new([3; N]);
234        let index = from_fn(|axis| self.side(axis) as usize);
235        space.linear_from_cartesian(index)
236    }
237
238    /// Converts an integer value into a region.
239    pub fn from_linear(val: usize) -> Self {
240        let space = IndexSpace::new([3; N]);
241        let index = space.cartesian_from_linear(val);
242        Self::new(from_fn(|axis| Side::from_value(index[axis] as u8)))
243    }
244}
245
246impl<const N: usize> Ord for Region<N> {
247    fn cmp(&self, other: &Self) -> Ordering {
248        let space = IndexSpace::new([3; N]);
249        let index = from_fn(|axis| self.side(axis) as usize);
250        let oindex = from_fn(|axis| other.side(axis) as usize);
251
252        space
253            .linear_from_cartesian(index)
254            .cmp(&space.linear_from_cartesian(oindex))
255    }
256}
257
258impl<const N: usize> PartialOrd for Region<N> {
259    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
260        Some(self.cmp(other))
261    }
262}
263
264impl<const N: usize> Display for Region<N> {
265    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266        for axis in 0..N {
267            match self.side(axis) {
268                Side::Left => f.write_char('-'),
269                Side::Middle => f.write_char('='),
270                Side::Right => f.write_char('+'),
271            }?;
272        }
273        Ok(())
274    }
275}
276
277// /// Allows iterating the nodes in a region.
278// pub struct RegionNodeIter<const N: usize> {
279//     inner: CartesianIter<N>,
280//     block: [usize; N],
281//     sides: [Side; N],
282// }
283
284// impl<const N: usize> Iterator for RegionNodeIter<N> {
285//     type Item = [isize; N];
286
287//     fn next(&mut self) -> Option<Self::Item> {
288//         let cart = self.inner.next()?;
289
290//         let mut result = [0isize; N];
291
292//         for axis in 0..N {
293//             result[axis] = match self.sides[axis] {
294//                 Side::Left => -(cart[axis] as isize),
295//                 Side::Right => (self.block[axis] + cart[axis]) as isize,
296//                 Side::Middle => cart[axis] as isize,
297//             }
298//         }
299
300//         Some(result)
301//     }
302// }
303
304// /// Allows iterating the vertices on the face of a region.
305// pub struct RegionFaceVertexIter<const N: usize> {
306//     inner: CartesianIter<N>,
307//     block: [usize; N],
308//     sides: [Side; N],
309// }
310
311// impl<const N: usize> Iterator for RegionFaceVertexIter<N> {
312//     type Item = [usize; N];
313
314//     fn next(&mut self) -> Option<Self::Item> {
315//         let cart = self.inner.next()?;
316
317//         let mut result = [0; N];
318
319//         for axis in 0..N {
320//             result[axis] = match self.sides[axis] {
321//                 Side::Left => 0,
322//                 Side::Right => self.block[axis] - 1,
323//                 Side::Middle => cart[axis],
324//             }
325//         }
326
327//         Some(result)
328//     }
329// }
330
331// pub struct RegionOffsetNodeIter<const N: usize> {
332//     inner: CartesianIter<N>,
333//     sides: [Side; N],
334// }
335
336// impl<const N: usize> Iterator for RegionOffsetNodeIter<N> {
337//     type Item = [isize; N];
338
339//     fn next(&mut self) -> Option<Self::Item> {
340//         let cart = self.inner.next()?;
341
342//         let mut result = [0isize; N];
343
344//         for axis in 0..N {
345//             result[axis] = match self.sides[axis] {
346//                 Side::Left => -(cart[axis] as isize),
347//                 Side::Right => cart[axis] as isize,
348//                 Side::Middle => 0,
349//             }
350//         }
351
352//         Some(result)
353//     }
354// }
355
356pub struct RegionIter<const N: usize> {
357    inner: CartesianIter<N>,
358}
359
360impl<const N: usize> Iterator for RegionIter<N> {
361    type Item = Region<N>;
362
363    fn next(&mut self) -> Option<Self::Item> {
364        Some(Region::new(self.inner.next()?.map(|idx| match idx {
365            0 => Side::Left,
366            1 => Side::Middle,
367            2 => Side::Right,
368            _ => unreachable!(),
369        })))
370    }
371}
372
373impl<const N: usize> ExactSizeIterator for RegionIter<N> {
374    fn len(&self) -> usize {
375        Region::<N>::COUNT
376    }
377}
378
379/// Iterates over all regions in an N-dimensional space.
380pub fn regions<const N: usize>() -> RegionIter<N> {
381    RegionIter {
382        inner: IndexSpace::new([3; N]).iter(),
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use crate::geometry::{Split, Face};
389
390    use super::{regions, Region, Side};
391
392    #[test]
393    fn region_iteration() {
394        let comparison = [
395            [Side::Left, Side::Left],
396            [Side::Middle, Side::Left],
397            [Side::Right, Side::Left],
398            [Side::Left, Side::Middle],
399            [Side::Middle, Side::Middle],
400            [Side::Right, Side::Middle],
401            [Side::Left, Side::Right],
402            [Side::Middle, Side::Right],
403            [Side::Right, Side::Right],
404        ];
405
406        for (region, compare) in regions().zip(comparison.into_iter()) {
407            assert_eq!(region, Region::new(compare));
408        }
409    }
410
411    #[test]
412    fn adjacency() {
413        let region = Region::new([Side::Left, Side::Right]);
414        assert_eq!(region.adjacency(), 2);
415
416        let mut faces = region.adjacent_faces();
417        assert_eq!(faces.next(), Some(Face::negative(0)));
418        assert_eq!(faces.next(), Some(Face::positive(1)));
419        assert_eq!(faces.next(), None);
420
421        let mut splits = region.adjacent_splits();
422        assert_eq!(splits.next(), Some(Split::pack([false, true])));
423        assert_eq!(splits.next(), None);
424
425        assert_eq!(region.adjacent_split(), Split::pack([false, true]));
426    }
427}