1use approx::{AbsDiffEq, RelativeEq, UlpsEq};
2use std::any::Any;
3use std::fmt::{self, Debug};
4use std::hash;
5use std::marker::PhantomData;
6
7#[cfg(feature = "serde-serialize-no-std")]
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use simba::scalar::RealField;
11
12use crate::base::allocator::Allocator;
13use crate::base::dimension::{DimNameAdd, DimNameSum, U1};
14use crate::base::storage::Owned;
15use crate::base::{Const, DefaultAllocator, DimName, OMatrix, SVector};
16
17use crate::geometry::Point;
18
19pub trait TCategory: Any + Debug + Copy + PartialEq + Send {
23 #[inline]
26 fn has_normalizer() -> bool {
27 true
28 }
29
30 fn check_homogeneous_invariants<T: RealField, D: DimName>(mat: &OMatrix<T, D, D>) -> bool
33 where
34 T::Epsilon: Clone,
35 DefaultAllocator: Allocator<D, D>;
36}
37
38pub trait TCategoryMul<Other: TCategory>: TCategory {
41 type Representative: TCategory;
45}
46
47pub trait SuperTCategoryOf<Other: TCategory>: TCategory {}
49
50pub trait SubTCategoryOf<Other: TCategory>: TCategory {}
54impl<T1, T2> SubTCategoryOf<T2> for T1
55where
56 T1: TCategory,
57 T2: SuperTCategoryOf<T1>,
58{
59}
60
61#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
63pub enum TGeneral {}
64
65#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
67pub enum TProjective {}
68
69#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
71pub enum TAffine {}
72
73impl TCategory for TGeneral {
74 #[inline]
75 fn check_homogeneous_invariants<T: RealField, D: DimName>(_: &OMatrix<T, D, D>) -> bool
76 where
77 T::Epsilon: Clone,
78 DefaultAllocator: Allocator<D, D>,
79 {
80 true
81 }
82}
83
84impl TCategory for TProjective {
85 #[inline]
86 fn check_homogeneous_invariants<T: RealField, D: DimName>(mat: &OMatrix<T, D, D>) -> bool
87 where
88 T::Epsilon: Clone,
89 DefaultAllocator: Allocator<D, D>,
90 {
91 mat.is_invertible()
92 }
93}
94
95impl TCategory for TAffine {
96 #[inline]
97 fn has_normalizer() -> bool {
98 false
99 }
100
101 #[inline]
102 fn check_homogeneous_invariants<T: RealField, D: DimName>(mat: &OMatrix<T, D, D>) -> bool
103 where
104 T::Epsilon: Clone,
105 DefaultAllocator: Allocator<D, D>,
106 {
107 let last = D::dim() - 1;
108 mat.is_invertible()
109 && mat[(last, last)] == T::one()
110 && (0..last).all(|i| mat[(last, i)].is_zero())
111 }
112}
113
114macro_rules! category_mul_impl(
115 ($($a: ident * $b: ident => $c: ty);* $(;)*) => {$(
116 impl TCategoryMul<$a> for $b {
117 type Representative = $c;
118 }
119 )*}
120);
121
122impl<T: TCategory> TCategoryMul<T> for T {
124 type Representative = T;
125}
126
127category_mul_impl!(
128TGeneral * TProjective => TGeneral;
130 TGeneral * TAffine => TGeneral;
131
132 TProjective * TGeneral => TGeneral;
133TProjective * TAffine => TProjective;
135
136 TAffine * TGeneral => TGeneral;
137 TAffine * TProjective => TProjective;
138);
140
141macro_rules! super_tcategory_impl(
142 ($($a: ident >= $b: ident);* $(;)*) => {$(
143 impl SuperTCategoryOf<$b> for $a { }
144 )*}
145);
146
147impl<T: TCategory> SuperTCategoryOf<T> for T {}
148
149super_tcategory_impl!(
150 TGeneral >= TProjective;
151 TGeneral >= TAffine;
152 TProjective >= TAffine;
153);
154
155#[repr(C)]
160pub struct Transform<T: RealField, C: TCategory, const D: usize>
161where
162 Const<D>: DimNameAdd<U1>,
163 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
164{
165 matrix: OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
166 _phantom: PhantomData<C>,
167}
168
169impl<T: RealField + Debug, C: TCategory, const D: usize> Debug for Transform<T, C, D>
170where
171 Const<D>: DimNameAdd<U1>,
172 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
173{
174 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
175 self.matrix.fmt(formatter)
176 }
177}
178
179impl<T: RealField + hash::Hash, C: TCategory, const D: usize> hash::Hash for Transform<T, C, D>
180where
181 Const<D>: DimNameAdd<U1>,
182 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
183 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: hash::Hash,
184{
185 fn hash<H: hash::Hasher>(&self, state: &mut H) {
186 self.matrix.hash(state);
187 }
188}
189
190impl<T: RealField + Copy, C: TCategory, const D: usize> Copy for Transform<T, C, D>
191where
192 Const<D>: DimNameAdd<U1>,
193 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
194 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Copy,
195{
196}
197
198impl<T: RealField, C: TCategory, const D: usize> Clone for Transform<T, C, D>
199where
200 Const<D>: DimNameAdd<U1>,
201 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
202{
203 #[inline]
204 fn clone(&self) -> Self {
205 Transform::from_matrix_unchecked(self.matrix.clone())
206 }
207}
208
209#[cfg(feature = "bytemuck")]
210unsafe impl<T, C: TCategory, const D: usize> bytemuck::Zeroable for Transform<T, C, D>
211where
212 T: RealField + bytemuck::Zeroable,
213 Const<D>: DimNameAdd<U1>,
214 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
215 OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: bytemuck::Zeroable,
216{
217}
218
219#[cfg(feature = "bytemuck")]
220unsafe impl<T, C: TCategory, const D: usize> bytemuck::Pod for Transform<T, C, D>
221where
222 T: RealField + bytemuck::Pod,
223 Const<D>: DimNameAdd<U1>,
224 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
225 OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: bytemuck::Pod,
226 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Copy,
227{
228}
229
230#[cfg(feature = "serde-serialize-no-std")]
231impl<T: RealField, C: TCategory, const D: usize> Serialize for Transform<T, C, D>
232where
233 Const<D>: DimNameAdd<U1>,
234 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
235 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Serialize,
236{
237 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238 where
239 S: Serializer,
240 {
241 self.matrix.serialize(serializer)
242 }
243}
244
245#[cfg(feature = "serde-serialize-no-std")]
246impl<'a, T: RealField, C: TCategory, const D: usize> Deserialize<'a> for Transform<T, C, D>
247where
248 Const<D>: DimNameAdd<U1>,
249 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
250 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Deserialize<'a>,
251{
252 fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
253 where
254 Des: Deserializer<'a>,
255 {
256 let matrix = OMatrix::<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>::deserialize(
257 deserializer,
258 )?;
259
260 Ok(Transform::from_matrix_unchecked(matrix))
261 }
262}
263
264impl<T: RealField + Eq, C: TCategory, const D: usize> Eq for Transform<T, C, D>
265where
266 Const<D>: DimNameAdd<U1>,
267 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
268{
269}
270
271impl<T: RealField, C: TCategory, const D: usize> PartialEq for Transform<T, C, D>
272where
273 Const<D>: DimNameAdd<U1>,
274 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
275{
276 #[inline]
277 fn eq(&self, right: &Self) -> bool {
278 self.matrix == right.matrix
279 }
280}
281
282impl<T: RealField, C: TCategory, const D: usize> Transform<T, C, D>
283where
284 Const<D>: DimNameAdd<U1>,
285 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
286{
287 #[inline]
290 pub fn from_matrix_unchecked(
291 matrix: OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
292 ) -> Self {
293 Transform {
294 matrix,
295 _phantom: PhantomData,
296 }
297 }
298
299 #[inline]
312 pub fn into_inner(self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
313 self.matrix
314 }
315
316 #[deprecated(note = "use `.into_inner()` instead")]
319 #[inline]
320 pub fn unwrap(self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
321 self.matrix
322 }
323
324 #[inline]
337 #[must_use]
338 pub fn matrix(&self) -> &OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
339 &self.matrix
340 }
341
342 #[inline]
365 pub fn matrix_mut_unchecked(
366 &mut self,
367 ) -> &mut OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
368 &mut self.matrix
369 }
370
371 #[inline]
378 pub fn set_category<CNew: SuperTCategoryOf<C>>(self) -> Transform<T, CNew, D> {
379 Transform::from_matrix_unchecked(self.matrix)
380 }
381
382 #[inline]
384 #[deprecated(
385 note = "This method is redundant with automatic `Copy` and the `.clone()` method and will be removed in a future release."
386 )]
387 pub fn clone_owned(&self) -> Transform<T, C, D> {
388 Transform::from_matrix_unchecked(self.matrix.clone_owned())
389 }
390
391 #[inline]
404 #[must_use]
405 pub fn to_homogeneous(&self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
406 self.matrix().clone_owned()
407 }
408
409 #[inline]
433 #[must_use = "Did you mean to use try_inverse_mut()?"]
434 pub fn try_inverse(self) -> Option<Transform<T, C, D>> {
435 self.matrix
436 .try_inverse()
437 .map(Transform::from_matrix_unchecked)
438 }
439
440 #[inline]
457 #[must_use = "Did you mean to use inverse_mut()?"]
458 pub fn inverse(self) -> Transform<T, C, D>
459 where
460 C: SubTCategoryOf<TProjective>,
461 {
462 Transform::from_matrix_unchecked(self.matrix.try_inverse().unwrap())
464 }
465
466 #[inline]
491 pub fn try_inverse_mut(&mut self) -> bool {
492 self.matrix.try_inverse_mut()
493 }
494
495 #[inline]
513 pub fn inverse_mut(&mut self)
514 where
515 C: SubTCategoryOf<TProjective>,
516 {
517 let _ = self.matrix.try_inverse_mut();
518 }
519}
520
521impl<T, C, const D: usize> Transform<T, C, D>
522where
523 T: RealField,
524 C: TCategory,
525 Const<D>: DimNameAdd<U1>,
526 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
527 + Allocator<DimNameSum<Const<D>, U1>>, {
530 #[inline]
534 #[must_use]
535 pub fn transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
536 self * pt
537 }
538
539 #[inline]
544 #[must_use]
545 pub fn transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
546 self * v
547 }
548}
549
550impl<T: RealField, C: TCategory, const D: usize> Transform<T, C, D>
551where
552 Const<D>: DimNameAdd<U1>,
553 C: SubTCategoryOf<TProjective>,
554 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
555 + Allocator<DimNameSum<Const<D>, U1>>, {
558 #[inline]
562 #[must_use]
563 pub fn inverse_transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
564 self.clone().inverse() * pt
565 }
566
567 #[inline]
571 #[must_use]
572 pub fn inverse_transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
573 self.clone().inverse() * v
574 }
575}
576
577impl<T: RealField, const D: usize> Transform<T, TGeneral, D>
578where
579 Const<D>: DimNameAdd<U1>,
580 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
581{
582 #[inline]
585 pub fn matrix_mut(
586 &mut self,
587 ) -> &mut OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
588 self.matrix_mut_unchecked()
589 }
590}
591
592impl<T: RealField, C: TCategory, const D: usize> AbsDiffEq for Transform<T, C, D>
593where
594 Const<D>: DimNameAdd<U1>,
595 T::Epsilon: Clone,
596 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
597{
598 type Epsilon = T::Epsilon;
599
600 #[inline]
601 fn default_epsilon() -> Self::Epsilon {
602 T::default_epsilon()
603 }
604
605 #[inline]
606 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
607 self.matrix.abs_diff_eq(&other.matrix, epsilon)
608 }
609}
610
611impl<T: RealField, C: TCategory, const D: usize> RelativeEq for Transform<T, C, D>
612where
613 Const<D>: DimNameAdd<U1>,
614 T::Epsilon: Clone,
615 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
616{
617 #[inline]
618 fn default_max_relative() -> Self::Epsilon {
619 T::default_max_relative()
620 }
621
622 #[inline]
623 fn relative_eq(
624 &self,
625 other: &Self,
626 epsilon: Self::Epsilon,
627 max_relative: Self::Epsilon,
628 ) -> bool {
629 self.matrix
630 .relative_eq(&other.matrix, epsilon, max_relative)
631 }
632}
633
634impl<T: RealField, C: TCategory, const D: usize> UlpsEq for Transform<T, C, D>
635where
636 Const<D>: DimNameAdd<U1>,
637 T::Epsilon: Clone,
638 DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
639{
640 #[inline]
641 fn default_max_ulps() -> u32 {
642 T::default_max_ulps()
643 }
644
645 #[inline]
646 fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
647 self.matrix.ulps_eq(&other.matrix, epsilon, max_ulps)
648 }
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654 use crate::base::Matrix4;
655
656 #[test]
657 fn checks_homogeneous_invariants_of_square_identity_matrix() {
658 assert!(TAffine::check_homogeneous_invariants(
659 &Matrix4::<f32>::identity()
660 ));
661 }
662}