1#![allow(clippy::needless_range_loop)]
2
3use core::{
8 array,
9 fmt::{self, Debug, Formatter},
10 marker::PhantomData as Pd,
11 ops::Range,
12};
13
14use crate::render::{NdcToScreen, ViewToProj};
15
16use super::{
17 approx::ApproxEq,
18 float::f32,
19 point::{Point2, Point2u, Point3, pt2},
20 space::{Linear, Proj3, Real},
21 vec::{ProjVec3, Vec2, Vec3, Vector, vec2, vec3},
22};
23
24pub trait LinearMap {
30 type Source;
32 type Dest;
34}
35
36pub trait Compose<Inner: LinearMap>: LinearMap<Source = Inner::Dest> {
41 type Result: LinearMap<Source = Inner::Source, Dest = Self::Dest>;
43}
44
45pub trait Apply<T> {
47 type Output;
49
50 #[must_use]
52 fn apply(&self, t: &T) -> Self::Output;
53}
54
55#[derive(Copy, Clone, Default, Eq, PartialEq)]
57pub struct RealToReal<const DIM: usize, SrcBasis = (), DstBasis = ()>(
58 Pd<(SrcBasis, DstBasis)>,
59);
60
61#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
63pub struct RealToProj<SrcBasis>(Pd<SrcBasis>);
64
65#[repr(transparent)]
67#[derive(Copy, Eq, PartialEq)]
68pub struct Matrix<Repr, Map>(pub Repr, Pd<Map>);
69
70pub type Mat2x2<Map = ()> = Matrix<[[f32; 2]; 2], Map>;
72pub type Mat3x3<Map = ()> = Matrix<[[f32; 3]; 3], Map>;
74pub type Mat4x4<Map = ()> = Matrix<[[f32; 4]; 4], Map>;
76
77#[macro_export]
99macro_rules! mat {
100 ( $( $( $elem:expr ),+ );+ $(;)? ) => {
101 $crate::math::mat::Matrix::new([
102 $([$($elem),+]),+
103 ])
104 };
105}
106
107impl<Repr, Map> Matrix<Repr, Map> {
108 #[inline]
110 pub const fn new(els: Repr) -> Self {
111 Self(els, Pd)
112 }
113
114 #[inline]
119 pub fn to<M>(&self) -> Matrix<Repr, M>
120 where
121 Repr: Clone,
122 {
123 Matrix(self.0.clone(), Pd)
124 }
125}
126
127impl<Sc, const N: usize, const M: usize, Map> Matrix<[[Sc; N]; M], Map>
128where
129 Sc: Copy,
130 Map: LinearMap,
131{
132 #[inline]
139 pub fn row_vec(&self, i: usize) -> Vector<[Sc; N], Map::Source> {
140 Vector::new(self.0[i])
141 }
142 #[inline]
149 pub fn col_vec(&self, i: usize) -> Vector<[Sc; M], Map::Dest> {
150 Vector::new(self.0.map(|row| row[i]))
151 }
152}
153impl<Sc: Copy, const N: usize, const DIM: usize, S, D>
154 Matrix<[[Sc; N]; N], RealToReal<DIM, S, D>>
155{
156 pub fn transpose(self) -> Matrix<[[Sc; N]; N], RealToReal<DIM, D, S>> {
158 const { assert!(N >= DIM, "map dimension >= matrix dimension") }
159 array::from_fn(|j| array::from_fn(|i| self.0[i][j])).into()
160 }
161}
162
163impl<const N: usize, Map> Matrix<[[f32; N]; N], Map> {
164 pub const fn identity() -> Self {
179 let mut els = [[0.0; N]; N];
180 let mut i = 0;
181 while i < N {
182 els[i][i] = 1.0;
183 i += 1;
184 }
185 Self::new(els)
186 }
187
188 fn is_finite(&self) -> bool {
191 self.0.iter().flatten().all(|e| e.is_finite())
192 }
193}
194
195impl Mat4x4 {
196 pub const fn from_linear<S, D>(
200 i: Vec3<D>,
201 j: Vec3<D>,
202 k: Vec3<D>,
203 ) -> Mat4x4<RealToReal<3, S, D>> {
204 Self::from_affine(i, j, k, Point3::origin())
205 }
206
207 pub const fn from_affine<S, D>(
211 i: Vec3<D>,
212 j: Vec3<D>,
213 k: Vec3<D>,
214 o: Point3<D>,
215 ) -> Mat4x4<RealToReal<3, S, D>> {
216 let (o, i, j, k) = (o.0, i.0, j.0, k.0);
217 mat![
218 i[0], j[0], k[0], o[0];
219 i[1], j[1], k[1], o[1];
220 i[2], j[2], k[2], o[2];
221 0.0, 0.0, 0.0, 1.0
222 ]
223 }
224}
225
226impl<Sc, const N: usize, Map> Matrix<[[Sc; N]; N], Map>
227where
228 Sc: Linear<Scalar = Sc> + Copy,
229 Map: LinearMap,
230{
231 #[must_use]
241 pub fn compose<Inner: LinearMap>(
242 &self,
243 other: &Matrix<[[Sc; N]; N], Inner>,
244 ) -> Matrix<[[Sc; N]; N], <Map as Compose<Inner>>::Result>
245 where
246 Map: Compose<Inner>,
247 {
248 let cols: [_; N] = array::from_fn(|i| other.col_vec(i));
249 array::from_fn(|j| {
250 let row = self.row_vec(j);
251 array::from_fn(|i| row.dot(&cols[i]))
252 })
253 .into()
254 }
255 #[must_use]
262 pub fn then<Outer: Compose<Map>>(
263 &self,
264 other: &Matrix<[[Sc; N]; N], Outer>,
265 ) -> Matrix<[[Sc; N]; N], <Outer as Compose<Map>>::Result> {
266 other.compose(self)
267 }
268}
269
270impl<Src, Dest> Mat2x2<RealToReal<2, Src, Dest>> {
271 pub const fn determinant(&self) -> f32 {
284 let [[a, b], [c, d]] = self.0;
285 a * d - b * c
286 }
287
288 #[must_use]
307 pub const fn checked_inverse(
308 &self,
309 ) -> Option<Mat2x2<RealToReal<2, Dest, Src>>> {
310 let det = self.determinant();
311 if det.abs() < 1e-6 {
313 return None;
314 }
315 let r_det = 1.0 / det;
316 let [[a, b], [c, d]] = self.0;
317 Some(mat![
318 r_det * d, r_det * -b;
319 r_det * -c, r_det * a
320 ])
321 }
322
323 #[must_use]
351 pub const fn inverse(&self) -> Mat2x2<RealToReal<2, Dest, Src>> {
352 self.checked_inverse()
353 .expect("matrix cannot be singular or near-singular")
354 }
355}
356
357impl<Src, Dest> Mat3x3<RealToReal<2, Src, Dest>> {
358 pub const fn determinant(&self) -> f32 {
360 let [a, b, c] = self.0[0];
361
362 a * self.cofactor(0, 0)
367 + b * self.cofactor(0, 1)
368 + c * self.cofactor(0, 2)
369 }
370
371 #[inline]
395 pub const fn cofactor(&self, row: usize, col: usize) -> f32 {
396 let r1 = (row + 1) % 3;
398 let r2 = (row + 2) % 3;
399 let c1 = (col + 1) % 3;
400 let c2 = (col + 2) % 3;
401 self.0[r1][c1] * self.0[r2][c2] - self.0[r1][c2] * self.0[r2][c1]
402 }
403
404 #[must_use]
406 pub fn checked_inverse(&self) -> Option<Mat3x3<RealToReal<2, Dest, Src>>> {
407 let det = self.determinant();
408 if det.abs() < 1e-6 {
409 return None;
410 }
411
412 let c_a = self.cofactor(0, 0); let c_b = self.cofactor(0, 1); let c_c = self.cofactor(0, 2); let c_d = self.cofactor(1, 0); let c_e = self.cofactor(1, 1); let c_f = self.cofactor(1, 2); let c_g = self.cofactor(2, 0); let c_h = self.cofactor(2, 1); let c_i = self.cofactor(2, 2); let r_det = 1.0 / det;
424 let abc = r_det * vec3(c_a, c_d, c_g);
426 let def = r_det * vec3(c_b, c_e, c_h);
427 let ghi = r_det * vec3(c_c, c_f, c_i);
428
429 Some(Mat3x3::from_rows(abc, def, ghi))
430 }
431
432 pub fn inverse(&self) -> Mat3x3<RealToReal<2, Dest, Src>> {
433 self.checked_inverse()
434 .expect("matrix cannot be singular or near-singular")
435 }
436
437 const fn from_rows(i: Vec3<Src>, j: Vec3<Src>, k: Vec3<Src>) -> Self {
438 Self::new([i.0, j.0, k.0])
439 }
440}
441
442impl<Src, Dst> Mat4x4<RealToReal<3, Src, Dst>> {
443 pub fn determinant(&self) -> f32 {
460 let [[a, b, c, d], r, s, t] = self.0;
461 let det2 = |m, n| s[m] * t[n] - s[n] * t[m];
462 let det3 =
463 |j, k, l| r[j] * det2(k, l) - r[k] * det2(j, l) + r[l] * det2(j, k);
464
465 a * det3(1, 2, 3) - b * det3(0, 2, 3) + c * det3(0, 1, 3)
466 - d * det3(0, 1, 2)
467 }
468
469 #[must_use]
492 pub fn inverse(&self) -> Mat4x4<RealToReal<3, Dst, Src>> {
493 use super::float::f32;
494 if cfg!(debug_assertions) {
495 let det = self.determinant();
496 assert!(
497 !det.approx_eq(&0.0),
498 "a singular, near-singular, or non-finite matrix does not \
499 have a well-defined inverse (determinant = {det})"
500 );
501 }
502
503 fn sub_row(m: &mut Mat4x4, from: usize, to: usize, mul: f32) {
506 m.0[to] = (m.row_vec(to) - m.row_vec(from) * mul).0;
507 }
508
509 fn mul_row(m: &mut Mat4x4, row: usize, mul: f32) {
511 m.0[row] = (m.row_vec(row) * mul).0;
512 }
513
514 fn swap_rows(m: &mut Mat4x4, r: usize, s: usize) {
516 m.0.swap(r, s);
517 }
518
519 let inv = &mut Mat4x4::identity();
526 let this = &mut self.to();
527
528 for idx in 0..4 {
530 let pivot = (idx..4)
531 .max_by(|&r1, &r2| {
532 let v1 = this.0[r1][idx].abs();
533 let v2 = this.0[r2][idx].abs();
534 v1.total_cmp(&v2)
535 })
536 .unwrap();
537
538 if this.0[pivot][idx] != 0.0 {
539 swap_rows(this, idx, pivot);
540 swap_rows(inv, idx, pivot);
541
542 let div = 1.0 / this.0[idx][idx];
543 for r in (idx + 1)..4 {
544 let x = this.0[r][idx] * div;
545 sub_row(this, idx, r, x);
546 sub_row(inv, idx, r, x);
547 }
548 }
549 }
550 for &idx in &[3, 2, 1] {
552 let diag = this.0[idx][idx];
553 for r in 0..idx {
554 let x = this.0[r][idx] / diag;
555
556 sub_row(this, idx, r, x);
557 sub_row(inv, idx, r, x);
558 }
559 }
560 for r in 0..4 {
562 let x = 1.0 / this.0[r][r];
563 mul_row(this, r, x);
564 mul_row(inv, r, x);
565 }
566 debug_assert!(inv.is_finite());
567 inv.to()
568 }
569}
570
571impl<const DIM: usize, S, D> LinearMap for RealToReal<DIM, S, D> {
576 type Source = Real<DIM, S>;
577 type Dest = Real<DIM, D>;
578}
579
580impl<const DIM: usize, S, I, D> Compose<RealToReal<DIM, S, I>>
581 for RealToReal<DIM, I, D>
582{
583 type Result = RealToReal<DIM, S, D>;
584}
585
586impl<S> LinearMap for RealToProj<S> {
587 type Source = Real<3, S>;
588 type Dest = Proj3;
589}
590
591impl<S, I> Compose<RealToReal<3, S, I>> for RealToProj<I> {
592 type Result = RealToProj<S>;
593}
594
595impl LinearMap for () {
597 type Source = ();
598 type Dest = ();
599}
600
601impl<Repr, E, M> ApproxEq<Self, E> for Matrix<Repr, M>
602where
603 Repr: ApproxEq<Repr, E>,
604{
605 fn approx_eq_eps(&self, other: &Self, rel_eps: &E) -> bool {
606 self.0.approx_eq_eps(&other.0, rel_eps)
607 }
608
609 fn relative_epsilon() -> E {
610 Repr::relative_epsilon()
611 }
612}
613
614impl<Src, Dest> Apply<Vec2<Src>> for Mat2x2<RealToReal<2, Src, Dest>> {
617 type Output = Vec2<Dest>;
618
619 fn apply(&self, v: &Vec2<Src>) -> Vec2<Dest> {
629 vec2(self.row_vec(0).dot(v), self.row_vec(1).dot(v))
630 }
631}
632
633impl<Src, Dest> Apply<Point2<Src>> for Mat2x2<RealToReal<2, Src, Dest>> {
634 type Output = Point2<Dest>;
635
636 fn apply(&self, pt: &Point2<Src>) -> Point2<Dest> {
646 self.apply(&pt.to_vec()).to_pt()
647 }
648}
649
650impl<Src, Dest> Apply<Vec2<Src>> for Mat3x3<RealToReal<2, Src, Dest>> {
651 type Output = Vec2<Dest>;
652
653 fn apply(&self, v: &Vec2<Src>) -> Vec2<Dest> {
664 let v = Vector::new([v.x(), v.y(), 0.0]);
666 vec2(self.row_vec(0).dot(&v), self.row_vec(1).dot(&v))
667 }
668}
669
670impl<Src, Dest> Apply<Point2<Src>> for Mat3x3<RealToReal<2, Src, Dest>> {
671 type Output = Point2<Dest>;
672
673 fn apply(&self, p: &Point2<Src>) -> Point2<Dest> {
684 let v = Vector::new([p.x(), p.y(), 1.0]);
685 pt2(self.row_vec(0).dot(&v), self.row_vec(1).dot(&v))
686 }
687}
688
689impl<Src, Dest> Apply<Vec3<Src>> for Mat3x3<RealToReal<3, Src, Dest>> {
690 type Output = Vec3<Dest>;
691
692 fn apply(&self, v: &Vec3<Src>) -> Vec3<Dest> {
703 vec3(
704 self.row_vec(0).dot(v),
705 self.row_vec(1).dot(v),
706 self.row_vec(2).dot(v),
707 )
708 }
709}
710
711impl<Src, Dest> Apply<Point3<Src>> for Mat3x3<RealToReal<3, Src, Dest>> {
712 type Output = Point3<Dest>;
713
714 fn apply(&self, p: &Point3<Src>) -> Point3<Dest> {
725 self.apply(&p.to_vec()).to_pt()
726 }
727}
728
729impl<Src, Dst> Apply<Vec3<Src>> for Mat4x4<RealToReal<3, Src, Dst>> {
730 type Output = Vec3<Dst>;
731
732 fn apply(&self, v: &Vec3<Src>) -> Vec3<Dst> {
744 let v = [v.x(), v.y(), v.z(), 0.0].into();
745 array::from_fn(|i| self.row_vec(i).dot(&v)).into()
746 }
747}
748
749impl<Src, Dst> Apply<Point3<Src>> for Mat4x4<RealToReal<3, Src, Dst>> {
750 type Output = Point3<Dst>;
751
752 fn apply(&self, p: &Point3<Src>) -> Point3<Dst> {
764 let p = [p.x(), p.y(), p.z(), 1.0].into();
765 array::from_fn(|i| self.row_vec(i).dot(&p)).into()
766 }
767}
768
769impl<Src> Apply<Point3<Src>> for Mat4x4<RealToProj<Src>> {
770 type Output = ProjVec3;
771
772 fn apply(&self, p: &Point3<Src>) -> ProjVec3 {
784 let v = Vector::new([p.x(), p.y(), p.z(), 1.0]);
785 array::from_fn(|i| self.row_vec(i).dot(&v)).into()
786 }
787}
788
789impl<R: Clone, M> Clone for Matrix<R, M> {
794 fn clone(&self) -> Self {
795 self.to()
796 }
797}
798
799impl<const N: usize, Map> Default for Matrix<[[f32; N]; N], Map> {
800 fn default() -> Self {
802 Self::identity()
803 }
804}
805
806impl<S: Debug, Map: Debug + Default, const N: usize, const M: usize> Debug
807 for Matrix<[[S; N]; M], Map>
808{
809 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
810 writeln!(f, "Matrix<{:?}>[", Map::default())?;
811 for i in 0..M {
812 writeln!(f, " {:4?}", self.0[i])?;
813 }
814 write!(f, "]")
815 }
816}
817
818impl<const DIM: usize, F, T> Debug for RealToReal<DIM, F, T>
819where
820 F: Debug + Default,
821 T: Debug + Default,
822{
823 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
824 write!(f, "{:?}→{:?}", F::default(), T::default())
825 }
826}
827
828impl<Repr, M> From<Repr> for Matrix<Repr, M> {
829 fn from(repr: Repr) -> Self {
830 Self(repr, Pd)
831 }
832}
833
834pub const fn scale(s: Vec3) -> Mat4x4<RealToReal<3>> {
849 scale3(s.0[0], s.0[1], s.0[2])
850}
851
852pub const fn scale3(x: f32, y: f32, z: f32) -> Mat4x4<RealToReal<3>> {
853 mat![
854 x, 0.0, 0.0, 0.0;
855 0.0, y, 0.0, 0.0;
856 0.0, 0.0, z, 0.0;
857 0.0, 0.0, 0.0, 1.0;
858 ]
859}
860
861pub const fn translate(t: Vec3) -> Mat4x4<RealToReal<3>> {
863 translate3(t.0[0], t.0[1], t.0[2])
864}
865
866pub const fn translate3(x: f32, y: f32, z: f32) -> Mat4x4<RealToReal<3>> {
867 mat![
868 1.0, 0.0, 0.0, x ;
869 0.0, 1.0, 0.0, y ;
870 0.0, 0.0, 1.0, z ;
871 0.0, 0.0, 0.0, 1.0;
872 ]
873}
874
875#[cfg(feature = "fp")]
876use super::Angle;
877
878#[cfg(feature = "fp")]
889pub fn orient_y(new_y: Vec3, x: Vec3) -> Mat4x4<RealToReal<3>> {
890 orient(new_y, x.cross(&new_y).normalize())
891}
892#[cfg(feature = "fp")]
903pub fn orient_z(new_z: Vec3, x: Vec3) -> Mat4x4<RealToReal<3>> {
904 orient(new_z.cross(&x).normalize(), new_z)
905}
906
907#[cfg(feature = "fp")]
917fn orient(new_y: Vec3, new_z: Vec3) -> Mat4x4<RealToReal<3>> {
918 let new_x = new_y.cross(&new_z);
919 assert!(
920 !new_x.len_sqr().approx_eq(&0.0),
921 "{new_y:?} × {new_z:?} non-finite or too close to zero vector"
922 );
923 Mat4x4::from_linear(new_x, new_y, new_z)
924}
925
926#[cfg(feature = "fp")]
930pub fn rotate_x(a: Angle) -> Mat4x4<RealToReal<3>> {
931 let (sin, cos) = a.sin_cos();
932 mat![
933 1.0, 0.0, 0.0, 0.0;
934 0.0, cos, sin, 0.0;
935 0.0, -sin, cos, 0.0;
936 0.0, 0.0, 0.0, 1.0;
937 ]
938}
939#[cfg(feature = "fp")]
941pub fn rotate_y(a: Angle) -> Mat4x4<RealToReal<3>> {
942 let (sin, cos) = a.sin_cos();
943 mat![
944 cos, 0.0, -sin, 0.0;
945 0.0, 1.0, 0.0, 0.0;
946 sin, 0.0, cos, 0.0;
947 0.0, 0.0, 0.0, 1.0;
948 ]
949}
950#[cfg(feature = "fp")]
952pub fn rotate_z(a: Angle) -> Mat4x4<RealToReal<3>> {
953 let (sin, cos) = a.sin_cos();
954 mat![
955 cos, sin, 0.0, 0.0;
956 -sin, cos, 0.0, 0.0;
957 0.0, 0.0, 1.0, 0.0;
958 0.0, 0.0, 0.0, 1.0;
959 ]
960}
961
962#[cfg(feature = "fp")]
964pub fn rotate2(a: Angle) -> Mat3x3<RealToReal<2>> {
965 let (sin, cos) = a.sin_cos();
966 mat![
967 cos, sin, 0.0;
968 -sin, cos, 0.0;
969 0.0, 0.0, 1.0;
970 ]
971}
972
973#[cfg(feature = "fp")]
975pub fn rotate(axis: Vec3, a: Angle) -> Mat4x4<RealToReal<3>> {
976 use crate::math::approx::ApproxEq;
977
978 let mut other = Vec3::X;
982 if axis.cross(&other).len_sqr().approx_eq(&0.0) {
983 other = Vec3::Y;
985 }
986
987 let z_to_axis = orient_z(axis.normalize(), other);
988 let axis_to_z = z_to_axis.transpose();
990 axis_to_z.then(&rotate_z(a)).then(&z_to_axis)
991}
992
993pub fn perspective(
1008 focal_ratio: f32,
1009 aspect_ratio: f32,
1010 near_far: Range<f32>,
1011) -> Mat4x4<ViewToProj> {
1012 let (near, far) = (near_far.start, near_far.end);
1013
1014 assert!(focal_ratio > 0.0, "focal ratio must be positive");
1015 assert!(aspect_ratio > 0.0, "aspect ratio must be positive");
1016 assert!(near > 0.0, "near must be positive");
1017 assert!(far > near, "far must be greater than near");
1018
1019 let e00 = focal_ratio;
1020 let e11 = e00 * aspect_ratio;
1021 let e22 = (far + near) / (far - near);
1022 let e23 = 2.0 * far * near / (near - far);
1023 mat![
1024 e00, 0.0, 0.0, 0.0;
1025 0.0, e11, 0.0, 0.0;
1026 0.0, 0.0, e22, e23;
1027 0.0, 0.0, 1.0, 0.0;
1028 ]
1029}
1030
1031pub fn orthographic(lbn: Point3, rtf: Point3) -> Mat4x4<ViewToProj> {
1037 let half_d = (rtf - lbn) / 2.0;
1038 let [cx, cy, cz] = (lbn + half_d).0;
1039 let [idx, idy, idz] = half_d.map(f32::recip).0;
1040 mat![
1041 idx, 0.0, 0.0, -cx * idx;
1042 0.0, idy, 0.0, -cy * idy;
1043 0.0, 0.0, idz, -cz * idz;
1044 0.0, 0.0, 0.0, 1.0;
1045 ]
1046}
1047
1048pub fn viewport(bounds: Range<Point2u>) -> Mat4x4<NdcToScreen> {
1054 let s = bounds.start.map(|c| c as f32);
1055 let e = bounds.end.map(|c| c as f32);
1056 let half_d = (e - s) / 2.0;
1057 let [dx, dy] = half_d.0;
1058 let [cx, cy] = (s + half_d).0;
1059 mat![
1060 dx, 0.0, 0.0, cx;
1061 0.0, dy, 0.0, cy;
1062 0.0, 0.0, 1.0, 0.0;
1063 0.0, 0.0, 0.0, 1.0;
1064 ]
1065}
1066
1067#[cfg(test)]
1068mod tests {
1069 use crate::assert_approx_eq;
1070 use crate::math::pt3;
1071
1072 #[cfg(feature = "fp")]
1073 use crate::math::degs;
1074
1075 use super::*;
1076
1077 #[derive(Debug, Default, Eq, PartialEq)]
1078 struct Basis1;
1079 #[derive(Debug, Default, Eq, PartialEq)]
1080 struct Basis2;
1081
1082 type Map<const N: usize = 3> = RealToReal<N, Basis1, Basis2>;
1083 type InvMap<const N: usize = 3> = RealToReal<N, Basis2, Basis1>;
1084
1085 const X: Vec3 = Vec3::X;
1086 const Y: Vec3 = Vec3::Y;
1087 const Z: Vec3 = Vec3::Z;
1088 const O: Vec3 = Vec3::new([0.0; 3]);
1089
1090 mod mat2x2 {
1091 use super::*;
1092
1093 #[test]
1094 fn determinant_of_identity_is_one() {
1095 let id = Mat2x2::<RealToReal<2>>::identity();
1096 assert_eq!(id.determinant(), 1.0);
1097 }
1098 #[test]
1099 fn determinant_of_reflection_is_negative_one() {
1100 let refl: Mat2x2<Map<2>> = [[0.0, 1.0], [1.0, 0.0]].into();
1101 assert_eq!(refl.determinant(), -1.0);
1102 }
1103
1104 #[test]
1105 fn inverse_of_identity_is_identity() {
1106 let id = Mat2x2::<RealToReal<2>>::identity();
1107 assert_eq!(id.inverse(), id);
1108 }
1109 #[test]
1110 fn inverse_of_inverse_is_original() {
1111 let m: Mat2x2<Map<2>> = [[0.5, 1.5], [1.0, -0.5]].into();
1112 let m_inv: Mat2x2<InvMap<2>> = m.inverse();
1113 assert_approx_eq!(m_inv.inverse(), m);
1114 }
1115 #[test]
1116 fn composition_of_inverse_is_identity() {
1117 let m: Mat2x2<Map<2>> = [[0.5, 1.5], [1.0, -0.5]].into();
1118 let m_inv: Mat2x2<InvMap<2>> = m.inverse();
1119 assert_approx_eq!(m.compose(&m_inv), Mat2x2::identity());
1120 assert_approx_eq!(m.then(&m_inv), Mat2x2::identity());
1121 }
1122 }
1123
1124 mod mat3x3 {
1125 use super::*;
1126
1127 const MAT: Mat3x3<Map> = mat![
1128 0.0, 1.0, 2.0;
1129 10.0, 11.0, 12.0;
1130 20.0, 21.0, 22.0;
1131 ];
1132
1133 #[test]
1134 fn row_col_vecs() {
1135 assert_eq!(MAT.row_vec(2), vec3::<_, Basis1>(20.0, 21.0, 22.0));
1136 assert_eq!(MAT.col_vec(2), vec3::<_, Basis2>(2.0, 12.0, 22.0));
1137 }
1138
1139 #[test]
1140 fn composition() {
1141 let tr: Mat3x3<Map<2>> = mat![
1142 1.0, 0.0, 2.0;
1143 0.0, 1.0, -3.0;
1144 0.0, 0.0, 1.0;
1145 ];
1146 let sc: Mat3x3<InvMap<2>> = mat![
1147 -1.0, 0.0, 0.0;
1148 0.0, 2.0, 0.0;
1149 0.0, 0.0, 1.0;
1150 ];
1151
1152 let tr_sc = tr.then(&sc);
1153 let sc_tr = sc.then(&tr);
1154
1155 assert_eq!(tr_sc, sc.compose(&tr));
1156 assert_eq!(sc_tr, tr.compose(&sc));
1157
1158 assert_eq!(tr_sc.apply(&vec2(1.0, 2.0)), vec2(-1.0, 4.0));
1159 assert_eq!(sc_tr.apply(&vec2(1.0, 2.0)), vec2(-1.0, 4.0));
1160
1161 assert_eq!(tr_sc.apply(&pt2(1.0, 2.0)), pt2(-3.0, -2.0));
1162 assert_eq!(sc_tr.apply(&pt2(1.0, 2.0)), pt2(1.0, 1.0));
1163 }
1164
1165 #[test]
1166 fn scaling() {
1167 let m: Mat3x3<Map<2>> = mat![
1168 2.0, 0.0, 0.0;
1169 0.0, -3.0, 0.0;
1170 0.0, 0.0, 1.0;
1171 ];
1172 assert_eq!(m.apply(&vec2(1.0, 2.0)), vec2(2.0, -6.0));
1173 assert_eq!(m.apply(&pt2(2.0, -1.0)), pt2(4.0, 3.0));
1174 }
1175
1176 #[test]
1177 fn translation() {
1178 let m: Mat3x3<Map<2>> = mat![
1179 1.0, 0.0, 2.0;
1180 0.0, 1.0, -3.0;
1181 0.0, 0.0, 1.0;
1182 ];
1183 assert_eq!(m.apply(&vec2(1.0, 2.0)), vec2(1.0, 2.0));
1184 assert_eq!(m.apply(&pt2(2.0, -1.0)), pt2(4.0, -4.0));
1185 }
1186
1187 #[test]
1188 fn inverse_of_identity_is_identity() {
1189 let i = Mat3x3::<RealToReal<_>>::identity();
1190 assert_eq!(i.inverse(), i);
1191 }
1192 #[test]
1193 fn inverse_of_scale_is_reciprocal_scale() {
1194 let scale: Mat3x3<Map<2>> = mat![
1195 2.0, 0.0, 0.0;
1196 0.0, -3.0, 0.0;
1197 0.0, 0.0, 4.0;
1198 ];
1199 assert_eq!(
1200 scale.inverse(),
1201 mat![
1202 1.0/2.0, 0.0, 0.0;
1203 0.0, -1.0/3.0, 0.0;
1204 0.0, 0.0, 1.0/4.0
1205 ]
1206 );
1207 }
1208 #[test]
1209 fn matrix_composed_with_inverse_is_identity() {
1210 let mat: Mat3x3<Map<2>> = mat![
1211 1.0, -2.0, 2.0;
1212 3.0, 4.0, -3.0;
1213 0.0, 0.0, 1.0;
1214 ];
1215 let composed = mat.compose(&mat.inverse());
1216 assert_approx_eq!(composed, Mat3x3::identity());
1217 }
1218
1219 #[test]
1220 fn singular_matrix_has_no_inverse() {
1221 let singular: Mat3x3<Map<2>> = mat![
1222 1.0, 2.0, 0.0;
1223 0.0, 0.0, 0.0;
1224 0.0, 0.0, 1.0;
1225 ];
1226
1227 assert_approx_eq!(singular.checked_inverse(), None);
1228 }
1229
1230 #[test]
1231 fn matrix_debug() {
1232 assert_eq!(
1233 alloc::format!("{MAT:?}"),
1234 r#"Matrix<Basis1→Basis2>[
1235 [ 0.0, 1.0, 2.0]
1236 [10.0, 11.0, 12.0]
1237 [20.0, 21.0, 22.0]
1238]"#
1239 );
1240 }
1241 }
1242
1243 mod mat4x4 {
1244 use super::*;
1245
1246 const MAT: Mat4x4<Map> = mat![
1247 0.0, 1.0, 2.0, 3.0;
1248 10.0, 11.0, 12.0, 13.0;
1249 20.0, 21.0, 22.0, 23.0;
1250 30.0, 31.0, 32.0, 33.0;
1251 ];
1252
1253 #[test]
1254 fn row_col_vecs() {
1255 assert_eq!(MAT.row_vec(1), [10.0, 11.0, 12.0, 13.0].into());
1256 assert_eq!(MAT.col_vec(3), [3.0, 13.0, 23.0, 33.0].into());
1257 }
1258
1259 #[test]
1260 fn composition() {
1261 let t = translate3(1.0, 2.0, 3.0).to::<Map>();
1262 let s = scale3(3.0, 2.0, 1.0).to::<InvMap>();
1263
1264 let ts = t.then(&s);
1265 let st = s.then(&t);
1266
1267 assert_eq!(ts, s.compose(&t));
1268 assert_eq!(st, t.compose(&s));
1269
1270 let o = <Point3>::origin();
1271 assert_eq!(ts.apply(&o.to()), pt3::<_, Basis1>(3.0, 4.0, 3.0));
1272 assert_eq!(st.apply(&o.to()), pt3::<_, Basis2>(1.0, 2.0, 3.0));
1273 }
1274
1275 #[test]
1276 fn scaling() {
1277 let m = scale3(1.0, -2.0, 3.0);
1278
1279 let v = vec3(0.0, 4.0, -3.0);
1280 assert_eq!(m.apply(&v), vec3(0.0, -8.0, -9.0));
1281
1282 let p = pt3(4.0, 0.0, -3.0);
1283 assert_eq!(m.apply(&p), pt3(4.0, 0.0, -9.0));
1284 }
1285
1286 #[test]
1287 fn translation() {
1288 let m = translate3(1.0, 2.0, 3.0);
1289
1290 let v = vec3(0.0, 5.0, -3.0);
1291 assert_eq!(m.apply(&v), vec3(0.0, 5.0, -3.0));
1292
1293 let p = pt3(3.0, 5.0, 0.0);
1294 assert_eq!(m.apply(&p), pt3(4.0, 7.0, 3.0));
1295 }
1296
1297 #[cfg(feature = "fp")]
1298 #[test]
1299 fn rotation_x() {
1300 let m = rotate_x(degs(90.0));
1301
1302 assert_eq!(m.apply(&O), O);
1303
1304 assert_approx_eq!(m.apply(&Z), Y);
1305 assert_approx_eq!(
1306 m.apply(&pt3(0.0, -2.0, 0.0)),
1307 pt3(0.0, 0.0, 2.0)
1308 );
1309 }
1310
1311 #[cfg(feature = "fp")]
1312 #[test]
1313 fn rotation_y() {
1314 let m = rotate_y(degs(90.0));
1315
1316 assert_eq!(m.apply(&O), O);
1317
1318 assert_approx_eq!(m.apply(&X), Z);
1319 assert_approx_eq!(
1320 m.apply(&pt3(0.0, 0.0, -2.0)),
1321 pt3(2.0, 0.0, 0.0)
1322 );
1323 }
1324
1325 #[cfg(feature = "fp")]
1326 #[test]
1327 fn rotation_z() {
1328 let m = rotate_z(degs(90.0));
1329
1330 assert_eq!(m.apply(&O), O);
1331
1332 assert_approx_eq!(m.apply(&Y), X);
1333 assert_approx_eq!(
1334 m.apply(&(pt3(-2.0, 0.0, 0.0))),
1335 pt3(0.0, 2.0, 0.0)
1336 );
1337 }
1338
1339 #[cfg(feature = "fp")]
1340 #[test]
1341 fn rotation_arbitrary() {
1342 let m = rotate(vec3(1.0, 1.0, 0.0).normalize(), degs(180.0));
1343
1344 assert_approx_eq!(m.apply(&X), Y);
1345 assert_approx_eq!(m.apply(&Y), X);
1346 assert_approx_eq!(m.apply(&Z), -Z);
1347 }
1348
1349 #[cfg(feature = "fp")]
1350 #[test]
1351 fn rotation_arbitrary_x() {
1352 let a = rotate(X, degs(128.0));
1353 let b = rotate_x(degs(128.0));
1354 assert_eq!(a, b);
1355 }
1356 #[cfg(feature = "fp")]
1357 #[test]
1358 fn rotation_arbitrary_y() {
1359 let a = rotate(Y, degs(128.0));
1360 let b = rotate_y(degs(128.0));
1361 assert_eq!(a, b);
1362 }
1363 #[cfg(feature = "fp")]
1364 #[test]
1365 fn rotation_arbitrary_z() {
1366 let a = rotate(Z, degs(128.0));
1367 let b = rotate_z(degs(128.0));
1368 assert_eq!(a, b);
1369 }
1370
1371 #[test]
1372 fn from_basis() {
1373 let m = Mat4x4::from_linear(Y, 2.0 * Z, -3.0 * X);
1374 assert_eq!(m.apply(&X), Y);
1375 assert_eq!(m.apply(&Y), 2.0 * Z);
1376 assert_eq!(m.apply(&Z), -3.0 * X);
1377 }
1378
1379 #[cfg(feature = "fp")]
1380 #[test]
1381 fn orientation_no_op() {
1382 let m = orient_y(Y, X);
1383
1384 assert_eq!(m.apply(&X), X);
1385 assert_eq!(m.apply(&X.to_pt()), X.to_pt());
1386
1387 assert_eq!(m.apply(&Y), Y);
1388 assert_eq!(m.apply(&Y.to_pt()), Y.to_pt());
1389
1390 assert_eq!(m.apply(&Z), Z);
1391 assert_eq!(m.apply(&Z.to_pt()), Z.to_pt());
1392 }
1393
1394 #[cfg(feature = "fp")]
1395 #[test]
1396 fn orientation_y_to_z() {
1397 let m = orient_y(Z, X);
1398
1399 assert_eq!(m.apply(&X), X);
1400 assert_eq!(m.apply(&X.to_pt()), X.to_pt());
1401
1402 assert_eq!(m.apply(&Y), Z);
1403 assert_eq!(m.apply(&Y.to_pt()), Z.to_pt());
1404
1405 assert_eq!(m.apply(&Z), -Y);
1406 assert_eq!(m.apply(&Z.to_pt()), (-Y).to_pt());
1407 }
1408
1409 #[cfg(feature = "fp")]
1410 #[test]
1411 fn orientation_z_to_y() {
1412 let m = orient_z(Y, X);
1413
1414 assert_eq!(m.apply(&X), X);
1415 assert_eq!(m.apply(&X.to_pt()), X.to_pt());
1416
1417 assert_eq!(m.apply(&Y), -Z);
1418 assert_eq!(m.apply(&Y.to_pt()), (-Z).to_pt());
1419
1420 assert_eq!(m.apply(&Z), Y);
1421 assert_eq!(m.apply(&Z.to_pt()), Y.to_pt());
1422 }
1423
1424 #[test]
1425 fn matrix_debug() {
1426 assert_eq!(
1427 alloc::format!("{MAT:?}"),
1428 r#"Matrix<Basis1→Basis2>[
1429 [ 0.0, 1.0, 2.0, 3.0]
1430 [10.0, 11.0, 12.0, 13.0]
1431 [20.0, 21.0, 22.0, 23.0]
1432 [30.0, 31.0, 32.0, 33.0]
1433]"#
1434 );
1435 }
1436 }
1437
1438 #[test]
1439 fn transposition() {
1440 let m = Matrix::<_, Map>::new([
1441 [0.0, 1.0, 2.0], [10.0, 11.0, 12.0],
1443 [20.0, 21.0, 22.0],
1444 ]);
1445 assert_eq!(
1446 m.transpose(),
1447 Matrix::<_, InvMap>::new([
1448 [0.0, 10.0, 20.0], [1.0, 11.0, 21.0],
1450 [2.0, 12.0, 22.0],
1451 ])
1452 );
1453 }
1454
1455 #[test]
1456 fn determinant_of_identity_is_one() {
1457 let id: Mat4x4<Map> = Mat4x4::identity();
1458 assert_eq!(id.determinant(), 1.0);
1459 }
1460
1461 #[test]
1462 fn determinant_of_scaling_is_product_of_diagonal() {
1463 let scale: Mat4x4<_> = scale3(2.0, 3.0, 4.0);
1464 assert_eq!(scale.determinant(), 24.0);
1465 }
1466
1467 #[cfg(feature = "fp")]
1468 #[test]
1469 fn determinant_of_rotation_is_one() {
1470 let rot = rotate_x(degs(73.0)).then(&rotate_y(degs(-106.0)));
1471 assert_approx_eq!(rot.determinant(), 1.0);
1472 }
1473
1474 #[cfg(feature = "fp")]
1475 #[test]
1476 fn matrix_composed_with_inverse_is_identity() {
1477 let m = translate3(1.0e3, -2.0e2, 0.0)
1478 .then(&scale3(0.5, 100.0, 42.0))
1479 .to::<Map>();
1480
1481 let m_inv: Mat4x4<InvMap> = m.inverse();
1482
1483 assert_eq!(
1484 m.compose(&m_inv),
1485 Mat4x4::<RealToReal<3, Basis2, Basis2>>::identity()
1486 );
1487 assert_eq!(
1488 m_inv.compose(&m),
1489 Mat4x4::<RealToReal<3, Basis1, Basis1>>::identity()
1490 );
1491 }
1492
1493 #[test]
1494 fn inverse_reverts_transform() {
1495 let m: Mat4x4<Map> = scale3(1.0, 2.0, 0.5)
1496 .then(&translate3(-2.0, 3.0, 0.0))
1497 .to();
1498 let m_inv: Mat4x4<InvMap> = m.inverse();
1499
1500 let v1: Vec3<Basis1> = vec3(1.0, -2.0, 3.0);
1501 let v2: Vec3<Basis2> = vec3(2.0, 0.0, -2.0);
1502
1503 assert_eq!(m_inv.apply(&m.apply(&v1)), v1);
1504 assert_eq!(m.apply(&m_inv.apply(&v2)), v2);
1505 }
1506
1507 #[test]
1508 fn orthographic_box_maps_to_unit_cube() {
1509 let lbn = pt3(-20.0, 0.0, 0.01);
1510 let rtf = pt3(100.0, 50.0, 100.0);
1511
1512 let m = orthographic(lbn, rtf);
1513
1514 assert_approx_eq!(m.apply(&lbn.to()), [-1.0, -1.0, -1.0, 1.0].into());
1515 assert_approx_eq!(m.apply(&rtf.to()), [1.0, 1.0, 1.0, 1.0].into());
1516 }
1517
1518 #[test]
1519 fn perspective_frustum_maps_to_unit_cube() {
1520 let left_bot_near = pt3(-0.125, -0.0625, 0.1);
1521 let right_top_far = pt3(125.0, 62.5, 100.0);
1522
1523 let m = perspective(0.8, 2.0, 0.1..100.0);
1524
1525 let lbn = m.apply(&left_bot_near);
1526 assert_approx_eq!(lbn / lbn.w(), [-1.0, -1.0, -1.0, 1.0].into());
1527
1528 let rtf = m.apply(&right_top_far);
1529 assert_approx_eq!(rtf / rtf.w(), [1.0, 1.0, 1.0, 1.0].into());
1530 }
1531
1532 #[test]
1533 fn viewport_maps_ndc_to_screen() {
1534 let m = viewport(pt2(20, 10)..pt2(620, 470));
1535
1536 assert_eq!(m.apply(&pt3(-1.0, -1.0, 0.2)), pt3(20.0, 10.0, 0.2));
1537 assert_eq!(m.apply(&pt3(1.0, 1.0, 0.6)), pt3(620.0, 470.0, 0.6));
1538 }
1539}