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<T, 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)]
63#[cfg_attr(
64 all(not(target_os = "cuda"), feature = "cuda"),
65 derive(cust::DeviceCopy)
66)]
67pub enum TGeneral {}
68
69#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
71#[cfg_attr(
72 all(not(target_os = "cuda"), feature = "cuda"),
73 derive(cust::DeviceCopy)
74)]
75pub enum TProjective {}
76
77#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
79#[cfg_attr(
80 all(not(target_os = "cuda"), feature = "cuda"),
81 derive(cust::DeviceCopy)
82)]
83pub enum TAffine {}
84
85impl TCategory for TGeneral {
86 #[inline]
87 fn check_homogeneous_invariants<T: RealField, D: DimName>(_: &OMatrix<T, D, D>) -> bool
88 where
89 T::Epsilon: Clone,
90 DefaultAllocator: Allocator<T, D, D>,
91 {
92 true
93 }
94}
95
96impl TCategory for TProjective {
97 #[inline]
98 fn check_homogeneous_invariants<T: RealField, D: DimName>(mat: &OMatrix<T, D, D>) -> bool
99 where
100 T::Epsilon: Clone,
101 DefaultAllocator: Allocator<T, D, D>,
102 {
103 mat.is_invertible()
104 }
105}
106
107impl TCategory for TAffine {
108 #[inline]
109 fn has_normalizer() -> bool {
110 false
111 }
112
113 #[inline]
114 fn check_homogeneous_invariants<T: RealField, D: DimName>(mat: &OMatrix<T, D, D>) -> bool
115 where
116 T::Epsilon: Clone,
117 DefaultAllocator: Allocator<T, D, D>,
118 {
119 let last = D::dim() - 1;
120 mat.is_invertible()
121 && mat[(last, last)] == T::one()
122 && (0..last).all(|i| mat[(last, i)].is_zero())
123 }
124}
125
126macro_rules! category_mul_impl(
127 ($($a: ident * $b: ident => $c: ty);* $(;)*) => {$(
128 impl TCategoryMul<$a> for $b {
129 type Representative = $c;
130 }
131 )*}
132);
133
134impl<T: TCategory> TCategoryMul<T> for T {
136 type Representative = T;
137}
138
139category_mul_impl!(
140TGeneral * TProjective => TGeneral;
142 TGeneral * TAffine => TGeneral;
143
144 TProjective * TGeneral => TGeneral;
145TProjective * TAffine => TProjective;
147
148 TAffine * TGeneral => TGeneral;
149 TAffine * TProjective => TProjective;
150);
152
153macro_rules! super_tcategory_impl(
154 ($($a: ident >= $b: ident);* $(;)*) => {$(
155 impl SuperTCategoryOf<$b> for $a { }
156 )*}
157);
158
159impl<T: TCategory> SuperTCategoryOf<T> for T {}
160
161super_tcategory_impl!(
162 TGeneral >= TProjective;
163 TGeneral >= TAffine;
164 TProjective >= TAffine;
165);
166
167#[repr(C)]
172pub struct Transform<T: RealField, C: TCategory, const D: usize>
173where
174 Const<D>: DimNameAdd<U1>,
175 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
176{
177 matrix: OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
178 _phantom: PhantomData<C>,
179}
180
181impl<T: RealField + Debug, C: TCategory, const D: usize> Debug for Transform<T, C, D>
182where
183 Const<D>: DimNameAdd<U1>,
184 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
185{
186 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
187 self.matrix.fmt(formatter)
188 }
189}
190
191impl<T: RealField + hash::Hash, C: TCategory, const D: usize> hash::Hash for Transform<T, C, D>
192where
193 Const<D>: DimNameAdd<U1>,
194 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
195 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: hash::Hash,
196{
197 fn hash<H: hash::Hasher>(&self, state: &mut H) {
198 self.matrix.hash(state);
199 }
200}
201
202impl<T: RealField + Copy, C: TCategory, const D: usize> Copy for Transform<T, C, D>
203where
204 Const<D>: DimNameAdd<U1>,
205 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
206 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Copy,
207{
208}
209
210#[cfg(all(not(target_os = "cuda"), feature = "cuda"))]
211unsafe impl<T: RealField + cust::memory::DeviceCopy, C: TCategory, const D: usize>
212 cust::memory::DeviceCopy for Transform<T, C, D>
213where
214 Const<D>: DimNameAdd<U1>,
215 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
216 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: cust::memory::DeviceCopy,
217{
218}
219
220impl<T: RealField, C: TCategory, const D: usize> Clone for Transform<T, C, D>
221where
222 Const<D>: DimNameAdd<U1>,
223 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
224{
225 #[inline]
226 fn clone(&self) -> Self {
227 Transform::from_matrix_unchecked(self.matrix.clone())
228 }
229}
230
231#[cfg(feature = "bytemuck")]
232unsafe impl<T, C: TCategory, const D: usize> bytemuck::Zeroable for Transform<T, C, D>
233where
234 T: RealField + bytemuck::Zeroable,
235 Const<D>: DimNameAdd<U1>,
236 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
237 OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: bytemuck::Zeroable,
238{
239}
240
241#[cfg(feature = "bytemuck")]
242unsafe impl<T, C: TCategory, const D: usize> bytemuck::Pod for Transform<T, C, D>
243where
244 T: RealField + bytemuck::Pod,
245 Const<D>: DimNameAdd<U1>,
246 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
247 OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: bytemuck::Pod,
248 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Copy,
249{
250}
251
252#[cfg(feature = "serde-serialize-no-std")]
253impl<T: RealField, C: TCategory, const D: usize> Serialize for Transform<T, C, D>
254where
255 Const<D>: DimNameAdd<U1>,
256 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
257 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Serialize,
258{
259 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
260 where
261 S: Serializer,
262 {
263 self.matrix.serialize(serializer)
264 }
265}
266
267#[cfg(feature = "serde-serialize-no-std")]
268impl<'a, T: RealField, C: TCategory, const D: usize> Deserialize<'a> for Transform<T, C, D>
269where
270 Const<D>: DimNameAdd<U1>,
271 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
272 Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Deserialize<'a>,
273{
274 fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
275 where
276 Des: Deserializer<'a>,
277 {
278 let matrix = OMatrix::<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>::deserialize(
279 deserializer,
280 )?;
281
282 Ok(Transform::from_matrix_unchecked(matrix))
283 }
284}
285
286impl<T: RealField + Eq, C: TCategory, const D: usize> Eq for Transform<T, C, D>
287where
288 Const<D>: DimNameAdd<U1>,
289 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
290{
291}
292
293impl<T: RealField, C: TCategory, const D: usize> PartialEq for Transform<T, C, D>
294where
295 Const<D>: DimNameAdd<U1>,
296 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
297{
298 #[inline]
299 fn eq(&self, right: &Self) -> bool {
300 self.matrix == right.matrix
301 }
302}
303
304impl<T: RealField, C: TCategory, const D: usize> Transform<T, C, D>
305where
306 Const<D>: DimNameAdd<U1>,
307 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
308{
309 #[inline]
312 pub fn from_matrix_unchecked(
313 matrix: OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
314 ) -> Self {
315 Transform {
316 matrix,
317 _phantom: PhantomData,
318 }
319 }
320
321 #[inline]
334 pub fn into_inner(self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
335 self.matrix
336 }
337
338 #[deprecated(note = "use `.into_inner()` instead")]
341 #[inline]
342 pub fn unwrap(self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
343 self.matrix
344 }
345
346 #[inline]
359 #[must_use]
360 pub fn matrix(&self) -> &OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
361 &self.matrix
362 }
363
364 #[inline]
387 pub fn matrix_mut_unchecked(
388 &mut self,
389 ) -> &mut OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
390 &mut self.matrix
391 }
392
393 #[inline]
400 pub fn set_category<CNew: SuperTCategoryOf<C>>(self) -> Transform<T, CNew, D> {
401 Transform::from_matrix_unchecked(self.matrix)
402 }
403
404 #[inline]
406 #[deprecated(
407 note = "This method is redundant with automatic `Copy` and the `.clone()` method and will be removed in a future release."
408 )]
409 pub fn clone_owned(&self) -> Transform<T, C, D> {
410 Transform::from_matrix_unchecked(self.matrix.clone_owned())
411 }
412
413 #[inline]
426 #[must_use]
427 pub fn to_homogeneous(&self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
428 self.matrix().clone_owned()
429 }
430
431 #[inline]
455 #[must_use = "Did you mean to use try_inverse_mut()?"]
456 pub fn try_inverse(self) -> Option<Transform<T, C, D>> {
457 self.matrix
458 .try_inverse()
459 .map(Transform::from_matrix_unchecked)
460 }
461
462 #[inline]
479 #[must_use = "Did you mean to use inverse_mut()?"]
480 pub fn inverse(self) -> Transform<T, C, D>
481 where
482 C: SubTCategoryOf<TProjective>,
483 {
484 Transform::from_matrix_unchecked(self.matrix.try_inverse().unwrap())
486 }
487
488 #[inline]
513 pub fn try_inverse_mut(&mut self) -> bool {
514 self.matrix.try_inverse_mut()
515 }
516
517 #[inline]
535 pub fn inverse_mut(&mut self)
536 where
537 C: SubTCategoryOf<TProjective>,
538 {
539 let _ = self.matrix.try_inverse_mut();
540 }
541}
542
543impl<T, C, const D: usize> Transform<T, C, D>
544where
545 T: RealField,
546 C: TCategory,
547 Const<D>: DimNameAdd<U1>,
548 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
549 + Allocator<T, DimNameSum<Const<D>, U1>>, {
552 #[inline]
556 #[must_use]
557 pub fn transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
558 self * pt
559 }
560
561 #[inline]
566 #[must_use]
567 pub fn transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
568 self * v
569 }
570}
571
572impl<T: RealField, C: TCategory, const D: usize> Transform<T, C, D>
573where
574 Const<D>: DimNameAdd<U1>,
575 C: SubTCategoryOf<TProjective>,
576 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
577 + Allocator<T, DimNameSum<Const<D>, U1>>, {
580 #[inline]
584 #[must_use]
585 pub fn inverse_transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
586 self.clone().inverse() * pt
587 }
588
589 #[inline]
593 #[must_use]
594 pub fn inverse_transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
595 self.clone().inverse() * v
596 }
597}
598
599impl<T: RealField, const D: usize> Transform<T, TGeneral, D>
600where
601 Const<D>: DimNameAdd<U1>,
602 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
603{
604 #[inline]
607 pub fn matrix_mut(
608 &mut self,
609 ) -> &mut OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
610 self.matrix_mut_unchecked()
611 }
612}
613
614impl<T: RealField, C: TCategory, const D: usize> AbsDiffEq for Transform<T, C, D>
615where
616 Const<D>: DimNameAdd<U1>,
617 T::Epsilon: Clone,
618 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
619{
620 type Epsilon = T::Epsilon;
621
622 #[inline]
623 fn default_epsilon() -> Self::Epsilon {
624 T::default_epsilon()
625 }
626
627 #[inline]
628 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
629 self.matrix.abs_diff_eq(&other.matrix, epsilon)
630 }
631}
632
633impl<T: RealField, C: TCategory, const D: usize> RelativeEq for Transform<T, C, D>
634where
635 Const<D>: DimNameAdd<U1>,
636 T::Epsilon: Clone,
637 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
638{
639 #[inline]
640 fn default_max_relative() -> Self::Epsilon {
641 T::default_max_relative()
642 }
643
644 #[inline]
645 fn relative_eq(
646 &self,
647 other: &Self,
648 epsilon: Self::Epsilon,
649 max_relative: Self::Epsilon,
650 ) -> bool {
651 self.matrix
652 .relative_eq(&other.matrix, epsilon, max_relative)
653 }
654}
655
656impl<T: RealField, C: TCategory, const D: usize> UlpsEq for Transform<T, C, D>
657where
658 Const<D>: DimNameAdd<U1>,
659 T::Epsilon: Clone,
660 DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
661{
662 #[inline]
663 fn default_max_ulps() -> u32 {
664 T::default_max_ulps()
665 }
666
667 #[inline]
668 fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
669 self.matrix.ulps_eq(&other.matrix, epsilon, max_ulps)
670 }
671}
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676 use crate::base::Matrix4;
677
678 #[test]
679 fn checks_homogeneous_invariants_of_square_identity_matrix() {
680 assert!(TAffine::check_homogeneous_invariants(
681 &Matrix4::<f32>::identity()
682 ));
683 }
684}