1#![allow(clippy::needless_range_loop)]
2
3use core::array;
6use core::fmt::{self, Debug, Formatter};
7use core::marker::PhantomData;
8use core::ops::Range;
9
10use crate::render::{NdcToScreen, ViewToProj};
11
12use super::space::{Linear, Proj4, Real};
13use super::vec::{ProjVec4, Vec2, Vec2u, Vec3, Vector};
14
15pub trait LinearMap {
21 type Source;
23 type Dest;
25}
26
27pub trait Compose<Inner: LinearMap>: LinearMap<Source = Inner::Dest> {
31 type Result: LinearMap<Source = Inner::Source, Dest = Self::Dest>;
33}
34
35#[derive(Copy, Clone, Default, Eq, PartialEq)]
37pub struct RealToReal<const DIM: usize, SrcBasis = (), DstBasis = ()>(
38 PhantomData<(SrcBasis, DstBasis)>,
39);
40
41#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
43pub struct RealToProj<SrcBasis>(PhantomData<SrcBasis>);
44
45#[repr(transparent)]
47#[derive(Copy, Eq, PartialEq)]
48pub struct Matrix<Repr, Map>(pub Repr, PhantomData<Map>);
49
50pub type Mat3x3<Map = ()> = Matrix<[[f32; 3]; 3], Map>;
52pub type Mat4x4<Map = ()> = Matrix<[[f32; 4]; 4], Map>;
54
55impl<Repr, Map> Matrix<Repr, Map> {
60 #[inline]
62 pub const fn new(els: Repr) -> Self {
63 Self(els, PhantomData)
64 }
65
66 #[inline]
71 pub fn to<M>(&self) -> Matrix<Repr, M>
72 where
73 Repr: Clone,
74 {
75 Matrix::new(self.0.clone())
76 }
77}
78
79impl<Sc, const N: usize, const M: usize, Map> Matrix<[[Sc; N]; M], Map>
80where
81 Sc: Copy,
82 Map: LinearMap,
83{
84 #[inline]
90 pub fn row_vec(&self, i: usize) -> Vector<[Sc; N], Map::Source> {
91 Vector::new(self.0[i])
92 }
93 #[inline]
100 pub fn col_vec(&self, i: usize) -> Vector<[Sc; M], Map::Dest> {
101 Vector::new(self.0.map(|row| row[i]))
102 }
103}
104impl<Sc: Copy, const N: usize, const DIM: usize, S, D>
105 Matrix<[[Sc; N]; N], RealToReal<DIM, S, D>>
106{
107 pub fn transpose(self) -> Matrix<[[Sc; N]; N], RealToReal<DIM, D, S>> {
109 const { assert!(N >= DIM, "map dimension >= matrix dimension") }
110 array::from_fn(|j| array::from_fn(|i| self.0[i][j])).into()
111 }
112}
113
114impl<const N: usize, Map> Matrix<[[f32; N]; N], Map> {
115 pub const fn identity() -> Self {
117 let mut els = [[0.0; N]; N];
118 let mut i = 0;
119 while i < N {
120 els[i][i] = 1.0;
121 i += 1;
122 }
123 Self::new(els)
124 }
125
126 fn is_finite(&self) -> bool {
129 self.0.iter().flatten().all(|e| e.is_finite())
130 }
131}
132
133impl<M: LinearMap> Mat4x4<M> {
134 pub const fn from_basis(i: Vec3, j: Vec3, k: Vec3) -> Self {
136 Self::new([
137 [i.0[0], i.0[1], i.0[2], 0.0],
138 [j.0[0], j.0[1], j.0[2], 0.0],
139 [k.0[0], k.0[1], k.0[2], 0.0],
140 [0.0, 0.0, 0.0, 1.0],
141 ])
142 }
143}
144
145impl<Sc, const N: usize, Map> Matrix<[[Sc; N]; N], Map>
146where
147 Sc: Linear<Scalar = Sc> + Copy,
148 Map: LinearMap,
149{
150 #[must_use]
160 pub fn compose<Inner: LinearMap>(
161 &self,
162 other: &Matrix<[[Sc; N]; N], Inner>,
163 ) -> Matrix<[[Sc; N]; N], <Map as Compose<Inner>>::Result>
164 where
165 Map: Compose<Inner>,
166 {
167 let cols: [_; N] = array::from_fn(|i| other.col_vec(i));
168 array::from_fn(|j| {
169 let row = self.row_vec(j);
170 array::from_fn(|i| row.dot(&cols[i]))
171 })
172 .into()
173 }
174 #[must_use]
181 pub fn then<Outer: Compose<Map>>(
182 &self,
183 other: &Matrix<[[Sc; N]; N], Outer>,
184 ) -> Matrix<[[Sc; N]; N], <Outer as Compose<Map>>::Result> {
185 other.compose(self)
186 }
187}
188
189impl<Src, Dst> Mat3x3<RealToReal<2, Src, Dst>> {
190 #[must_use]
201 pub fn apply(&self, v: &Vec2<Src>) -> Vec2<Dst> {
202 let v = [v.x(), v.y(), 1.0].into();
203 array::from_fn(|i| self.row_vec(i).dot(&v)).into()
204 }
205}
206
207impl<Src, Dst> Mat4x4<RealToReal<3, Src, Dst>> {
208 #[must_use]
220 pub fn apply(&self, v: &Vec3<Src>) -> Vec3<Dst> {
221 let v = [v.x(), v.y(), v.z(), 1.0].into();
222 array::from_fn(|i| self.row_vec(i).dot(&v)).into()
223 }
224
225 pub fn determinant(&self) -> f32 {
243 let [[a, b, c, d], r, s, t] = self.0;
244 let det2 = |m, n| s[m] * t[n] - s[n] * t[m];
245 let det3 =
246 |j, k, l| r[j] * det2(k, l) - r[k] * det2(j, l) + r[l] * det2(j, k);
247
248 a * det3(1, 2, 3) - b * det3(0, 2, 3) + c * det3(0, 1, 3)
249 - d * det3(0, 1, 2)
250 }
251
252 #[must_use]
276 pub fn inverse(&self) -> Mat4x4<RealToReal<3, Dst, Src>> {
277 use super::float::f32;
278 if cfg!(debug_assertions) {
279 let det = self.determinant();
280 assert!(
281 f32::abs(det) > f32::EPSILON,
282 "a singular, near-singular, or non-finite matrix does not \
283 have a well-defined inverse (determinant = {det})"
284 );
285 }
286
287 fn sub_row(m: &mut Mat4x4, from: usize, to: usize, mul: f32) {
290 m.0[to] = (m.row_vec(to) - m.row_vec(from) * mul).0;
291 }
292
293 fn mul_row(m: &mut Mat4x4, row: usize, mul: f32) {
295 m.0[row] = (m.row_vec(row) * mul).0;
296 }
297
298 fn swap_rows(m: &mut Mat4x4, r: usize, s: usize) {
300 m.0.swap(r, s);
301 }
302
303 let inv = &mut Mat4x4::identity();
310 let this = &mut self.to();
311
312 for idx in 0..4 {
314 let pivot = (idx..4)
315 .max_by(|&r1, &r2| {
316 let v1 = f32::abs(this.0[r1][idx]);
317 let v2 = f32::abs(this.0[r2][idx]);
318 v1.partial_cmp(&v2).unwrap()
319 })
320 .unwrap();
321
322 if this.0[pivot][idx] != 0.0 {
323 swap_rows(this, idx, pivot);
324 swap_rows(inv, idx, pivot);
325
326 let div = 1.0 / this.0[idx][idx];
327 for r in (idx + 1)..4 {
328 let x = this.0[r][idx] * div;
329 sub_row(this, idx, r, x);
330 sub_row(inv, idx, r, x);
331 }
332 }
333 }
334 for &idx in &[3, 2, 1] {
336 let diag = this.0[idx][idx];
337 for r in 0..idx {
338 let x = this.0[r][idx] / diag;
339
340 sub_row(this, idx, r, x);
341 sub_row(inv, idx, r, x);
342 }
343 }
344 for r in 0..4 {
346 let x = 1.0 / this.0[r][r];
347 mul_row(this, r, x);
348 mul_row(inv, r, x);
349 }
350 debug_assert!(inv.is_finite());
351 inv.to()
352 }
353}
354
355impl<Src> Mat4x4<RealToProj<Src>> {
356 #[must_use]
368 pub fn apply(&self, v: &Vec3<Src>) -> ProjVec4 {
369 let v = Vector::from([v.x(), v.y(), v.z(), 1.0]);
370 [
371 self.row_vec(0).dot(&v),
372 self.row_vec(1).dot(&v),
373 self.row_vec(2).dot(&v),
374 self.row_vec(3).dot(&v),
375 ]
376 .into()
377 }
378}
379
380impl<const DIM: usize, S, D> LinearMap for RealToReal<DIM, S, D> {
385 type Source = Real<DIM, S>;
386 type Dest = Real<DIM, D>;
387}
388
389impl<const DIM: usize, S, I, D> Compose<RealToReal<DIM, S, I>>
390 for RealToReal<DIM, I, D>
391{
392 type Result = RealToReal<DIM, S, D>;
393}
394
395impl<S> LinearMap for RealToProj<S> {
396 type Source = Real<3, S>;
397 type Dest = Proj4;
398}
399
400impl<S, I> Compose<RealToReal<3, S, I>> for RealToProj<I> {
401 type Result = RealToProj<S>;
402}
403
404impl LinearMap for () {
406 type Source = ();
407 type Dest = ();
408}
409
410impl<R: Clone, M> Clone for Matrix<R, M> {
415 fn clone(&self) -> Self {
416 self.to()
417 }
418}
419
420impl<const N: usize, Map> Default for Matrix<[[f32; N]; N], Map> {
421 fn default() -> Self {
423 Self::identity()
424 }
425}
426
427impl<S: Debug, Map: Debug + Default, const N: usize, const M: usize> Debug
428 for Matrix<[[S; N]; M], Map>
429{
430 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
431 writeln!(f, "Matrix<{:?}>[", Map::default())?;
432 for i in 0..M {
433 writeln!(f, " {:6.2?}", self.0[i])?;
434 }
435 write!(f, "]")
436 }
437}
438
439impl<const DIM: usize, F, T> Debug for RealToReal<DIM, F, T>
440where
441 F: Debug + Default,
442 T: Debug + Default,
443{
444 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
445 write!(f, "{:?}→{:?}", F::default(), T::default())
446 }
447}
448
449impl<Repr, M> From<Repr> for Matrix<Repr, M> {
450 fn from(repr: Repr) -> Self {
451 Self(repr, PhantomData)
452 }
453}
454
455pub const fn scale(s: Vec3) -> Mat4x4<RealToReal<3>> {
470 let [x, y, z] = s.0;
471 Matrix::new([
472 [x, 0.0, 0.0, 0.0],
473 [0.0, y, 0.0, 0.0],
474 [0.0, 0.0, z, 0.0],
475 [0.0, 0.0, 0.0, 1.0],
476 ])
477}
478
479pub const fn translate(t: Vec3) -> Mat4x4<RealToReal<3>> {
481 let [x, y, z] = t.0;
482 Matrix::new([
483 [1.0, 0.0, 0.0, x],
484 [0.0, 1.0, 0.0, y],
485 [0.0, 0.0, 1.0, z],
486 [0.0, 0.0, 0.0, 1.0],
487 ])
488}
489
490#[cfg(feature = "fp")]
497pub fn orient_y(new_y: Vec3, x: Vec3) -> Mat4x4<RealToReal<3>> {
498 orient(new_y, x.cross(&new_y).normalize())
499}
500#[cfg(feature = "fp")]
507pub fn orient_z(new_z: Vec3, x: Vec3) -> Mat4x4<RealToReal<3>> {
508 orient(new_z.cross(&x).normalize(), new_z)
509}
510
511#[cfg(feature = "fp")]
512fn orient(new_y: Vec3, new_z: Vec3) -> Mat4x4<RealToReal<3>> {
513 use crate::math::{space::Linear, ApproxEq};
514
515 assert!(!new_y.approx_eq(&Vec3::zero()));
516 assert!(!new_z.approx_eq(&Vec3::zero()));
517
518 let new_x = new_y.cross(&new_z);
519 Mat4x4::from_basis(new_x, new_y, new_z)
520}
521
522#[cfg(feature = "fp")]
526pub fn rotate_x(a: super::angle::Angle) -> Mat4x4<RealToReal<3>> {
527 let (sin, cos) = a.sin_cos();
528 [
529 [1.0, 0.0, 0.0, 0.0],
530 [0.0, cos, sin, 0.0],
531 [0.0, -sin, cos, 0.0],
532 [0.0, 0.0, 0.0, 1.0],
533 ]
534 .into()
535}
536#[cfg(feature = "fp")]
538pub fn rotate_y(a: super::angle::Angle) -> Mat4x4<RealToReal<3>> {
539 let (sin, cos) = a.sin_cos();
540 [
541 [cos, 0.0, -sin, 0.0],
542 [0.0, 1.0, 0.0, 0.0],
543 [sin, 0.0, cos, 0.0],
544 [0.0, 0.0, 0.0, 1.0],
545 ]
546 .into()
547}
548#[cfg(feature = "fp")]
550pub fn rotate_z(a: super::angle::Angle) -> Mat4x4<RealToReal<3>> {
551 let (sin, cos) = a.sin_cos();
552 [
553 [cos, sin, 0.0, 0.0],
554 [-sin, cos, 0.0, 0.0],
555 [0.0, 0.0, 1.0, 0.0],
556 [0.0, 0.0, 0.0, 1.0],
557 ]
558 .into()
559}
560
561pub fn perspective(
576 focal_ratio: f32,
577 aspect_ratio: f32,
578 near_far: Range<f32>,
579) -> Mat4x4<ViewToProj> {
580 let (near, far) = (near_far.start, near_far.end);
581
582 assert!(focal_ratio > 0.0, "focal ratio must be positive");
583 assert!(aspect_ratio > 0.0, "aspect ratio must be positive");
584 assert!(near > 0.0, "near must be positive");
585 assert!(!near_far.is_empty(), "far must be greater than near");
586
587 let e00 = focal_ratio;
588 let e11 = e00 * aspect_ratio;
589 let e22 = (far + near) / (far - near);
590 let e23 = 2.0 * far * near / (near - far);
591 [
592 [e00, 0.0, 0.0, 0.0],
593 [0.0, e11, 0.0, 0.0],
594 [0.0, 0.0, e22, e23],
595 [0.0, 0.0, 1.0, 0.0],
596 ]
597 .into()
598}
599
600pub fn orthographic(lbn: Vec3, rtf: Vec3) -> Mat4x4<ViewToProj> {
606 let [dx, dy, dz] = (rtf - lbn).0;
607 let [sx, sy, sz] = (rtf + lbn).0;
608 [
609 [2.0 / dx, 0.0, 0.0, -sx / dx],
610 [0.0, 2.0 / dy, 0.0, -sy / dy],
611 [0.0, 0.0, 2.0 / dz, -sz / dz],
612 [0.0, 0.0, 0.0, 1.0],
613 ]
614 .into()
615}
616
617pub fn viewport(bounds: Range<Vec2u>) -> Mat4x4<NdcToScreen> {
623 let Range { start, end } = bounds;
624 let h = (end.x() - start.x()) as f32 / 2.0;
625 let v = (end.y() - start.y()) as f32 / 2.0;
626 [
627 [h, 0.0, 0.0, h + start.x() as f32],
628 [0.0, v, 0.0, v + start.y() as f32],
629 [0.0, 0.0, 1.0, 0.0],
630 [0.0, 0.0, 0.0, 1.0],
631 ]
632 .into()
633}
634
635#[cfg(test)]
636mod tests {
637 use crate::assert_approx_eq;
638 use crate::math::vec::{splat, vec2, vec3};
639
640 #[cfg(feature = "fp")]
641 use crate::math::angle::degs;
642
643 use super::*;
644
645 #[derive(Debug, Default, Eq, PartialEq)]
646 struct Basis1;
647 #[derive(Debug, Default, Eq, PartialEq)]
648 struct Basis2;
649
650 type Map<const N: usize = 3> = RealToReal<N, Basis1, Basis2>;
651 type InvMap<const N: usize = 3> = RealToReal<N, Basis2, Basis1>;
652
653 mod mat3x3 {
654 use super::*;
655
656 const MAT: Mat3x3<Map> = Matrix::new([
657 [0.0, 1.0, 2.0], [10.0, 11.0, 12.0],
659 [20.0, 21.0, 22.0],
660 ]);
661
662 #[test]
663 fn row_col_vecs() {
664 assert_eq!(MAT.row_vec(2), vec3::<_, Basis1>(20.0, 21.0, 22.0));
665 assert_eq!(MAT.col_vec(2), vec3::<_, Basis2>(2.0, 12.0, 22.0));
666 }
667
668 #[test]
669 fn composition() {
670 let t = Mat3x3::<Map<2>>::new([
671 [1.0, 0.0, 2.0], [0.0, 1.0, -3.0],
673 [0.0, 0.0, 1.0],
674 ]);
675 let s = Mat3x3::<InvMap<2>>::new([
676 [-1.0, 0.0, 0.0], [0.0, 2.0, 0.0],
678 [0.0, 0.0, 1.0],
679 ]);
680
681 let ts = t.then(&s);
682 let st = s.then(&t);
683
684 assert_eq!(ts, s.compose(&t));
685 assert_eq!(st, t.compose(&s));
686
687 assert_eq!(ts.apply(&splat(0.0)), vec2(-2.0, -6.0));
688 assert_eq!(st.apply(&splat(0.0)), vec2(2.0, -3.0));
689 }
690
691 #[test]
692 fn scaling() {
693 let m = Mat3x3::<Map<2>>::new([
694 [2.0, 0.0, 0.0], [0.0, -3.0, 0.0],
696 [0.0, 0.0, 1.0],
697 ]);
698 assert_eq!(m.apply(&vec2(1.0, 2.0)), vec2(2.0, -6.0));
699 }
700
701 #[test]
702 fn translation() {
703 let m = Mat3x3::<Map<2>>::new([
704 [1.0, 0.0, 2.0], [0.0, 1.0, -3.0],
706 [0.0, 0.0, 1.0],
707 ]);
708 assert_eq!(m.apply(&vec2(1.0, 2.0)), vec2(3.0, -1.0));
709 }
710
711 #[test]
712 fn matrix_debug() {
713 assert_eq!(
714 alloc::format!("{MAT:?}"),
715 r#"Matrix<Basis1→Basis2>[
716 [ 0.00, 1.00, 2.00]
717 [ 10.00, 11.00, 12.00]
718 [ 20.00, 21.00, 22.00]
719]"#
720 );
721 }
722 }
723
724 mod mat4x4 {
725 use super::*;
726
727 const MAT: Mat4x4<Map> = Matrix::new([
728 [0.0, 1.0, 2.0, 3.0],
729 [10.0, 11.0, 12.0, 13.0],
730 [20.0, 21.0, 22.0, 23.0],
731 [30.0, 31.0, 32.0, 33.0],
732 ]);
733
734 #[test]
735 fn row_col_vecs() {
736 assert_eq!(MAT.row_vec(1), [10.0, 11.0, 12.0, 13.0].into());
737 assert_eq!(MAT.col_vec(3), [3.0, 13.0, 23.0, 33.0].into());
738 }
739
740 #[test]
741 fn composition() {
742 let t = translate(vec3(1.0, 2.0, 3.0)).to::<Map>();
743 let s = scale(vec3(3.0, 2.0, 1.0)).to::<InvMap>();
744
745 let ts = t.then(&s);
746 let st = s.then(&t);
747
748 assert_eq!(ts, s.compose(&t));
749 assert_eq!(st, t.compose(&s));
750
751 assert_eq!(ts.apply(&splat(0.0)), vec3::<_, Basis1>(3.0, 4.0, 3.0));
752 assert_eq!(st.apply(&splat(0.0)), vec3::<_, Basis2>(1.0, 2.0, 3.0));
753 }
754
755 #[test]
756 fn scaling_vec3() {
757 let m = scale(vec3(1.0, -2.0, 3.0));
758 let v = vec3(0.0, 4.0, -3.0);
759 assert_eq!(m.apply(&v), vec3(0.0, -8.0, -9.0));
760 }
761
762 #[test]
763 fn translation_vec3() {
764 let m = translate(vec3(1.0, 2.0, 3.0));
765 let v = vec3(0.0, 5.0, -3.0);
766 assert_eq!(m.apply(&v), vec3(1.0, 7.0, 0.0));
767 }
768
769 #[cfg(feature = "fp")]
770 #[test]
771 fn rotation_x() {
772 let m = rotate_x(degs(90.0));
773 assert_eq!(m.apply(&splat(0.0)), splat(0.0));
774 assert_approx_eq!(
775 m.apply(&vec3(0.0, 0.0, 1.0)),
776 vec3(0.0, 1.0, 0.0)
777 );
778 }
779
780 #[cfg(feature = "fp")]
781 #[test]
782 fn rotation_y() {
783 let m = rotate_y(degs(90.0));
784 assert_eq!(m.apply(&splat(0.0)), splat(0.0));
785 assert_approx_eq!(
786 m.apply(&vec3(1.0, 0.0, 0.0)),
787 vec3(0.0, 0.0, 1.0)
788 );
789 }
790
791 #[cfg(feature = "fp")]
792 #[test]
793 fn rotation_z() {
794 let m = rotate_z(degs(90.0));
795 assert_eq!(m.apply(&splat(0.0)), splat(0.0));
796 assert_approx_eq!(
797 m.apply(&vec3(0.0, 1.0, 0.0)),
798 vec3(1.0, 0.0, 0.0)
799 );
800 }
801
802 #[test]
803 fn matrix_debug() {
804 assert_eq!(
805 alloc::format!("{MAT:?}"),
806 r#"Matrix<Basis1→Basis2>[
807 [ 0.00, 1.00, 2.00, 3.00]
808 [ 10.00, 11.00, 12.00, 13.00]
809 [ 20.00, 21.00, 22.00, 23.00]
810 [ 30.00, 31.00, 32.00, 33.00]
811]"#
812 );
813 }
814 }
815
816 #[test]
817 fn transposition() {
818 let m = Matrix::<_, Map>::new([
819 [0.0, 1.0, 2.0], [10.0, 11.0, 12.0],
821 [20.0, 21.0, 22.0],
822 ]);
823 assert_eq!(
824 m.transpose(),
825 Matrix::<_, InvMap>::new([
826 [0.0, 10.0, 20.0], [1.0, 11.0, 21.0],
828 [2.0, 12.0, 22.0],
829 ])
830 );
831 }
832
833 #[test]
834 fn determinant_of_identity_is_one() {
835 let id: Mat4x4<Map> = Mat4x4::identity();
836 assert_eq!(id.determinant(), 1.0);
837 }
838
839 #[test]
840 fn determinant_of_scaling_is_product_of_diagonal() {
841 let scale: Mat4x4<_> = scale(vec3(2.0, 3.0, 4.0));
842 assert_eq!(scale.determinant(), 24.0);
843 }
844
845 #[cfg(feature = "fp")]
846 #[test]
847 fn determinant_of_rotation_is_one() {
848 let rot = rotate_x(degs(73.0)).then(&rotate_y(degs(-106.0)));
849 assert_approx_eq!(rot.determinant(), 1.0);
850 }
851
852 #[cfg(feature = "fp")]
853 #[test]
854 fn mat_times_mat_inverse_is_identity() {
855 let m = translate(vec3(1.0e3, -2.0e2, 0.0))
856 .then(&scale(vec3(0.5, 100.0, 42.0)))
857 .to::<Map>();
858
859 let m_inv: Mat4x4<InvMap> = m.inverse();
860
861 assert_eq!(
862 m.compose(&m_inv),
863 Mat4x4::<RealToReal<3, Basis2, Basis2>>::identity()
864 );
865 assert_eq!(
866 m_inv.compose(&m),
867 Mat4x4::<RealToReal<3, Basis1, Basis1>>::identity()
868 );
869 }
870
871 #[test]
872 fn inverse_reverts_transform() {
873 let m: Mat4x4<Map> = scale(vec3(1.0, 2.0, 0.5))
874 .then(&translate(vec3(-2.0, 3.0, 0.0)))
875 .to();
876 let m_inv: Mat4x4<InvMap> = m.inverse();
877
878 let v1: Vec3<Basis1> = vec3(1.0, -2.0, 3.0);
879 let v2: Vec3<Basis2> = vec3(2.0, 0.0, -2.0);
880
881 assert_eq!(m_inv.apply(&m.apply(&v1)), v1);
882 assert_eq!(m.apply(&m_inv.apply(&v2)), v2);
883 }
884
885 #[test]
886 fn orthographic_box_maps_to_unit_cube() {
887 let lbn = vec3(-20.0, 0.0, 0.01);
888 let rtf = vec3(100.0, 50.0, 100.0);
889
890 let m = orthographic(lbn, rtf);
891
892 assert_approx_eq!(m.apply(&lbn.to()), [-1.0, -1.0, -1.0, 1.0].into());
893 assert_approx_eq!(m.apply(&rtf.to()), splat(1.0));
894 }
895
896 #[test]
897 fn perspective_frustum_maps_to_unit_cube() {
898 let left_bot_near = vec3(-0.125, -0.0625, 0.1);
899 let right_top_far = vec3(125.0, 62.5, 100.0);
900
901 let m = perspective(0.8, 2.0, 0.1..100.0);
902
903 let lbn = m.apply(&left_bot_near);
904 assert_approx_eq!(lbn / lbn.w(), [-1.0, -1.0, -1.0, 1.0].into());
905
906 let rtf = m.apply(&right_top_far);
907 assert_approx_eq!(rtf / rtf.w(), splat(1.0));
908 }
909}