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()
}
}