Browse Source

Implement more matrix operations

Also simplify FVec operations since it's a Copy type, but keep using
references for Matrix implementations. Use type-safe matrix multiplication
with other matrices and vectors.
pull/28/head
Ian Chamberlain 1 year ago
parent
commit
e2abfa300a
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 126
      citro3d/src/math.rs
  2. 17
      citro3d/src/math/fvec.rs
  3. 142
      citro3d/src/math/ops.rs

126
citro3d/src/math.rs

@ -22,10 +22,22 @@ pub struct IVec(citro3d_sys::C3D_IVec); @@ -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<const M: usize, const N: usize>(citro3d_sys::C3D_Mtx);
impl<const M: usize, const N: usize> fmt::Debug for Matrix<M, N> {
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::<Self>())
.field(&inner)
.finish()
}
}
impl<const M: usize, const N: usize> Matrix<M, N> {
const ROW_SIZE: () = assert!(M == 3 || M == 4);
const COLUMN_SIZE: () = assert!(N > 0 && N <= 4);
@ -44,6 +56,14 @@ mod mtx { @@ -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<const M: usize, const N: usize> Matrix<M, N> { @@ -65,9 +85,72 @@ impl<const M: usize, const N: usize> Matrix<M, N> {
Self::new(out.assume_init())
}
}
/// Transpose the matrix, swapping rows and columns.
pub fn transpose(mut self) -> Matrix<N, M> {
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<const N: usize> Matrix<N, N> {
/// 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<Self, Self> {
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<const N: usize> Matrix<N, N> { @@ -79,6 +162,49 @@ impl<const N: usize> Matrix<N, N> {
}
}
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.

17
citro3d/src/math/fvec.rs

@ -4,7 +4,7 @@ use std::fmt; @@ -4,7 +4,7 @@ use std::fmt;
/// A vector of `f32`s.
#[derive(Clone, Copy)]
pub struct FVec<const N: usize>(pub(super) citro3d_sys::C3D_FVec);
pub struct FVec<const N: usize>(pub(crate) citro3d_sys::C3D_FVec);
/// A 3-vector of `f32`s.
pub type FVec3 = FVec<3>;
@ -21,18 +21,6 @@ impl<const N: usize> fmt::Debug for FVec<N> { @@ -21,18 +21,6 @@ impl<const N: usize> fmt::Debug for FVec<N> {
}
}
impl<Rhs, const N: usize> PartialEq<Rhs> for FVec<N>
where
Rhs: Copy,
Self: From<Rhs>,
{
fn eq(&self, other: &Rhs) -> bool {
unsafe { self.0.c == Self::from(*other).0.c }
}
}
impl<const N: usize> Eq for FVec<N> {}
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<const N: usize> FVec<N> { @@ -127,9 +115,10 @@ impl<const N: usize> FVec<N> {
#[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() {

142
citro3d/src/math/ops.rs

@ -1,21 +1,22 @@ @@ -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<Rhs: Borrow<Self>> Add<Rhs> 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<Rhs: Borrow<Self>> Sub<Rhs> 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<f32> for FVec4 { @@ -35,19 +36,19 @@ impl Mul<f32> for FVec4 {
}
}
impl<Rhs: Borrow<Self>> Add<Rhs> 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<Rhs: Borrow<Self>> Sub<Rhs> 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 @@ -78,11 +79,94 @@ where
}
}
impl<const N: usize> PartialEq for FVec<N> {
fn eq(&self, other: &Self) -> bool {
unsafe { self.0.c == other.0.c }
}
}
impl<const N: usize> Eq for FVec<N> {}
impl<Rhs: Borrow<Self>, const M: usize, const N: usize> Add<Rhs> for &Matrix<M, N> {
type Output = <Self as Deref>::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<Rhs: Borrow<Self>, const M: usize, const N: usize> Sub<Rhs> for &Matrix<M, N> {
type Output = <Self as Deref>::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<const M: usize, const N: usize, const P: usize> Mul<&Matrix<N, P>> for &Matrix<M, N> {
type Output = Matrix<M, P>;
fn mul(self, rhs: &Matrix<N, P>) -> 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<const M: usize, const N: usize, const P: usize> Mul<Matrix<N, P>> for &Matrix<M, N> {
type Output = Matrix<M, P>;
fn mul(self, rhs: Matrix<N, P>) -> Self::Output {
self * &rhs
}
}
impl Mul<FVec3> 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<FVec4> 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<FVec3> 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<Rhs: Borrow<Self>, const M: usize, const N: usize> PartialEq<Rhs> for Matrix<M, N> {
fn eq(&self, other: &Rhs) -> bool {
unsafe { (*self.as_raw()).m == (*other.borrow().as_raw()).m }
}
}
impl<const M: usize, const N: usize> Eq for Matrix<M, N> {}
#[cfg(test)]
mod tests {
#![allow(clippy::op_ref)]
use super::*;
use crate::math::{Matrix3, Matrix4};
#[test]
fn vec3() {
@ -90,9 +174,7 @@ mod tests { @@ -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 { @@ -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));
}
}

Loading…
Cancel
Save