Browse Source

Add approximate equal impls and tests

More doctests instead of libtests.

For now, just FVec4, but FVec4 and matrices coming next.
pull/28/head
Ian Chamberlain 1 year ago
parent
commit
f770f504b0
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 3
      citro3d/Cargo.toml
  2. 95
      citro3d/src/math/fvec.rs
  3. 5
      citro3d/src/math/matrix.rs
  4. 97
      citro3d/src/math/ops.rs

3
citro3d/Cargo.toml

@ -6,12 +6,13 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
citro3d-macros = { version = "0.1.0", path = "../citro3d-macros" }
bitflags = "1.3.2" bitflags = "1.3.2"
bytemuck = { version = "1.10.0", features = ["extern_crate_std"] } bytemuck = { version = "1.10.0", features = ["extern_crate_std"] }
citro3d-macros = { version = "0.1.0", path = "../citro3d-macros" }
citro3d-sys = { git = "https://github.com/rust3ds/citro3d-rs.git" } citro3d-sys = { git = "https://github.com/rust3ds/citro3d-rs.git" }
ctru-rs = { git = "https://github.com/rust3ds/ctru-rs.git" } ctru-rs = { git = "https://github.com/rust3ds/ctru-rs.git" }
ctru-sys = { git = "https://github.com/rust3ds/ctru-rs.git" } ctru-sys = { git = "https://github.com/rust3ds/ctru-rs.git" }
float-cmp = "0.9.0"
libc = "0.2.125" libc = "0.2.125"
[dev-dependencies] [dev-dependencies]

95
citro3d/src/math/fvec.rs

