Ian Chamberlain
1 year ago
committed by
GitHub
12 changed files with 977 additions and 197 deletions
@ -0,0 +1,266 @@ |
|||||||
|
//! Floating-point vectors.
|
||||||
|
|
||||||
|
use std::fmt; |
||||||
|
|
||||||
|
/// A vector of `f32`s.
|
||||||
|
#[derive(Clone, Copy)] |
||||||
|
pub struct FVec<const N: usize>(pub(crate) citro3d_sys::C3D_FVec); |
||||||
|
|
||||||
|
/// A 3-vector of `f32`s.
|
||||||
|
pub type FVec3 = FVec<3>; |
||||||
|
|
||||||
|
/// A 4-vector of `f32`s.
|
||||||
|
pub type FVec4 = FVec<4>; |
||||||
|
|
||||||
|
impl<const N: usize> fmt::Debug for FVec<N> { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||||
|
let inner = unsafe { self.0.__bindgen_anon_1 }; |
||||||
|
let type_name = std::any::type_name::<Self>().split("::").last().unwrap(); |
||||||
|
f.debug_tuple(type_name).field(&inner).finish() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<const N: usize> FVec<N> { |
||||||
|
/// The vector's `x` component (also called the `i` component of `ijk[r]`).
|
||||||
|
#[doc(alias = "i")] |
||||||
|
pub fn x(self) -> f32 { |
||||||
|
unsafe { self.0.__bindgen_anon_1.x } |
||||||
|
} |
||||||
|
|
||||||
|
/// The vector's `y` component (also called the `j` component of `ijk[r]`).
|
||||||
|
#[doc(alias = "j")] |
||||||
|
pub fn y(self) -> f32 { |
||||||
|
unsafe { self.0.__bindgen_anon_1.y } |
||||||
|
} |
||||||
|
|
||||||
|
/// The vector's `i` component (also called the `k` component of `ijk[r]`).
|
||||||
|
#[doc(alias = "k")] |
||||||
|
pub fn z(self) -> f32 { |
||||||
|
unsafe { self.0.__bindgen_anon_1.z } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl FVec4 { |
||||||
|
/// The vector's `w` component (also called `r` for the real component of `ijk[r]`).
|
||||||
|
#[doc(alias = "r")] |
||||||
|
pub fn w(self) -> f32 { |
||||||
|
unsafe { self.0.__bindgen_anon_1.w } |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a new [`FVec4`] from its components.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec4;
|
||||||
|
/// let v = FVec4::new(1.0, 2.0, 3.0, 4.0);
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec4_New")] |
||||||
|
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { |
||||||
|
Self(unsafe { citro3d_sys::FVec4_New(x, y, z, w) }) |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a new [`FVec4`], setting each component to `v`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec4;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let v = FVec4::splat(1.0);
|
||||||
|
/// assert_abs_diff_eq!(v, FVec4::new(1.0, 1.0, 1.0, 1.0));
|
||||||
|
/// ```
|
||||||
|
pub fn splat(v: f32) -> Self { |
||||||
|
Self::new(v, v, v, v) |
||||||
|
} |
||||||
|
|
||||||
|
/// Divide the vector's XYZ components by its W component.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec4;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let v = FVec4::new(2.0, 4.0, 6.0, 2.0);
|
||||||
|
/// assert_abs_diff_eq!(v.perspective_divide(), FVec4::new(1.0, 2.0, 3.0, 1.0));
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec4_PerspDivide")] |
||||||
|
pub fn perspective_divide(self) -> Self { |
||||||
|
Self(unsafe { citro3d_sys::FVec4_PerspDivide(self.0) }) |
||||||
|
} |
||||||
|
|
||||||
|
/// The dot product of two vectors.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec4;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let v1 = FVec4::new(1.0, 2.0, 3.0, 4.0);
|
||||||
|
/// let v2 = FVec4::new(1.0, 0.5, 1.0, 0.5);
|
||||||
|
/// assert_abs_diff_eq!(v1.dot(v2), 7.0);
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec4_Dot")] |
||||||
|
pub fn dot(self, rhs: Self) -> f32 { |
||||||
|
unsafe { citro3d_sys::FVec4_Dot(self.0, rhs.0) } |
||||||
|
} |
||||||
|
|
||||||
|
/// The magnitude of the vector.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec4;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let v = FVec4::splat(1.0);
|
||||||
|
/// assert_abs_diff_eq!(v.magnitude(), 2.0);
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec4_Magnitude")] |
||||||
|
pub fn magnitude(self) -> f32 { |
||||||
|
unsafe { citro3d_sys::FVec4_Magnitude(self.0) } |
||||||
|
} |
||||||
|
|
||||||
|
/// Normalize the vector to a magnitude of `1.0`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec4;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let v = FVec4::new(1.0, 2.0, 2.0, 4.0);
|
||||||
|
/// assert_abs_diff_eq!(v.normalize(), FVec4::new(0.2, 0.4, 0.4, 0.8));
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec3_Normalize")] |
||||||
|
pub fn normalize(self) -> Self { |
||||||
|
Self(unsafe { citro3d_sys::FVec4_Normalize(self.0) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl FVec3 { |
||||||
|
/// Create a new [`FVec3`] from its components.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec3;
|
||||||
|
/// let v = FVec3::new(1.0, 2.0, 3.0);
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec3_New")] |
||||||
|
pub fn new(x: f32, y: f32, z: f32) -> Self { |
||||||
|
Self(unsafe { citro3d_sys::FVec3_New(x, y, z) }) |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a new [`FVec3`], setting each component to the given `v`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec3;
|
||||||
|
/// let v = FVec3::splat(1.0);
|
||||||
|
/// ```
|
||||||
|
pub fn splat(v: f32) -> Self { |
||||||
|
Self::new(v, v, v) |
||||||
|
} |
||||||
|
|
||||||
|
/// The distance between two points in 3D space.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec3;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let l = FVec3::new(1.0, 3.0, 4.0);
|
||||||
|
/// let r = FVec3::new(0.0, 1.0, 2.0);
|
||||||
|
///
|
||||||
|
/// assert_abs_diff_eq!(l.distance(r), 3.0);
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec3_Distance")] |
||||||
|
pub fn distance(self, rhs: Self) -> f32 { |
||||||
|
unsafe { citro3d_sys::FVec3_Distance(self.0, rhs.0) } |
||||||
|
} |
||||||
|
|
||||||
|
/// The cross product of two 3D vectors.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec3;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let l = FVec3::new(1.0, 0.0, 0.0);
|
||||||
|
/// let r = FVec3::new(0.0, 1.0, 0.0);
|
||||||
|
/// assert_abs_diff_eq!(l.cross(r), FVec3::new(0.0, 0.0, 1.0));
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec3_Cross")] |
||||||
|
pub fn cross(self, rhs: Self) -> Self { |
||||||
|
Self(unsafe { citro3d_sys::FVec3_Cross(self.0, rhs.0) }) |
||||||
|
} |
||||||
|
|
||||||
|
/// The dot product of two vectors.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec3;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let l = FVec3::new(1.0, 2.0, 3.0);
|
||||||
|
/// let r = FVec3::new(3.0, 2.0, 1.0);
|
||||||
|
/// assert_abs_diff_eq!(l.dot(r), 10.0);
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec3_Dot")] |
||||||
|
pub fn dot(self, rhs: Self) -> f32 { |
||||||
|
unsafe { citro3d_sys::FVec3_Dot(self.0, rhs.0) } |
||||||
|
} |
||||||
|
|
||||||
|
/// The magnitude of the vector.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec3;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let v = FVec3::splat(3.0f32.sqrt());
|
||||||
|
/// assert_abs_diff_eq!(v.magnitude(), 3.0);
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec3_Magnitude")] |
||||||
|
pub fn magnitude(self) -> f32 { |
||||||
|
unsafe { citro3d_sys::FVec3_Magnitude(self.0) } |
||||||
|
} |
||||||
|
|
||||||
|
/// Normalize the vector to a magnitude of `1.0`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # let _runner = test_runner::GdbRunner::default();
|
||||||
|
/// # use citro3d::math::FVec3;
|
||||||
|
/// # use approx::assert_abs_diff_eq;
|
||||||
|
/// let v = FVec3::splat(1.0);
|
||||||
|
/// assert_abs_diff_eq!(v.normalize(), FVec3::splat(1.0 / 3.0_f32.sqrt()));
|
||||||
|
/// ```
|
||||||
|
#[doc(alias = "FVec3_Normalize")] |
||||||
|
pub fn normalize(self) -> Self { |
||||||
|
Self(unsafe { citro3d_sys::FVec3_Normalize(self.0) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use approx::assert_abs_diff_eq; |
||||||
|
|
||||||
|
use super::*; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn fvec4() { |
||||||
|
let v = FVec4::new(1.0, 2.0, 3.0, 4.0); |
||||||
|
let actual = [v.x(), v.y(), v.z(), v.w()]; |
||||||
|
let expected = [1.0, 2.0, 3.0, 4.0]; |
||||||
|
assert_abs_diff_eq!(&actual[..], &expected[..]); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn fvec3() { |
||||||
|
let v = FVec3::new(1.0, 2.0, 3.0); |
||||||
|
let actual = [v.x(), v.y(), v.z()]; |
||||||
|
let expected = [1.0, 2.0, 3.0]; |
||||||
|
assert_abs_diff_eq!(&actual[..], &expected[..]); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,214 @@ |
|||||||
|
use std::mem::MaybeUninit; |
||||||
|
|
||||||
|
pub use private::Matrix; |
||||||
|
|
||||||
|
use super::{CoordinateOrientation, FVec3}; |
||||||
|
|
||||||
|
mod private { |
||||||
|
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> Matrix<M, N> { |
||||||
|
const ROW_SIZE: () = assert!(M == 3 || M == 4); |
||||||
|
const COLUMN_SIZE: () = assert!(N > 0 && N <= 4); |
||||||
|
|
||||||
|
// This constructor validates, at compile time, that the
|
||||||
|
// constructed matrix is 3xN or 4xN matrix, where 0 < N ≤ 4.
|
||||||
|
// We put this struct in a submodule to enforce that nothing creates
|
||||||
|
// a Matrix without calling this constructor.
|
||||||
|
#[allow(clippy::let_unit_value)] |
||||||
|
pub(crate) fn new(value: citro3d_sys::C3D_Mtx) -> Self { |
||||||
|
let () = Self::ROW_SIZE; |
||||||
|
let () = Self::COLUMN_SIZE; |
||||||
|
Self(value) |
||||||
|
} |
||||||
|
|
||||||
|
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 |
||||||
|
} |
||||||
|
|
||||||
|
/// Trim the matrix down to only the rows and columns we care about,
|
||||||
|
/// since the inner representation is always 4x4.
|
||||||
|
///
|
||||||
|
/// NOTE: this probably shouldn't be used in hot paths since it copies
|
||||||
|
/// the underlying storage. For some use cases slicing might be better,
|
||||||
|
/// although the underlying slice would always contain extra values for
|
||||||
|
/// matrices smaller than 4x4.
|
||||||
|
pub(crate) fn as_rows(&self) -> [[f32; N]; M] { |
||||||
|
let rows = unsafe { self.0.r }.map(|row| -> [f32; N] { |
||||||
|
// Rows are stored in WZYX order, so we slice from back to front.
|
||||||
|
// UNWRAP: N ≤ 4, so slicing to a smaller array should always work
|
||||||
|
unsafe { row.c[(4 - N)..].try_into() }.unwrap() |
||||||
|
}); |
||||||
|
|
||||||
|
// UNWRAP: M ≤ 4, so slicing to a smaller array should always work
|
||||||
|
rows[..M].try_into().unwrap() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<const M: usize, const N: usize> fmt::Debug for Matrix<M, N> { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||||
|
let inner = self.as_rows().map(|mut row| { |
||||||
|
// Rows are stored in WZYX order which is opposite of how most people
|
||||||
|
// probably expect, so reverse each row in-place for debug printing
|
||||||
|
row.reverse(); |
||||||
|
row |
||||||
|
}); |
||||||
|
|
||||||
|
let type_name = std::any::type_name::<Self>().split("::").last().unwrap(); |
||||||
|
f.debug_tuple(type_name).field(&inner).finish() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A 3x3 row-major matrix of `f32`s.
|
||||||
|
pub type Matrix3 = Matrix<3, 3>; |
||||||
|
/// A 4x4 row-major matrix of `f32`s.
|
||||||
|
pub type Matrix4 = Matrix<4, 4>; |
||||||
|
|
||||||
|
impl<const M: usize, const N: usize> Matrix<M, N> { |
||||||
|
/// 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::new(out.assume_init()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Transpose the matrix, swapping rows and columns.
|
||||||
|
#[doc(alias = "Mtx_Transpose")] |
||||||
|
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`].
|
||||||
|
#[doc(alias = "Mtx_Inverse")] |
||||||
|
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 { |
||||||
|
let mut out = MaybeUninit::uninit(); |
||||||
|
unsafe { |
||||||
|
citro3d_sys::Mtx_Identity(out.as_mut_ptr()); |
||||||
|
Self::new(out.assume_init()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Matrix3 { |
||||||
|
/// Construct a 3x3 matrix with the given values on the diagonal.
|
||||||
|
#[doc(alias = "Mtx_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.
|
||||||
|
#[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::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()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,285 @@ |
|||||||
|
use std::borrow::Borrow; |
||||||
|
use std::mem::MaybeUninit; |
||||||
|
use std::ops::{Add, Deref, Div, Mul, Neg, Sub}; |
||||||
|
|
||||||
|
#[cfg(feature = "approx")] |
||||||
|
use approx::AbsDiffEq; |
||||||
|
|
||||||
|
use super::{FVec, FVec3, FVec4, Matrix, Matrix3, Matrix4}; |
||||||
|
|
||||||
|
// region: FVec4 math operators
|
||||||
|
|
||||||
|
impl Add for FVec4 { |
||||||
|
type Output = Self; |
||||||
|
|
||||||
|
#[doc(alias = "FVec4_Add")] |
||||||
|
fn add(self, rhs: Self) -> Self::Output { |
||||||
|
Self(unsafe { citro3d_sys::FVec4_Add(self.0, rhs.0) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Sub for FVec4 { |
||||||
|
type Output = Self; |
||||||
|
|
||||||
|
#[doc(alias = "FVec4_Subtract")] |
||||||
|
fn sub(self, rhs: Self) -> Self::Output { |
||||||
|
Self(unsafe { citro3d_sys::FVec4_Subtract(self.0, rhs.0) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Neg for FVec4 { |
||||||
|
type Output = Self; |
||||||
|
|
||||||
|
#[doc(alias = "FVec4_Negate")] |
||||||
|
fn neg(self) -> Self::Output { |
||||||
|
Self(unsafe { citro3d_sys::FVec4_Negate(self.0) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Mul<f32> for FVec4 { |
||||||
|
type Output = Self; |
||||||
|
|
||||||
|
#[doc(alias = "FVec4_Scale")] |
||||||
|
fn mul(self, rhs: f32) -> Self::Output { |
||||||
|
Self(unsafe { citro3d_sys::FVec4_Scale(self.0, rhs) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: FVec3 math operators
|
||||||
|
|
||||||
|
impl Add for FVec3 { |
||||||
|
type Output = Self; |
||||||
|
|
||||||
|
#[doc(alias = "FVec3_Add")] |
||||||
|
fn add(self, rhs: Self) -> Self::Output { |
||||||
|
Self(unsafe { citro3d_sys::FVec3_Add(self.0, rhs.0) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Sub for FVec3 { |
||||||
|
type Output = Self; |
||||||
|
|
||||||
|
#[doc(alias = "FVec3_Subtract")] |
||||||
|
fn sub(self, rhs: Self) -> Self::Output { |
||||||
|
Self(unsafe { citro3d_sys::FVec3_Subtract(self.0, rhs.0) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Neg for FVec3 { |
||||||
|
type Output = Self; |
||||||
|
|
||||||
|
#[doc(alias = "FVec3_Negate")] |
||||||
|
fn neg(self) -> Self::Output { |
||||||
|
Self(unsafe { citro3d_sys::FVec3_Negate(self.0) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Mul<f32> for FVec3 { |
||||||
|
type Output = Self; |
||||||
|
|
||||||
|
#[doc(alias = "FVec3_Scale")] |
||||||
|
fn mul(self, rhs: f32) -> Self::Output { |
||||||
|
Self(unsafe { citro3d_sys::FVec3_Scale(self.0, rhs) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
impl<const N: usize> Div<f32> for FVec<N> |
||||||
|
where |
||||||
|
FVec<N>: Mul<f32>, |
||||||
|
{ |
||||||
|
type Output = <Self as Mul<f32>>::Output; |
||||||
|
|
||||||
|
fn div(self, rhs: f32) -> Self::Output { |
||||||
|
self * (1.0 / rhs) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<const N: usize> PartialEq for FVec<N> { |
||||||
|
fn eq(&self, other: &Self) -> bool { |
||||||
|
let range = (4 - N)..; |
||||||
|
unsafe { self.0.c[range.clone()] == other.0.c[range] } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<const N: usize> Eq for FVec<N> {} |
||||||
|
|
||||||
|
#[cfg(feature = "approx")] |
||||||
|
impl<const N: usize> AbsDiffEq for FVec<N> { |
||||||
|
type Epsilon = f32; |
||||||
|
|
||||||
|
fn default_epsilon() -> Self::Epsilon { |
||||||
|
// See https://docs.rs/almost/latest/almost/#why-another-crate
|
||||||
|
// for rationale of using this over just EPSILON
|
||||||
|
f32::EPSILON.sqrt() |
||||||
|
} |
||||||
|
|
||||||
|
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { |
||||||
|
let range = (4 - N)..; |
||||||
|
let (lhs, rhs) = unsafe { (&self.0.c[range.clone()], &other.0.c[range]) }; |
||||||
|
lhs.abs_diff_eq(rhs, epsilon) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// region: Matrix math operators
|
||||||
|
|
||||||
|
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) }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
impl<Rhs: Borrow<Self>, const M: usize, const N: usize> PartialEq<Rhs> for Matrix<M, N> { |
||||||
|
fn eq(&self, other: &Rhs) -> bool { |
||||||
|
self.as_rows() == other.borrow().as_rows() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<const M: usize, const N: usize> Eq for Matrix<M, N> {} |
||||||
|
|
||||||
|
#[cfg(feature = "approx")] |
||||||
|
#[doc(cfg(feature = "approx"))] |
||||||
|
impl<const M: usize, const N: usize> AbsDiffEq for Matrix<M, N> { |
||||||
|
type Epsilon = f32; |
||||||
|
|
||||||
|
fn default_epsilon() -> Self::Epsilon { |
||||||
|
// See https://docs.rs/almost/latest/almost/#why-another-crate
|
||||||
|
// for rationale of using this over just EPSILON
|
||||||
|
f32::EPSILON.sqrt() |
||||||
|
} |
||||||
|
|
||||||
|
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { |
||||||
|
let lhs = self.as_rows(); |
||||||
|
let rhs = other.as_rows(); |
||||||
|
|
||||||
|
for row in 0..M { |
||||||
|
for col in 0..N { |
||||||
|
if !lhs[row][col].abs_diff_eq(&rhs[row][col], epsilon) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use approx::assert_abs_diff_eq; |
||||||
|
|
||||||
|
use super::*; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn fvec3() { |
||||||
|
let l = FVec3::splat(1.0); |
||||||
|
let r = FVec3::splat(2.0); |
||||||
|
|
||||||
|
assert_abs_diff_eq!(l + r, FVec3::splat(3.0)); |
||||||
|
assert_abs_diff_eq!(l - r, FVec3::splat(-1.0)); |
||||||
|
assert_abs_diff_eq!(-l, FVec3::splat(-1.0)); |
||||||
|
assert_abs_diff_eq!(l * 1.5, FVec3::splat(1.5)); |
||||||
|
assert_abs_diff_eq!(l / 2.0, FVec3::splat(0.5)); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn fvec4() { |
||||||
|
let l = FVec4::splat(1.0); |
||||||
|
let r = FVec4::splat(2.0); |
||||||
|
|
||||||
|
assert_abs_diff_eq!(l + r, FVec4::splat(3.0)); |
||||||
|
assert_abs_diff_eq!(l - r, FVec4::splat(-1.0)); |
||||||
|
assert_abs_diff_eq!(-l, FVec4::splat(-1.0)); |
||||||
|
assert_abs_diff_eq!(l * 1.5, FVec4::splat(1.5)); |
||||||
|
assert_abs_diff_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_abs_diff_eq!(&(l * r), l); |
||||||
|
assert_abs_diff_eq!(&(l + r), &Matrix3::diagonal(2.0, 3.0, 4.0)); |
||||||
|
assert_abs_diff_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_abs_diff_eq!(&(l * r), l); |
||||||
|
assert_abs_diff_eq!(&(l + r), &Matrix4::diagonal(2.0, 3.0, 4.0, 5.0)); |
||||||
|
assert_abs_diff_eq!(&(l - r), &Matrix4::diagonal(0.0, 1.0, 2.0, 3.0)); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue