retrofire_core/math/
space.rs

1//! Linear (vector) spaces and affine spaces.
2//!
3//! TODO
4
5use core::fmt::{Debug, Formatter};
6use core::iter::zip;
7use core::marker::PhantomData;
8
9use crate::math::vary::{Iter, Vary, ZDiv};
10
11/// Trait for types representing elements of an affine space.
12///
13/// TODO More documentation, definition of affine space
14pub trait Affine: Sized {
15    /// The space that `Self` is the element type of.
16    type Space;
17    /// The (signed) difference of two values of `Self`.
18    /// `Diff` must have the same dimension as `Self`.
19    type Diff: Linear;
20
21    /// The dimension of `Self`.
22    const DIM: usize;
23
24    /// Adds `diff` to `self` component-wise.
25    ///
26    /// `add` is commutative and associative.
27    fn add(&self, diff: &Self::Diff) -> Self;
28
29    /// Subtracts `other` from `self`, returning the (signed) difference.
30    ///
31    /// `sub` is anti-commutative: `v.sub(w) == w.sub(v).neg()`.
32    fn sub(&self, other: &Self) -> Self::Diff;
33
34    /// Returns an affine combination of points.
35    ///
36    /// Given a list of weights (w<sub>1</sub>, ..., w<sub>*n*</sub>) and
37    /// points (P<sub>1</sub>, ..., P<sub>*n*</sub>),
38    /// returns
39    ///
40    /// w<sub>1</sub> * P<sub>1</sub> + ... + w<sub>*n*</sub> * P<sub>*n*</sub>
41    fn combine<S: Copy, const N: usize>(
42        weights: &[S; N],
43        points: &[Self; N],
44    ) -> Self
45    where
46        Self: Clone,
47        Self::Diff: Linear<Scalar = S>,
48    {
49        const { assert!(N != 0) }
50
51        let p0 = &points[0]; // ok, asserted N > 0
52        zip(&weights[1..], &points[1..])
53            .fold(p0.clone(), |res, (w, q)| res.add(&q.sub(p0).mul(*w)))
54    }
55}
56
57/// Trait for types representing elements of a linear space (vector space).
58///
59/// A `Linear` type is a type that is `Affine` and
60/// additionally satisfies the following conditions:
61///
62/// * The difference type [`Diff`][Affine::Diff] is equal to `Self`
63/// * The type has an additive identity, returned by the [`zero`][Self::zero] method
64/// * Every value has an additive inverse, returned by the [`neg`][Self::neg] method
65///
66/// TODO More documentation
67pub trait Linear: Affine<Diff = Self> {
68    /// The scalar type associated with `Self`.
69    type Scalar: Sized;
70
71    /// Returns the additive identity of `Self`.
72    fn zero() -> Self;
73
74    /// Returns the additive inverse of `self`.
75    fn neg(&self) -> Self {
76        Self::zero().sub(self)
77    }
78
79    /// Multiplies all components of `self` by `scalar`.
80    ///
81    /// `mul` is commutative and associative, and distributes over
82    /// `add` and `sub` (up to rounding errors):
83    /// ```
84    /// # use retrofire_core::math::space::{Affine, Linear};
85    /// # let [v, w, x, a] = [1.0f32, 2.0, 3.0, 4.0];
86    /// v.mul(w) == w.mul(v);
87    /// v.mul(w).mul(x) == v.mul(w.mul(x));
88    /// v.mul(a).add(&w.mul(a)) == v.add(&w).mul(a);
89    /// v.mul(a).sub(&w.mul(a)) == v.add(&w).sub(&a);
90    /// ```
91    fn mul(&self, scalar: Self::Scalar) -> Self;
92}
93
94/// Tag type for real vector spaces and Euclidean spaces.
95///
96/// For example, the type `Real<3>` corresponds to ℝ³.
97#[derive(Copy, Clone, Default, Eq, PartialEq)]
98pub struct Real<const DIM: usize, Basis = ()>(PhantomData<Basis>);
99
100/// Tag type for the projective 3-space over the reals, 𝗣<sub>3</sub>(ℝ).
101///
102/// The properties of this space make it useful for implementing perspective
103/// projection. Clipping is also done in the projective space.
104#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
105pub struct Proj3;
106
107#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
108pub struct Hom<const DIM: usize, Basis = ()>(PhantomData<Basis>);
109
110impl Affine for f32 {
111    type Space = ();
112    type Diff = Self;
113    const DIM: usize = 1;
114
115    fn add(&self, other: &f32) -> f32 {
116        self + other
117    }
118    fn sub(&self, other: &f32) -> f32 {
119        self - other
120    }
121}
122
123impl Linear for f32 {
124    type Scalar = f32;
125
126    fn zero() -> f32 {
127        0.0
128    }
129    fn mul(&self, rhs: f32) -> f32 {
130        self * rhs
131    }
132}
133
134impl Affine for i32 {
135    type Space = ();
136    type Diff = Self;
137    const DIM: usize = 1;
138
139    fn add(&self, rhs: &i32) -> i32 {
140        self + rhs
141    }
142    fn sub(&self, rhs: &i32) -> i32 {
143        self - rhs
144    }
145}
146
147impl Linear for i32 {
148    type Scalar = Self;
149
150    fn zero() -> i32 {
151        0
152    }
153    fn mul(&self, rhs: i32) -> i32 {
154        self * rhs
155    }
156}
157
158impl Affine for u32 {
159    type Space = ();
160    type Diff = i32;
161    const DIM: usize = 1;
162
163    fn add(&self, rhs: &i32) -> u32 {
164        let (res, o) = self.overflowing_add_signed(*rhs);
165        debug_assert!(!o, "overflow adding {rhs}_i32 to {self}_u32");
166        res
167    }
168
169    fn sub(&self, rhs: &u32) -> i32 {
170        let diff = *self as i64 - *rhs as i64;
171        debug_assert!(
172            i32::try_from(diff).is_ok(),
173            "overflow subtracting {rhs}_u32 from {self}_u32"
174        );
175        diff as i32
176    }
177}
178
179impl<V: Clone> Vary for V
180where
181    Self: Affine<Diff: Linear<Scalar = f32> + Clone> + ZDiv,
182{
183    type Iter = Iter<Self>;
184    type Diff = <Self as Affine>::Diff;
185
186    #[inline]
187    fn vary(self, step: Self::Diff, n: Option<u32>) -> Self::Iter {
188        Iter { val: self, step, n }
189    }
190
191    fn dv_dt(&self, other: &Self, recip_dt: f32) -> Self::Diff {
192        other.sub(self).mul(recip_dt)
193    }
194
195    /// Adds `delta` to `self`.
196    #[inline]
197    fn step(&self, delta: &Self::Diff) -> Self {
198        self.add(delta)
199    }
200}
201
202impl<const DIM: usize, B> Debug for Real<DIM, B>
203where
204    B: Debug + Default,
205{
206    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
207        const DIMS: [&str; 4] = ["", "²", "³", "⁴"];
208        let b = B::default();
209        if let Some(dim) = DIMS.get(DIM - 1) {
210            write!(f, "ℝ{dim}<{b:?}>")
211        } else {
212            write!(f, "ℝ^{DIM}<{b:?}>")
213        }
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    mod f32 {
222        use super::*;
223
224        #[test]
225        fn affine_ops() {
226            assert_eq!(f32::DIM, 1);
227
228            assert_eq!(1_f32.add(&2_f32), 3_f32);
229            assert_eq!(3_f32.add(&-2_f32), 1_f32);
230
231            assert_eq!(3_f32.sub(&2_f32), 1_f32);
232            assert_eq!(1_f32.sub(&4_f32), -3_f32);
233        }
234
235        #[test]
236        fn linear_ops() {
237            assert_eq!(f32::zero(), 0.0);
238
239            assert_eq!(2_f32.neg(), -2_f32);
240            assert_eq!(-3_f32.neg(), 3_f32);
241
242            assert_eq!(3_f32.mul(2_f32), 6_f32);
243            assert_eq!(3_f32.mul(0.5_f32), 1.5_f32);
244            assert_eq!(3_f32.mul(-2_f32), -6_f32);
245        }
246    }
247
248    mod i32 {
249        use super::*;
250
251        #[test]
252        fn affine_ops() {
253            assert_eq!(i32::DIM, 1);
254
255            assert_eq!(1_i32.add(&2_i32), 3_i32);
256            assert_eq!(2_i32.add(&-3_i32), -1_i32);
257
258            assert_eq!(3_i32.sub(&2_i32), 1_i32);
259            assert_eq!(3_i32.sub(&4_i32), -1_i32);
260        }
261
262        #[test]
263        fn linear_ops() {
264            assert_eq!(i32::zero(), 0);
265
266            assert_eq!(2_i32.neg(), -2_i32);
267            assert_eq!(-3_i32.neg(), 3_i32);
268
269            assert_eq!(3_i32.mul(2_i32), 6_i32);
270            assert_eq!(2_i32.mul(-3_i32), -6_i32);
271        }
272    }
273
274    mod u32 {
275        use super::*;
276
277        #[test]
278        fn affine_ops() {
279            assert_eq!(u32::DIM, 1);
280
281            assert_eq!(1_u32.add(&2_i32), 3_u32);
282            assert_eq!(3_u32.add(&-2_i32), 1_u32);
283
284            assert_eq!(3_u32.sub(&2_u32), 1_i32);
285            assert_eq!(3_u32.sub(&4_u32), -1_i32);
286        }
287
288        #[test]
289        #[should_panic]
290        fn affine_add_underflow_should_panic() {
291            _ = 3_u32.add(&-4_i32);
292        }
293
294        #[test]
295        #[should_panic]
296        fn affine_add_overflow_should_panic() {
297            _ = (u32::MAX / 2 + 2).add(&i32::MAX);
298        }
299
300        #[test]
301        #[should_panic]
302        fn affine_sub_underflow_should_panic() {
303            _ = 3_u32.sub(&u32::MAX);
304        }
305
306        #[test]
307        #[should_panic]
308        fn affine_sub_overflow_should_panic() {
309            _ = u32::MAX.sub(&1_u32);
310        }
311    }
312}