@ -15,45 +15,96 @@ pub type FVec4 = FVec<4>;
impl<const N: usize> fmt::Debug for FVec<N> { impl<const N: usize> fmt::Debug for FVec<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let inner = unsafe { self.0.__bindgen_anon_1 }; let inner = unsafe { self.0.__bindgen_anon_1 };
f.debug_tuple(std::any::type_name::<Self>()) let type_name = std::any::type_name::<Self>().split("::").last().unwrap();
.field(&inner) f.debug_tuple(type_name).field(&inner).finish()
.finish()
} }
} }
impl FVec4 { impl FVec4 {
/// Create a new [`FVec4`] from its components. /// Create a new [`FVec4`] from its components.
#[doc(alias = "FVec4_New")]
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
Self(unsafe { citro3d_sys::FVec4_New(x, y, z, w) }) Self(unsafe { citro3d_sys::FVec4_New(x, y, z, w) })
} }
/// Create a new [`FVec4`], setting each component to `v`. /// Create a new [`FVec4`], setting each component to `v`.
///
/// # Example
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::math::FVec4;
/// # use float_cmp::assert_approx_eq;
/// let v = FVec4::splat(1.0);
/// assert_approx_eq!(FVec4, v, FVec4::new(1.0, 1.0, 1.0, 1.0));
/// ```
pub fn splat(v: f32) -> Self { pub fn splat(v: f32) -> Self {
Self::new(v, v, v, v) Self::new(v, v, v, v)
} }
/// The vector's `w` component (sometimes also called the `r` component of `ijk[r]`). /// The vector's `w` component (sometimes also called `r` for the real
/// component of a quaternion `ijk[r]`).
#[doc(alias = "r")] #[doc(alias = "r")]
pub fn w(&self) -> f32 { pub fn w(&self) -> f32 {
unsafe { self.0.__bindgen_anon_1.w } unsafe { self.0.__bindgen_anon_1.w }
} }
/// Divide the vector's XYZ components by its W component. /// Divide the vector's XYZ components by its W component.
/// # Example
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::math::FVec4;
/// # use float_cmp::assert_approx_eq;
/// let v = FVec4::new(2.0, 4.0, 6.0, 2.0);
/// assert_approx_eq!(
/// FVec4,
/// v.perspective_divide(),
/// FVec4::new(1.0, 2.0, 3.0, 1.0)
/// );
/// ```
#[doc(alias = "FVec4_PerspDivide")]
pub fn perspective_divide(&self) -> Self { pub fn perspective_divide(&self) -> Self {
Self(unsafe { citro3d_sys::FVec4_PerspDivide(self.0) }) Self(unsafe { citro3d_sys::FVec4_PerspDivide(self.0) })
} }
/// The dot product of two vectors. /// The dot product of two vectors.
/// # Example
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::math::FVec4;
/// # use float_cmp::assert_approx_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_approx_eq!(f32, v1.dot(&v2), 7.0);
/// ```
#[doc(alias = "FVec4_Dot")]
pub fn dot(&self, rhs: &Self) -> f32 { pub fn dot(&self, rhs: &Self) -> f32 {
unsafe { citro3d_sys::FVec4_Dot(self.0, rhs.0) } unsafe { citro3d_sys::FVec4_Dot(self.0, rhs.0) }
} }
/// The magnitude of the vector. /// The magnitude of the vector.
/// # Example
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::math::FVec4;
/// # use float_cmp::assert_approx_eq;
/// let v = FVec4::splat(1.0);
/// assert_approx_eq!(f32, v.magnitude(), 2.0);
/// ```
#[doc(alias = "FVec4_Magnitude")]
pub fn magnitude(&self) -> f32 { pub fn magnitude(&self) -> f32 {
unsafe { citro3d_sys::FVec4_Magnitude(self.0) } unsafe { citro3d_sys::FVec4_Magnitude(self.0) }
} }
/// Normalize the vector to a magnitude of `1.0`. /// Normalize the vector to a magnitude of `1.0`.
/// # Example
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::math::FVec4;
/// # use float_cmp::assert_approx_eq;
/// let v = FVec4::new(1.0, 2.0, 2.0, 4.0);
/// assert_approx_eq!(FVec4, v, FVec4::new(0.1, 0.4, 0.4, 0.8));
/// ```
#[doc(alias = "FVec3_Normalize")]
pub fn normalize(&self) -> Self { pub fn normalize(&self) -> Self {
Self(unsafe { citro3d_sys::FVec3_Normalize(self.0) }) Self(unsafe { citro3d_sys::FVec3_Normalize(self.0) })
} }
@ -61,6 +112,7 @@ impl FVec4 {
impl FVec3 { impl FVec3 {
/// Create a new [`FVec3`] from its components. /// Create a new [`FVec3`] from its components.
#[doc(alias = "FVec3_New")]
pub fn new(x: f32, y: f32, z: f32) -> Self { pub fn new(x: f32, y: f32, z: f32) -> Self {
Self(unsafe { citro3d_sys::FVec3_New(x, y, z) }) Self(unsafe { citro3d_sys::FVec3_New(x, y, z) })
} }
@ -71,26 +123,31 @@ impl FVec3 {
} }
/// The distance between two points in 3D space. /// The distance between two points in 3D space.
#[doc(alias = "FVec3_Distance")]
pub fn distance(&self, rhs: &Self) -> f32 { pub fn distance(&self, rhs: &Self) -> f32 {
unsafe { citro3d_sys::FVec3_Distance(self.0, rhs.0) } unsafe { citro3d_sys::FVec3_Distance(self.0, rhs.0) }
} }
/// The cross product of two 3D vectors. /// The cross product of two 3D vectors.
#[doc(alias = "FVec3_Cross")]
pub fn cross(&self, rhs: &Self) -> Self { pub fn cross(&self, rhs: &Self) -> Self {
Self(unsafe { citro3d_sys::FVec3_Cross(self.0, rhs.0) }) Self(unsafe { citro3d_sys::FVec3_Cross(self.0, rhs.0) })
} }
/// The dot product of two vectors. /// The dot product of two vectors.
#[doc(alias = "FVec3_Dot")]
pub fn dot(&self, rhs: &Self) -> f32 { pub fn dot(&self, rhs: &Self) -> f32 {
unsafe { citro3d_sys::FVec3_Dot(self.0, rhs.0) } unsafe { citro3d_sys::FVec3_Dot(self.0, rhs.0) }
} }
/// The magnitude of the vector. /// The magnitude of the vector.
#[doc(alias = "FVec3_Magnitude")]
pub fn magnitude(&self) -> f32 { pub fn magnitude(&self) -> f32 {
unsafe { citro3d_sys::FVec3_Magnitude(self.0) } unsafe { citro3d_sys::FVec3_Magnitude(self.0) }
} }
/// Normalize the vector to a magnitude of `1.0`. /// Normalize the vector to a magnitude of `1.0`.
#[doc(alias = "FVec3_Normalize")]
pub fn normalize(&self) -> Self { pub fn normalize(&self) -> Self {
Self(unsafe { citro3d_sys::FVec3_Normalize(self.0) }) Self(unsafe { citro3d_sys::FVec3_Normalize(self.0) })
} }
@ -119,33 +176,21 @@ mod tests {
use super::*; use super::*;
// TODO: These could probably all be doctests instead it's just sort of a pain
#[test] #[test]
fn fvec4() { fn fvec4() {
let l = FVec4::new(2.0, 2.0, 2.0, 2.0); let v = FVec4::new(1.0, 2.0, 3.0, 4.0);
let actual = [v.x(), v.y(), v.z(), v.w()];
assert_eq!(l, FVec4::splat(2.0)); let expected = [1.0, 2.0, 3.0, 4.0];
assert_approx_eq!(&[f32], &actual, &expected);
for component in [l.x(), l.y(), l.z(), l.w()] {
assert_approx_eq!(f32, component, 2.0);
}
assert_eq!(l.perspective_divide(), FVec4::splat(1.0));
let dot = l.dot(&FVec4::splat(3.0));
assert_approx_eq!(f32, dot, 24.0);
assert_approx_eq!(f32, l.magnitude(), 4.0);
let norm = l.normalize();
assert_approx_eq!(f32, norm.magnitude(), 1.0);
for component in [l.y(), l.z(), l.w()] {
assert_approx_eq!(f32, component, l.x());
}
} }
#[test] #[test]
fn fvec3() { 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_approx_eq!(&[f32], &actual, &expected);
let l = FVec3::new(2.0, 2.0, 2.0); let l = FVec3::new(2.0, 2.0, 2.0);
assert_eq!(l, FVec3::splat(2.0)); assert_eq!(l, FVec3::splat(2.0));

5
citro3d/src/math/matrix.rs

@ -41,6 +41,11 @@ mod private {
/// Trim the matrix down to only the rows and columns we care about, /// Trim the matrix down to only the rows and columns we care about,
/// since the inner representation is always 4x4. /// 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] { pub(crate) fn as_rows(&self) -> [[f32; N]; M] {
let rows = unsafe { self.0.r }.map(|row| -> [f32; N] { let rows = unsafe { self.0.r }.map(|row| -> [f32; N] {
// Rows are stored in WZYX order, so we slice from back to front. // Rows are stored in WZYX order, so we slice from back to front.

97
citro3d/src/math/ops.rs

@ -2,11 +2,16 @@ use std::borrow::Borrow;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::ops::{Add, Deref, Div, Mul, Neg, Sub}; use std::ops::{Add, Deref, Div, Mul, Neg, Sub};
use float_cmp::ApproxEq;
use super::{FVec, FVec3, FVec4, Matrix, Matrix3, Matrix4}; use super::{FVec, FVec3, FVec4, Matrix, Matrix3, Matrix4};
// region: FVec4 math operators
impl Add for FVec4 { impl Add for FVec4 {
type Output = Self; type Output = Self;
#[doc(alias = "FVec4_Add")]
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
Self(unsafe { citro3d_sys::FVec4_Add(self.0, rhs.0) }) Self(unsafe { citro3d_sys::FVec4_Add(self.0, rhs.0) })
} }
@ -15,6 +20,7 @@ impl Add for FVec4 {
impl Sub for FVec4 { impl Sub for FVec4 {
type Output = Self; type Output = Self;
#[doc(alias = "FVec4_Subtract")]
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
Self(unsafe { citro3d_sys::FVec4_Subtract(self.0, rhs.0) }) Self(unsafe { citro3d_sys::FVec4_Subtract(self.0, rhs.0) })
} }
@ -23,6 +29,7 @@ impl Sub for FVec4 {
impl Neg for FVec4 { impl Neg for FVec4 {
type Output = Self; type Output = Self;
#[doc(alias = "FVec4_Negate")]
fn neg(self) -> Self::Output { fn neg(self) -> Self::Output {
Self(unsafe { citro3d_sys::FVec4_Negate(self.0) }) Self(unsafe { citro3d_sys::FVec4_Negate(self.0) })
} }
@ -31,14 +38,20 @@ impl Neg for FVec4 {
impl Mul<f32> for FVec4 { impl Mul<f32> for FVec4 {
type Output = Self; type Output = Self;
#[doc(alias = "FVec4_Scale")]
fn mul(self, rhs: f32) -> Self::Output { fn mul(self, rhs: f32) -> Self::Output {
Self(unsafe { citro3d_sys::FVec4_Scale(self.0, rhs) }) Self(unsafe { citro3d_sys::FVec4_Scale(self.0, rhs) })
} }
} }
// endregion
// region: FVec3 math operators
impl Add for FVec3 { impl Add for FVec3 {
type Output = Self; type Output = Self;
#[doc(alias = "FVec3_Add")]
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
Self(unsafe { citro3d_sys::FVec3_Add(self.0, rhs.0) }) Self(unsafe { citro3d_sys::FVec3_Add(self.0, rhs.0) })
} }
@ -47,6 +60,7 @@ impl Add for FVec3 {
impl Sub for FVec3 { impl Sub for FVec3 {
type Output = Self; type Output = Self;
#[doc(alias = "FVec3_Subtract")]
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
Self(unsafe { citro3d_sys::FVec3_Subtract(self.0, rhs.0) }) Self(unsafe { citro3d_sys::FVec3_Subtract(self.0, rhs.0) })
} }
@ -55,6 +69,7 @@ impl Sub for FVec3 {
impl Neg for FVec3 { impl Neg for FVec3 {
type Output = Self; type Output = Self;
#[doc(alias = "FVec3_Negate")]
fn neg(self) -> Self::Output { fn neg(self) -> Self::Output {
Self(unsafe { citro3d_sys::FVec3_Negate(self.0) }) Self(unsafe { citro3d_sys::FVec3_Negate(self.0) })
} }
@ -63,11 +78,14 @@ impl Neg for FVec3 {
impl Mul<f32> for FVec3 { impl Mul<f32> for FVec3 {
type Output = Self; type Output = Self;
#[doc(alias = "FVec3_Scale")]
fn mul(self, rhs: f32) -> Self::Output { fn mul(self, rhs: f32) -> Self::Output {
Self(unsafe { citro3d_sys::FVec3_Scale(self.0, rhs) }) Self(unsafe { citro3d_sys::FVec3_Scale(self.0, rhs) })
} }
} }
// endregion
impl<const N: usize> Div<f32> for FVec<N> impl<const N: usize> Div<f32> for FVec<N>
where where
FVec<N>: Mul<f32>, FVec<N>: Mul<f32>,
@ -81,12 +99,29 @@ where
impl<const N: usize> PartialEq for FVec<N> { impl<const N: usize> PartialEq for FVec<N> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
unsafe { self.0.c == other.0.c } let range = (4 - N)..;
unsafe { self.0.c[range.clone()] == other.0.c[range] }
} }
} }
impl<const N: usize> Eq for FVec<N> {} impl<const N: usize> Eq for FVec<N> {}
impl<Margin: Copy + Default, const N: usize> ApproxEq for FVec<N>
where
f32: ApproxEq<Margin = Margin>,
{
type Margin = Margin;
fn approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool {
let margin = margin.into();
let range = (4 - N)..;
let (lhs, rhs) = unsafe { (&self.0.c[range.clone()], &other.0.c[range]) };
lhs.approx_eq(rhs, margin)
}
}
// region: Matrix math operators
impl<Rhs: Borrow<Self>, const M: usize, const N: usize> Add<Rhs> for &Matrix<M, N> { impl<Rhs: Borrow<Self>, const M: usize, const N: usize> Add<Rhs> for &Matrix<M, N> {
type Output = <Self as Deref>::Target; type Output = <Self as Deref>::Target;
@ -155,6 +190,8 @@ impl Mul<FVec3> for &Matrix<4, 3> {
} }
} }
// endregion
impl<Rhs: Borrow<Self>, const M: usize, const N: usize> PartialEq<Rhs> for Matrix<M, N> { impl<Rhs: Borrow<Self>, const M: usize, const N: usize> PartialEq<Rhs> for Matrix<M, N> {
fn eq(&self, other: &Rhs) -> bool { fn eq(&self, other: &Rhs) -> bool {
self.as_rows() == other.borrow().as_rows() self.as_rows() == other.borrow().as_rows()
@ -163,8 +200,34 @@ impl<Rhs: Borrow<Self>, const M: usize, const N: usize> PartialEq<Rhs> for Matri
impl<const M: usize, const N: usize> Eq for Matrix<M, N> {} impl<const M: usize, const N: usize> Eq for Matrix<M, N> {}
impl<Margin, const M: usize, const N: usize> ApproxEq for &Matrix<M, N>
where
Margin: Copy + Default,
f32: ApproxEq<Margin = Margin>,
{
type Margin = Margin;
fn approx_eq<Marg: Into<Self::Margin>>(self, other: Self, margin: Marg) -> bool {
let margin = margin.into();
let lhs = self.as_rows();
let rhs = other.as_rows();
for row in 0..M {
for col in 0..N {
if !lhs[row][col].approx_eq(rhs[row][col], margin) {
return false;
}
}
}
true
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use float_cmp::assert_approx_eq;
use super::*; use super::*;
#[test] #[test]
@ -172,11 +235,11 @@ mod tests {
let l = FVec3::splat(1.0); let l = FVec3::splat(1.0);
let r = FVec3::splat(2.0); let r = FVec3::splat(2.0);
assert_eq!(l + r, FVec3::splat(3.0)); assert_approx_eq!(FVec3, l + r, FVec3::splat(3.0));
assert_eq!(l - r, FVec3::splat(-1.0)); assert_approx_eq!(FVec3, l - r, FVec3::splat(-1.0));
assert_eq!(-l, FVec3::splat(-1.0)); assert_approx_eq!(FVec3, -l, FVec3::splat(-1.0));
assert_eq!(l * 1.5, FVec3::splat(1.5)); assert_approx_eq!(FVec3, l * 1.5, FVec3::splat(1.5));
assert_eq!(l / 2.0, FVec3::splat(0.5)); assert_approx_eq!(FVec3, l / 2.0, FVec3::splat(0.5));
} }
#[test] #[test]
@ -184,11 +247,11 @@ mod tests {
let l = FVec4::splat(1.0); let l = FVec4::splat(1.0);
let r = FVec4::splat(2.0); let r = FVec4::splat(2.0);
assert_eq!(l + r, FVec4::splat(3.0)); assert_approx_eq!(FVec4, l + r, FVec4::splat(3.0));
assert_eq!(l - r, FVec4::splat(-1.0)); assert_approx_eq!(FVec4, l - r, FVec4::splat(-1.0));
assert_eq!(-l, FVec4::splat(-1.0)); assert_approx_eq!(FVec4, -l, FVec4::splat(-1.0));
assert_eq!(l * 1.5, FVec4::splat(1.5)); assert_approx_eq!(FVec4, l * 1.5, FVec4::splat(1.5));
assert_eq!(l / 2.0, FVec4::splat(0.5)); assert_approx_eq!(FVec4, l / 2.0, FVec4::splat(0.5));
} }
#[test] #[test]
@ -197,9 +260,9 @@ mod tests {
let r = Matrix3::identity(); let r = Matrix3::identity();
let (l, r) = (&l, &r); let (l, r) = (&l, &r);
assert_eq!(l * r, l); assert_approx_eq!(&Matrix3, &(l * r), l);
assert_eq!(l + r, Matrix3::diagonal(2.0, 3.0, 4.0)); assert_approx_eq!(&Matrix3, &(l + r), &Matrix3::diagonal(2.0, 3.0, 4.0));
assert_eq!(l - r, Matrix3::diagonal(0.0, 1.0, 2.0)); assert_approx_eq!(&Matrix3, &(l - r), &Matrix3::diagonal(0.0, 1.0, 2.0));
} }
#[test] #[test]
@ -208,8 +271,8 @@ mod tests {
let r = Matrix4::identity(); let r = Matrix4::identity();
let (l, r) = (&l, &r); let (l, r) = (&l, &r);
assert_eq!(l * r, l); assert_approx_eq!(&Matrix4, &(l * r), l);
assert_eq!(l + r, Matrix4::diagonal(2.0, 3.0, 4.0, 5.0)); assert_approx_eq!(&Matrix4, &(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)); assert_approx_eq!(&Matrix4, &(l - r), &Matrix4::diagonal(0.0, 1.0, 2.0, 3.0));
} }
} }

Loading…
Cancel
Save