diff --git a/citro3d/src/math.rs b/citro3d/src/math.rs index 976ddc7..64fa613 100644 --- a/citro3d/src/math.rs +++ b/citro3d/src/math.rs @@ -22,10 +22,22 @@ pub struct IVec(citro3d_sys::C3D_IVec); pub struct FQuat(citro3d_sys::C3D_FQuat); mod mtx { + use std::fmt; + /// An `M`x`N` row-major matrix of `f32`s. #[doc(alias = "C3D_Mtx")] + #[derive(Clone)] pub struct Matrix(citro3d_sys::C3D_Mtx); + impl fmt::Debug for Matrix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let inner = unsafe { self.0.r.map(|v| v.c) }; + f.debug_tuple(std::any::type_name::()) + .field(&inner) + .finish() + } + } + impl Matrix { const ROW_SIZE: () = assert!(M == 3 || M == 4); const COLUMN_SIZE: () = assert!(N > 0 && N <= 4); @@ -44,6 +56,14 @@ mod mtx { pub(crate) fn as_raw(&self) -> *const citro3d_sys::C3D_Mtx { &self.0 } + + pub(crate) fn into_raw(self) -> citro3d_sys::C3D_Mtx { + self.0 + } + + pub(crate) fn as_mut(&mut self) -> *mut citro3d_sys::C3D_Mtx { + &mut self.0 + } } } @@ -65,9 +85,72 @@ impl Matrix { Self::new(out.assume_init()) } } + + /// Transpose the matrix, swapping rows and columns. + pub fn transpose(mut self) -> Matrix { + unsafe { + citro3d_sys::Mtx_Transpose(self.as_mut()); + } + Matrix::new(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. + pub fn translate(&mut self, x: f32, y: f32, z: f32) { + unsafe { citro3d_sys::Mtx_Translate(self.as_mut(), x, y, z, false) } + } + + /// Scale a transformation matrix by the given amounts in the X, Y, and Z directions. + pub fn scale(&mut self, x: f32, y: f32, z: f32) { + unsafe { citro3d_sys::Mtx_Scale(self.as_mut(), x, y, z) } + } + + /// Rotate a transformation matrix by the given angle around the given axis. + pub fn rotate(&mut self, axis: FVec3, angle: f32) { + unsafe { citro3d_sys::Mtx_Rotate(self.as_mut(), axis.0, angle, false) } + } + + /// Rotate a transformation matrix by the given angle around the X axis. + pub fn rotate_x(&mut self, angle: f32) { + unsafe { citro3d_sys::Mtx_RotateX(self.as_mut(), angle, false) } + } + + /// Rotate a transformation matrix by the given angle around the Y axis. + pub fn rotate_y(&mut self, angle: f32) { + unsafe { citro3d_sys::Mtx_RotateY(self.as_mut(), angle, false) } + } + + /// Rotate a transformation matrix by the given angle around the Z axis. + pub fn rotate_z(&mut self, angle: f32) { + unsafe { citro3d_sys::Mtx_RotateZ(self.as_mut(), angle, false) } + } + + // endregion } impl Matrix { + /// Find the inverse of the matrix. + /// + /// # Errors + /// + /// If the matrix has no inverse, it will be returned unchanged as an [`Err`]. + pub fn inverse(mut self) -> Result { + let determinant = unsafe { citro3d_sys::Mtx_Inverse(self.as_mut()) }; + if determinant == 0.0 { + Err(self) + } else { + Ok(self) + } + } + /// Construct the identity matrix. #[doc(alias = "Mtx_Identity")] pub fn identity() -> Self { @@ -79,6 +162,49 @@ impl Matrix { } } +impl Matrix3 { + /// Construct a 3x3 matrix with the given values on the diagonal. + pub fn diagonal(x: f32, y: f32, z: f32) -> Self { + let mut out = MaybeUninit::uninit(); + unsafe { + citro3d_sys::Mtx_Diagonal(out.as_mut_ptr(), x, y, z, 0.0); + Self::new(out.assume_init()) + } + } +} + +impl Matrix4 { + /// Construct a 4x4 matrix with the given values on the 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::new(out.assume_init()) + } + } + + /// Construct a 3D transformation matrix for a camera, given its position, + /// target, and upward direction. + 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::new(out.assume_init()) + } + } +} + // region: Projection configuration // // TODO: maybe move into `mod projection`, or hoist `projection::*` into here. diff --git a/citro3d/src/math/fvec.rs b/citro3d/src/math/fvec.rs index fce145d..76d1c2d 100644 --- a/citro3d/src/math/fvec.rs +++ b/citro3d/src/math/fvec.rs @@ -4,7 +4,7 @@ use std::fmt; /// A vector of `f32`s. #[derive(Clone, Copy)] -pub struct FVec(pub(super) citro3d_sys::C3D_FVec); +pub struct FVec(pub(crate) citro3d_sys::C3D_FVec); /// A 3-vector of `f32`s. pub type FVec3 = FVec<3>; @@ -21,18 +21,6 @@ impl fmt::Debug for FVec { } } -impl PartialEq for FVec -where - Rhs: Copy, - Self: From, -{ - fn eq(&self, other: &Rhs) -> bool { - unsafe { self.0.c == Self::from(*other).0.c } - } -} - -impl Eq for FVec {} - impl FVec4 { /// Create a new [`FVec4`] from its components. pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { @@ -127,9 +115,10 @@ impl FVec { #[cfg(test)] mod tests { - use super::*; use float_cmp::assert_approx_eq; + use super::*; + // TODO: These could probably all be doctests instead it's just sort of a pain #[test] fn fvec4() { diff --git a/citro3d/src/math/ops.rs b/citro3d/src/math/ops.rs index 3be3095..0cb51a4 100644 --- a/citro3d/src/math/ops.rs +++ b/citro3d/src/math/ops.rs @@ -1,21 +1,22 @@ use std::borrow::Borrow; -use std::ops::{Add, Div, Mul, Neg, Sub}; +use std::mem::MaybeUninit; +use std::ops::{Add, Deref, Div, Mul, Neg, Sub}; -use super::{FVec, FVec3, FVec4}; +use super::{FVec, FVec3, FVec4, Matrix, Matrix3, Matrix4}; -impl> Add for FVec4 { +impl Add for FVec4 { type Output = Self; - fn add(self, rhs: Rhs) -> Self::Output { - Self(unsafe { citro3d_sys::FVec4_Add(self.0, rhs.borrow().0) }) + fn add(self, rhs: Self) -> Self::Output { + Self(unsafe { citro3d_sys::FVec4_Add(self.0, rhs.0) }) } } -impl> Sub for FVec4 { +impl Sub for FVec4 { type Output = Self; - fn sub(self, rhs: Rhs) -> Self::Output { - Self(unsafe { citro3d_sys::FVec4_Subtract(self.0, rhs.borrow().0) }) + fn sub(self, rhs: Self) -> Self::Output { + Self(unsafe { citro3d_sys::FVec4_Subtract(self.0, rhs.0) }) } } @@ -35,19 +36,19 @@ impl Mul for FVec4 { } } -impl> Add for FVec3 { +impl Add for FVec3 { type Output = Self; - fn add(self, rhs: Rhs) -> Self::Output { - Self(unsafe { citro3d_sys::FVec3_Add(self.0, rhs.borrow().0) }) + fn add(self, rhs: Self) -> Self::Output { + Self(unsafe { citro3d_sys::FVec3_Add(self.0, rhs.0) }) } } -impl> Sub for FVec3 { +impl Sub for FVec3 { type Output = Self; - fn sub(self, rhs: Rhs) -> Self::Output { - Self(unsafe { citro3d_sys::FVec3_Subtract(self.0, rhs.borrow().0) }) + fn sub(self, rhs: Self) -> Self::Output { + Self(unsafe { citro3d_sys::FVec3_Subtract(self.0, rhs.0) }) } } @@ -78,11 +79,94 @@ where } } +impl PartialEq for FVec { + fn eq(&self, other: &Self) -> bool { + unsafe { self.0.c == other.0.c } + } +} + +impl Eq for FVec {} + +impl, const M: usize, const N: usize> Add for &Matrix { + type Output = ::Target; + + fn add(self, rhs: Rhs) -> Self::Output { + let mut out = MaybeUninit::uninit(); + unsafe { + citro3d_sys::Mtx_Add(out.as_mut_ptr(), self.as_raw(), rhs.borrow().as_raw()); + Matrix::new(out.assume_init()) + } + } +} + +impl, const M: usize, const N: usize> Sub for &Matrix { + type Output = ::Target; + + fn sub(self, rhs: Rhs) -> Self::Output { + let mut out = MaybeUninit::uninit(); + unsafe { + citro3d_sys::Mtx_Subtract(out.as_mut_ptr(), self.as_raw(), rhs.borrow().as_raw()); + Matrix::new(out.assume_init()) + } + } +} + +impl Mul<&Matrix> for &Matrix { + type Output = Matrix; + + fn mul(self, rhs: &Matrix) -> Self::Output { + let mut out = MaybeUninit::uninit(); + unsafe { + citro3d_sys::Mtx_Multiply(out.as_mut_ptr(), self.as_raw(), rhs.as_raw()); + Matrix::new(out.assume_init()) + } + } +} + +impl Mul> for &Matrix { + type Output = Matrix; + + fn mul(self, rhs: Matrix) -> Self::Output { + self * &rhs + } +} + +impl Mul for &Matrix3 { + type Output = FVec3; + + fn mul(self, rhs: FVec3) -> Self::Output { + FVec(unsafe { citro3d_sys::Mtx_MultiplyFVec3(self.as_raw(), rhs.0) }) + } +} + +impl Mul for &Matrix4 { + type Output = FVec4; + + fn mul(self, rhs: FVec4) -> Self::Output { + FVec(unsafe { citro3d_sys::Mtx_MultiplyFVec4(self.as_raw(), rhs.0) }) + } +} + +impl Mul for &Matrix<4, 3> { + type Output = FVec4; + + fn mul(self, rhs: FVec3) -> Self::Output { + FVec(unsafe { citro3d_sys::Mtx_MultiplyFVecH(self.as_raw(), rhs.0) }) + } +} + +impl, const M: usize, const N: usize> PartialEq for Matrix { + fn eq(&self, other: &Rhs) -> bool { + unsafe { (*self.as_raw()).m == (*other.borrow().as_raw()).m } + } +} + +impl Eq for Matrix {} + #[cfg(test)] mod tests { - #![allow(clippy::op_ref)] - use super::*; + use crate::math::{Matrix3, Matrix4}; #[test] fn vec3() { @@ -90,9 +174,7 @@ mod tests { let r = FVec3::splat(2.0); assert_eq!(l + r, FVec3::splat(3.0)); - assert_eq!(l + &r, FVec3::splat(3.0)); assert_eq!(l - r, FVec3::splat(-1.0)); - assert_eq!(l - &r, FVec3::splat(-1.0)); assert_eq!(-l, FVec3::splat(-1.0)); assert_eq!(l * 1.5, FVec3::splat(1.5)); assert_eq!(l / 2.0, FVec3::splat(0.5)); @@ -104,11 +186,31 @@ mod tests { let r = FVec4::splat(2.0); assert_eq!(l + r, FVec4::splat(3.0)); - assert_eq!(l + &r, FVec4::splat(3.0)); assert_eq!(l - r, FVec4::splat(-1.0)); - assert_eq!(l - &r, FVec4::splat(-1.0)); assert_eq!(-l, FVec4::splat(-1.0)); assert_eq!(l * 1.5, FVec4::splat(1.5)); assert_eq!(l / 2.0, FVec4::splat(0.5)); } + + #[test] + fn matrix3() { + let l = Matrix3::diagonal(1.0, 2.0, 3.0); + let r = Matrix3::identity(); + let (l, r) = (&l, &r); + + assert_eq!(l * r, r); + assert_eq!(l + r, Matrix3::diagonal(2.0, 3.0, 4.0)); + assert_eq!(l - r, Matrix3::diagonal(0.0, 1.0, 2.0)); + } + + #[test] + fn matrix4() { + let l = Matrix4::diagonal(1.0, 2.0, 3.0, 4.0); + let r = Matrix4::identity(); + let (l, r) = (&l, &r); + + assert_eq!(l * r, r); + assert_eq!(l + r, Matrix4::diagonal(2.0, 3.0, 4.0, 5.0)); + assert_eq!(l - r, Matrix4::diagonal(0.0, 1.0, 2.0, 3.0)); + } }