Skip to main content

mesh_sieve/topology/
orientation.rs

1//! Canonical orientation groups for meshes (edges, triangles, quads, ...)
2//! with small, copyable representations.
3
4use core::fmt::{Debug, Formatter};
5
6use crate::topology::sieve::oriented::Orientation;
7
8/// 1-bit flip (edge reversal in 1D); group C₂.
9/// Compose = XOR; inverse = self.
10#[derive(Copy, Clone, Eq, PartialEq, Hash, Default)]
11#[repr(transparent)]
12pub struct BitFlip(pub bool);
13impl Debug for BitFlip {
14    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
15        f.debug_tuple("BitFlip").field(&self.0).finish()
16    }
17}
18impl Orientation for BitFlip {
19    #[inline]
20    fn compose(a: Self, b: Self) -> Self {
21        BitFlip(a.0 ^ b.0)
22    }
23    #[inline]
24    fn inverse(a: Self) -> Self {
25        a
26    }
27}
28
29/// Pure rotation group C_N (polygon rotations); stored as u8 mod N.
30#[derive(Copy, Clone, Eq, PartialEq, Hash)]
31pub struct Rot<const N: u8>(pub u8);
32impl<const N: u8> Default for Rot<N> {
33    fn default() -> Self {
34        Rot(0)
35    }
36}
37impl<const N: u8> Debug for Rot<N> {
38    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
39        f.debug_tuple("Rot").field(&self.0).finish()
40    }
41}
42impl<const N: u8> Orientation for Rot<N> {
43    #[inline]
44    fn compose(a: Self, b: Self) -> Self {
45        Rot::<N>((a.0 + b.0) % N)
46    }
47    #[inline]
48    fn inverse(a: Self) -> Self {
49        Rot::<N>((N - (a.0 % N)) % N)
50    }
51}
52
53/// Dihedral group D_N (rotations + reflections); covers triangles (N=3) & quads (N=4).
54/// Element = r^k * s^f, with k∈[0,N), f∈{0,1}; law: (k,f)*(k',f') = (k + (-1)^f k' mod N, f xor f')
55#[derive(Copy, Clone, Eq, PartialEq, Hash)]
56pub struct Dihedral<const N: u8> {
57    pub rot: u8,
58    pub flip: bool,
59}
60impl<const N: u8> Default for Dihedral<N> {
61    fn default() -> Self {
62        Self {
63            rot: 0,
64            flip: false,
65        }
66    }
67}
68impl<const N: u8> Debug for Dihedral<N> {
69    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
70        f.debug_struct("Dihedral")
71            .field("rot", &self.rot)
72            .field("flip", &self.flip)
73            .finish()
74    }
75}
76impl<const N: u8> Orientation for Dihedral<N> {
77    #[inline]
78    fn compose(a: Self, b: Self) -> Self {
79        let add = if a.flip {
80            (N - (b.rot % N)) % N
81        } else {
82            b.rot % N
83        };
84        let rot = (a.rot + add) % N;
85        let flip = a.flip ^ b.flip;
86        Self { rot, flip }
87    }
88    #[inline]
89    fn inverse(a: Self) -> Self {
90        if !a.flip {
91            Self {
92                rot: (N - (a.rot % N)) % N,
93                flip: false,
94            }
95        } else {
96            Self {
97                rot: a.rot % N,
98                flip: true,
99            }
100        }
101    }
102}
103
104/// Small, fixed-size permutation group S_K, represented as mapping [0..K) -> [0..K).
105/// Useful for faces (triangles => S3) or element-local permutations in 3D.
106/// Compose(p,q) = p ∘ q (apply q, then p).
107#[derive(Copy, Clone, Eq, PartialEq, Hash)]
108pub struct Perm<const K: usize>(pub [u8; K]);
109impl<const K: usize> Default for Perm<K> {
110    fn default() -> Self {
111        let mut id = [0u8; K];
112        let mut i = 0;
113        while i < K {
114            id[i] = i as u8;
115            i += 1;
116        }
117        Perm(id)
118    }
119}
120impl<const K: usize> Debug for Perm<K> {
121    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
122        f.debug_tuple("Perm").field(&self.0).finish()
123    }
124}
125impl<const K: usize> Perm<K> {
126    #[inline]
127    pub fn new_unchecked(p: [u8; K]) -> Self {
128        Perm(p)
129    }
130    #[inline]
131    pub fn invert(&self) -> Self {
132        let mut inv = [0u8; K];
133        let mut i = 0;
134        while i < K {
135            inv[self.0[i] as usize] = i as u8;
136            i += 1;
137        }
138        Perm(inv)
139    }
140}
141impl<const K: usize> Orientation for Perm<K> {
142    #[inline]
143    fn compose(a: Self, b: Self) -> Self {
144        let mut out = [0u8; K];
145        let mut i = 0;
146        while i < K {
147            out[i] = a.0[b.0[i] as usize];
148            i += 1;
149        }
150        Perm(out)
151    }
152    #[inline]
153    fn inverse(a: Self) -> Self {
154        a.invert()
155    }
156}
157
158// Ergonomic aliases for meshes:
159/// Cheap sign-flip alias.
160pub use BitFlip as Sign; // 1D edge flip
161/// Triangle face orientation.
162pub type D3 = Dihedral<3>;
163/// Quad face orientation.
164pub type D4 = Dihedral<4>;
165/// Triangle vertex permutations.
166pub type S3 = Perm<3>;
167/// Tetra permutations.
168pub type S4 = Perm<4>;
169
170/// Accumulate a sequence of orientation steps along a path, left-to-right.
171/// Returns the total orientation from the seed to the end of the path.
172/// Identity is `O::default()`.
173#[inline]
174pub fn accumulate_path<O, I>(path: I) -> O
175where
176    O: Orientation,
177    I: IntoIterator<Item = O>,
178{
179    path.into_iter()
180        .fold(O::default(), |acc, step| O::compose(acc, step))
181}
182
183/// Extension for `OrientedSieve` call-sites (pure sugar).
184pub trait AccumulatePathExt: Sized {
185    fn accumulate_path<O, I>(path: I) -> O
186    where
187        O: Orientation,
188        I: IntoIterator<Item = O>,
189    {
190        accumulate_path(path)
191    }
192}
193impl<T> AccumulatePathExt for T {}