citro3d/math/
matrix.rs

1use std::mem::MaybeUninit;
2
3use super::{CoordinateOrientation, FVec3, FVec4};
4
5/// A 4x4 row-major matrix of `f32`s.
6///
7/// # Layout details
8/// Rows are actually stored as WZYX in memory. There are helper functions
9/// for accessing the rows in XYZW form. The `Debug` implementation prints
10/// the shows in WZYX form
11///
12/// It is also guaranteed to have the same layout as [`citro3d_sys::C3D_Mtx`]
13#[doc(alias = "C3D_Mtx")]
14#[derive(Clone, Copy)]
15#[repr(transparent)]
16pub struct Matrix4(citro3d_sys::C3D_Mtx);
17
18impl Matrix4 {
19    /// Construct a Matrix4 from the cells
20    ///
21    /// # Note
22    /// This expects rows to be in WZYX order
23    pub fn from_cells_wzyx(cells: [f32; 16]) -> Self {
24        Self(citro3d_sys::C3D_Mtx { m: cells })
25    }
26    /// Construct a Matrix4 from its rows
27    pub fn from_rows(rows: [FVec4; 4]) -> Self {
28        Self(citro3d_sys::C3D_Mtx {
29            r: rows.map(|r| r.0),
30        })
31    }
32    /// Create a new matrix from a raw citro3d_sys one
33    pub fn from_raw(value: citro3d_sys::C3D_Mtx) -> Self {
34        Self(value)
35    }
36
37    pub fn as_raw(&self) -> &citro3d_sys::C3D_Mtx {
38        &self.0
39    }
40
41    pub fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_Mtx {
42        &mut self.0
43    }
44
45    pub fn into_raw(self) -> citro3d_sys::C3D_Mtx {
46        self.0
47    }
48
49    /// Get the rows in raw (WZYX) form
50    pub fn rows_wzyx(self) -> [FVec4; 4] {
51        unsafe { self.0.r }.map(FVec4::from_raw)
52    }
53
54    /// Get the rows in XYZW form
55    pub fn rows_xyzw(self) -> [[f32; 4]; 4] {
56        self.rows_wzyx().map(|r| [r.x(), r.y(), r.z(), r.w()])
57    }
58    /// Construct the zero matrix.
59    #[doc(alias = "Mtx_Zeros")]
60    pub fn zero() -> Self {
61        // TODO: should this also be Default::default()?
62        let mut out = MaybeUninit::uninit();
63        unsafe {
64            citro3d_sys::Mtx_Zeros(out.as_mut_ptr());
65            Self::from_raw(out.assume_init())
66        }
67    }
68
69    /// Transpose the matrix, swapping rows and columns.
70    #[doc(alias = "Mtx_Transpose")]
71    pub fn transpose(mut self) -> Matrix4 {
72        unsafe {
73            citro3d_sys::Mtx_Transpose(self.as_raw_mut());
74        }
75        Matrix4::from_raw(self.into_raw())
76    }
77
78    // region: Matrix transformations
79    //
80    // NOTE: the `bRightSide` arg common to many of these APIs flips the order of
81    // operations so that a transformation occurs as self(T) instead of T(self).
82    // For now I'm not sure if that's a common use case, but if needed we could
83    // probably have some kinda wrapper type that does transformations in the
84    // opposite order, or an enum arg for these APIs or something.
85
86    /// Translate a transformation matrix by the given amounts in the X, Y, and Z
87    /// directions.
88    #[doc(alias = "Mtx_Translate")]
89    pub fn translate(&mut self, x: f32, y: f32, z: f32) {
90        unsafe { citro3d_sys::Mtx_Translate(self.as_raw_mut(), x, y, z, false) }
91    }
92
93    /// Scale a transformation matrix by the given amounts in the X, Y, and Z directions.
94    #[doc(alias = "Mtx_Scale")]
95    pub fn scale(&mut self, x: f32, y: f32, z: f32) {
96        unsafe { citro3d_sys::Mtx_Scale(self.as_raw_mut(), x, y, z) }
97    }
98
99    /// Rotate a transformation matrix by the given angle around the given axis.
100    #[doc(alias = "Mtx_Rotate")]
101    pub fn rotate(&mut self, axis: FVec3, angle: f32) {
102        unsafe { citro3d_sys::Mtx_Rotate(self.as_raw_mut(), axis.0, angle, false) }
103    }
104
105    /// Rotate a transformation matrix by the given angle around the X axis.
106    #[doc(alias = "Mtx_RotateX")]
107    pub fn rotate_x(&mut self, angle: f32) {
108        unsafe { citro3d_sys::Mtx_RotateX(self.as_raw_mut(), angle, false) }
109    }
110
111    /// Rotate a transformation matrix by the given angle around the Y axis.
112    #[doc(alias = "Mtx_RotateY")]
113    pub fn rotate_y(&mut self, angle: f32) {
114        unsafe { citro3d_sys::Mtx_RotateY(self.as_raw_mut(), angle, false) }
115    }
116
117    /// Rotate a transformation matrix by the given angle around the Z axis.
118    #[doc(alias = "Mtx_RotateZ")]
119    pub fn rotate_z(&mut self, angle: f32) {
120        unsafe { citro3d_sys::Mtx_RotateZ(self.as_raw_mut(), angle, false) }
121    }
122
123    /// Find the inverse of the matrix.
124    ///
125    /// # Errors
126    ///
127    /// If the matrix has no inverse, it will be returned unchanged as an [`Err`].
128    #[doc(alias = "Mtx_Inverse")]
129    pub fn inverse(mut self) -> Result<Self, Self> {
130        let determinant = unsafe { citro3d_sys::Mtx_Inverse(self.as_raw_mut()) };
131        if determinant == 0.0 {
132            Err(self)
133        } else {
134            Ok(self)
135        }
136    }
137
138    /// Construct the identity matrix.
139    #[doc(alias = "Mtx_Identity")]
140    pub fn identity() -> Self {
141        let mut out = MaybeUninit::uninit();
142        unsafe {
143            citro3d_sys::Mtx_Identity(out.as_mut_ptr());
144            Self::from_raw(out.assume_init())
145        }
146    }
147
148    /// Construct a 4x4 matrix with the given values on the diagonal.
149    #[doc(alias = "Mtx_Diagonal")]
150    pub fn diagonal(x: f32, y: f32, z: f32, w: f32) -> Self {
151        let mut out = MaybeUninit::uninit();
152        unsafe {
153            citro3d_sys::Mtx_Diagonal(out.as_mut_ptr(), x, y, z, w);
154            Self::from_raw(out.assume_init())
155        }
156    }
157
158    /// Construct a 3D transformation matrix for a camera, given its position,
159    /// target, and upward direction.
160    #[doc(alias = "Mtx_LookAt")]
161    pub fn looking_at(
162        camera_position: FVec3,
163        camera_target: FVec3,
164        camera_up: FVec3,
165        coordinates: CoordinateOrientation,
166    ) -> Self {
167        let mut out = MaybeUninit::uninit();
168        unsafe {
169            citro3d_sys::Mtx_LookAt(
170                out.as_mut_ptr(),
171                camera_position.0,
172                camera_target.0,
173                camera_up.0,
174                coordinates.is_left_handed(),
175            );
176            Self::from_raw(out.assume_init())
177        }
178    }
179}
180
181impl core::fmt::Debug for Matrix4 {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        f.debug_tuple("Matrix4").field(&self.rows_wzyx()).finish()
184    }
185}
186
187#[cfg(feature = "glam")]
188impl From<glam::Mat4> for Matrix4 {
189    fn from(mat: glam::Mat4) -> Self {
190        Matrix4::from_rows(core::array::from_fn(|i| mat.row(i).into()))
191    }
192}
193
194#[cfg(feature = "glam")]
195impl From<Matrix4> for glam::Mat4 {
196    fn from(mat: Matrix4) -> Self {
197        glam::Mat4::from_cols_array_2d(&mat.rows_xyzw()).transpose()
198    }
199}