1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
use std::mem::MaybeUninit;

use super::{CoordinateOrientation, FVec3, FVec4};

/// A 4x4 row-major matrix of `f32`s.
///
/// # Layout details
/// Rows are actually stored as WZYX in memory. There are helper functions
/// for accessing the rows in XYZW form. The `Debug` implementation prints
/// the shows in WZYX form
///
/// It is also guaranteed to have the same layout as [`citro3d_sys::C3D_Mtx`]
#[doc(alias = "C3D_Mtx")]
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Matrix4(citro3d_sys::C3D_Mtx);

impl Matrix4 {
    /// Construct a Matrix4 from the cells
    ///
    /// # Note
    /// This expects rows to be in WZYX order
    pub fn from_cells_wzyx(cells: [f32; 16]) -> Self {
        Self(citro3d_sys::C3D_Mtx { m: cells })
    }
    /// Construct a Matrix4 from its rows
    pub fn from_rows(rows: [FVec4; 4]) -> Self {
        Self(citro3d_sys::C3D_Mtx {
            r: rows.map(|r| r.0),
        })
    }
    /// Create a new matrix from a raw citro3d_sys one
    pub fn from_raw(value: citro3d_sys::C3D_Mtx) -> Self {
        Self(value)
    }

    pub fn as_raw(&self) -> &citro3d_sys::C3D_Mtx {
        &self.0
    }

    pub fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_Mtx {
        &mut self.0
    }

    pub fn into_raw(self) -> citro3d_sys::C3D_Mtx {
        self.0
    }

    /// Get the rows in raw (WZYX) form
    pub fn rows_wzyx(self) -> [FVec4; 4] {
        unsafe { self.0.r }.map(FVec4::from_raw)
    }

    /// Get the rows in XYZW form
    pub fn rows_xyzw(self) -> [[f32; 4]; 4] {
        self.rows_wzyx().map(|r| [r.x(), r.y(), r.z(), r.w()])
    }
    /// Construct the zero matrix.
    #[doc(alias = "Mtx_Zeros")]
    pub fn zero() -> Self {
        // TODO: should this also be Default::default()?
        let mut out = MaybeUninit::uninit();
        unsafe {
            citro3d_sys::Mtx_Zeros(out.as_mut_ptr());
            Self::from_raw(out.assume_init())
        }
    }

    /// Transpose the matrix, swapping rows and columns.
    #[doc(alias = "Mtx_Transpose")]
    pub fn transpose(mut self) -> Matrix4 {
        unsafe {
            citro3d_sys::Mtx_Transpose(self.as_raw_mut());
        }
        Matrix4::from_raw(self.into_raw())
    }

    // region: Matrix transformations
    //
    // NOTE: the `bRightSide` arg common to many of these APIs flips the order of
    // operations so that a transformation occurs as self(T) instead of T(self).
    // For now I'm not sure if that's a common use case, but if needed we could
    // probably have some kinda wrapper type that does transformations in the
    // opposite order, or an enum arg for these APIs or something.

    /// Translate a transformation matrix by the given amounts in the X, Y, and Z
    /// directions.
    #[doc(alias = "Mtx_Translate")]
    pub fn translate(&mut self, x: f32, y: f32, z: f32) {
        unsafe { citro3d_sys::Mtx_Translate(self.as_raw_mut(), x, y, z, false) }
    }

    /// Scale a transformation matrix by the given amounts in the X, Y, and Z directions.
    #[doc(alias = "Mtx_Scale")]
    pub fn scale(&mut self, x: f32, y: f32, z: f32) {
        unsafe { citro3d_sys::Mtx_Scale(self.as_raw_mut(), x, y, z) }
    }

    /// Rotate a transformation matrix by the given angle around the given axis.
    #[doc(alias = "Mtx_Rotate")]
    pub fn rotate(&mut self, axis: FVec3, angle: f32) {
        unsafe { citro3d_sys::Mtx_Rotate(self.as_raw_mut(), axis.0, angle, false) }
    }

    /// Rotate a transformation matrix by the given angle around the X axis.
    #[doc(alias = "Mtx_RotateX")]
    pub fn rotate_x(&mut self, angle: f32) {
        unsafe { citro3d_sys::Mtx_RotateX(self.as_raw_mut(), angle, false) }
    }

    /// Rotate a transformation matrix by the given angle around the Y axis.
    #[doc(alias = "Mtx_RotateY")]
    pub fn rotate_y(&mut self, angle: f32) {
        unsafe { citro3d_sys::Mtx_RotateY(self.as_raw_mut(), angle, false) }
    }

    /// Rotate a transformation matrix by the given angle around the Z axis.
    #[doc(alias = "Mtx_RotateZ")]
    pub fn rotate_z(&mut self, angle: f32) {
        unsafe { citro3d_sys::Mtx_RotateZ(self.as_raw_mut(), angle, false) }
    }

    /// Find the inverse of the matrix.
    ///
    /// # Errors
    ///
    /// If the matrix has no inverse, it will be returned unchanged as an [`Err`].
    #[doc(alias = "Mtx_Inverse")]
    pub fn inverse(mut self) -> Result<Self, Self> {
        let determinant = unsafe { citro3d_sys::Mtx_Inverse(self.as_raw_mut()) };
        if determinant == 0.0 {
            Err(self)
        } else {
            Ok(self)
        }
    }

    /// Construct the identity matrix.
    #[doc(alias = "Mtx_Identity")]
    pub fn identity() -> Self {
        let mut out = MaybeUninit::uninit();
        unsafe {
            citro3d_sys::Mtx_Identity(out.as_mut_ptr());
            Self::from_raw(out.assume_init())
        }
    }

    /// Construct a 4x4 matrix with the given values on the diagonal.
    #[doc(alias = "Mtx_Diagonal")]
    pub fn diagonal(x: f32, y: f32, z: f32, w: f32) -> Self {
        let mut out = MaybeUninit::uninit();
        unsafe {
            citro3d_sys::Mtx_Diagonal(out.as_mut_ptr(), x, y, z, w);
            Self::from_raw(out.assume_init())
        }
    }

    /// Construct a 3D transformation matrix for a camera, given its position,
    /// target, and upward direction.
    #[doc(alias = "Mtx_LookAt")]
    pub fn looking_at(
        camera_position: FVec3,
        camera_target: FVec3,
        camera_up: FVec3,
        coordinates: CoordinateOrientation,
    ) -> Self {
        let mut out = MaybeUninit::uninit();
        unsafe {
            citro3d_sys::Mtx_LookAt(
                out.as_mut_ptr(),
                camera_position.0,
                camera_target.0,
                camera_up.0,
                coordinates.is_left_handed(),
            );
            Self::from_raw(out.assume_init())
        }
    }
}

impl core::fmt::Debug for Matrix4 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("Matrix4").field(&self.rows_wzyx()).finish()
    }
}

#[cfg(feature = "glam")]
impl From<glam::Mat4> for Matrix4 {
    fn from(mat: glam::Mat4) -> Self {
        Matrix4::from_rows(core::array::from_fn(|i| mat.row(i).into()))
    }
}

#[cfg(feature = "glam")]
impl From<Matrix4> for glam::Mat4 {
    fn from(mat: Matrix4) -> Self {
        glam::Mat4::from_cols_array_2d(&mat.rows_xyzw()).transpose()
    }
}