Browse Source

Swap to `approx` for comparisons

Hide this impl behind a feature, which is always enabled for testing but
disabled by default for downstream crates. This feels a bit tidier to me.
pull/28/head
Ian Chamberlain 1 year ago
parent
commit
3174e66e7e
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 21
      citro3d/Cargo.toml
  2. 9
      citro3d/src/lib.rs
  3. 191
      citro3d/src/math/fvec.rs
  4. 81
      citro3d/src/math/ops.rs

21
citro3d/Cargo.toml

@ -6,15 +6,32 @@ version = "0.1.0" @@ -6,15 +6,32 @@ version = "0.1.0"
edition = "2021"
[dependencies]
approx = { version = "0.5.1", optional = true }
bitflags = "1.3.2"
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" }
ctru-rs = { git = "https://github.com/rust3ds/ctru-rs.git" }
ctru-sys = { git = "https://github.com/rust3ds/ctru-rs.git" }
float-cmp = "0.9.0"
document-features = "0.2.7"
libc = "0.2.125"
[features]
default = []
## Enable this feature to use the `approx` crate for comparing vectors and matrices.
approx = ["dep:approx"]
[dev-dependencies]
float-cmp = "0.9.0"
test-runner = { git = "https://github.com/rust3ds/test-runner.git" }
[dev-dependencies.citro3d]
# Basically, this works like `cargo 3ds test --features ...` for building tests
# https://github.com/rust-lang/cargo/issues/2911#issuecomment-749580481
path = "."
features = ["approx"]
[package.metadata.docs.rs]
all-features = true
default-target = "armv6k-nintendo-3ds"
targs = []
cargo-args = ["-Z", "build-std"]

9
citro3d/src/lib.rs

@ -1,6 +1,13 @@ @@ -1,6 +1,13 @@
//! Safe Rust bindings to `citro3d`.
#![feature(custom_test_frameworks)]
#![test_runner(test_runner::run_gdb)]
#![feature(doc_cfg)]
#![feature(doc_auto_cfg)]
//! Safe Rust bindings to `citro3d`. This crate wraps `citro3d-sys` to provide
//! safer APIs for graphics programs targeting the 3DS.
//!
//! ## Feature flags
#![doc = document_features::document_features!()]
pub mod attrib;
pub mod buffer;

191
citro3d/src/math/fvec.rs

@ -20,8 +20,41 @@ impl<const N: usize> fmt::Debug for FVec<N> { @@ -20,8 +20,41 @@ impl<const N: usize> fmt::Debug for FVec<N> {
}
}
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) })
@ -33,146 +66,185 @@ impl FVec4 { @@ -33,146 +66,185 @@ impl FVec4 {
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::math::FVec4;
/// # use float_cmp::assert_approx_eq;
/// # use approx::assert_abs_diff_eq;
/// let v = FVec4::splat(1.0);
/// assert_approx_eq!(FVec4, v, FVec4::new(1.0, 1.0, 1.0, 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)
}
/// The vector's `w` component (sometimes also called `r` for the real
/// component of a quaternion `ijk[r]`).
#[doc(alias = "r")]
pub fn w(&self) -> f32 {
unsafe { self.0.__bindgen_anon_1.w }
}
/// 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;
/// # use approx::assert_abs_diff_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)
/// );
/// 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 {
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 float_cmp::assert_approx_eq;
/// # 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_approx_eq!(f32, v1.dot(&v2), 7.0);
/// assert_abs_diff_eq!(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) }
}
/// The magnitude of the vector.
///
/// # Example
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::math::FVec4;
/// # use float_cmp::assert_approx_eq;
/// # use approx::assert_abs_diff_eq;
/// let v = FVec4::splat(1.0);
/// assert_approx_eq!(f32, v.magnitude(), 2.0);
/// assert_abs_diff_eq!(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) }
}
/// 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;
/// # use approx::assert_abs_diff_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));
/// assert_abs_diff_eq!(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) })
}
}
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 {
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 {
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 {
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 {
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(2.0);
/// assert_abs_diff_eq!(v.normalize(), FVec3::splat(1.0));
/// ```
#[doc(alias = "FVec3_Normalize")]
pub fn normalize(&self) -> Self {
pub fn normalize(self) -> Self {
Self(unsafe { citro3d_sys::FVec3_Normalize(self.0) })
}
}
impl<const N: usize> FVec<N> {
/// The vector's `x` component (sometimes also called the `i` component of `ijk[r]`).
pub fn x(&self) -> f32 {
unsafe { self.0.__bindgen_anon_1.x }
}
/// The vector's `y` component (sometimes also called the `j` component of `ijk[r]`).
pub fn y(&self) -> f32 {
unsafe { self.0.__bindgen_anon_1.y }
}
/// The vector's `i` component (sometimes also called the `k` component of `ijk[r]`).
pub fn z(&self) -> f32 {
unsafe { self.0.__bindgen_anon_1.z }
}
}
#[cfg(test)]
mod tests {
use float_cmp::assert_approx_eq;
use approx::assert_abs_diff_eq;
use super::*;
@ -181,7 +253,7 @@ mod tests { @@ -181,7 +253,7 @@ mod tests {
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_approx_eq!(&[f32], &actual, &expected);
assert_abs_diff_eq!(&actual[..], &expected[..]);
}
#[test]
@ -189,25 +261,6 @@ mod tests { @@ -189,25 +261,6 @@ mod tests {
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);
assert_eq!(l, FVec3::splat(2.0));
for component in [l.x(), l.y(), l.z()] {
assert_approx_eq!(f32, component, 2.0);
}
let dot = l.dot(&FVec3::splat(3.0));
assert_approx_eq!(f32, dot, 18.0);
assert_approx_eq!(f32, l.magnitude(), f32::sqrt(12.0));
let norm = l.normalize();
assert_approx_eq!(f32, norm.magnitude(), 1.0);
for component in [l.y(), l.z()] {
assert_approx_eq!(f32, l.x(), component);
}
assert_abs_diff_eq!(&actual[..], &expected[..]);
}
}

81
citro3d/src/math/ops.rs

@ -2,7 +2,8 @@ use std::borrow::Borrow; @@ -2,7 +2,8 @@ use std::borrow::Borrow;
use std::mem::MaybeUninit;
use std::ops::{Add, Deref, Div, Mul, Neg, Sub};
use float_cmp::ApproxEq;
#[cfg(feature = "approx")]
use approx::AbsDiffEq;
use super::{FVec, FVec3, FVec4, Matrix, Matrix3, Matrix4};
@ -106,17 +107,20 @@ impl<const N: usize> PartialEq for FVec<N> { @@ -106,17 +107,20 @@ impl<const N: usize> PartialEq 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;
#[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 approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool {
let margin = margin.into();
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.approx_eq(rhs, margin)
lhs.abs_diff_eq(rhs, epsilon)
}
}
@ -200,21 +204,24 @@ impl<Rhs: Borrow<Self>, const M: usize, const N: usize> PartialEq<Rhs> for Matri @@ -200,21 +204,24 @@ 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<Margin, const M: usize, const N: usize> ApproxEq for &Matrix<M, N>
where
Margin: Copy + Default,
f32: ApproxEq<Margin = Margin>,
{
type Margin = Margin;
#[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 approx_eq<Marg: Into<Self::Margin>>(self, other: Self, margin: Marg) -> bool {
let margin = margin.into();
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].approx_eq(rhs[row][col], margin) {
if !lhs[row][col].abs_diff_eq(&rhs[row][col], epsilon) {
return false;
}
}
@ -226,32 +233,32 @@ where @@ -226,32 +233,32 @@ where
#[cfg(test)]
mod tests {
use float_cmp::assert_approx_eq;
use approx::assert_abs_diff_eq;
use super::*;
#[test]
fn vec3() {
fn fvec3() {
let l = FVec3::splat(1.0);
let r = FVec3::splat(2.0);
assert_approx_eq!(FVec3, l + r, FVec3::splat(3.0));
assert_approx_eq!(FVec3, l - r, FVec3::splat(-1.0));
assert_approx_eq!(FVec3, -l, FVec3::splat(-1.0));
assert_approx_eq!(FVec3, l * 1.5, FVec3::splat(1.5));
assert_approx_eq!(FVec3, l / 2.0, FVec3::splat(0.5));
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 vec4() {
fn fvec4() {
let l = FVec4::splat(1.0);
let r = FVec4::splat(2.0);
assert_approx_eq!(FVec4, l + r, FVec4::splat(3.0));
assert_approx_eq!(FVec4, l - r, FVec4::splat(-1.0));
assert_approx_eq!(FVec4, -l, FVec4::splat(-1.0));
assert_approx_eq!(FVec4, l * 1.5, FVec4::splat(1.5));
assert_approx_eq!(FVec4, l / 2.0, FVec4::splat(0.5));
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]
@ -260,9 +267,9 @@ mod tests { @@ -260,9 +267,9 @@ mod tests {
let r = Matrix3::identity();
let (l, r) = (&l, &r);
assert_approx_eq!(&Matrix3, &(l * r), l);
assert_approx_eq!(&Matrix3, &(l + r), &Matrix3::diagonal(2.0, 3.0, 4.0));
assert_approx_eq!(&Matrix3, &(l - r), &Matrix3::diagonal(0.0, 1.0, 2.0));
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]
@ -271,8 +278,8 @@ mod tests { @@ -271,8 +278,8 @@ mod tests {
let r = Matrix4::identity();
let (l, r) = (&l, &r);
assert_approx_eq!(&Matrix4, &(l * r), l);
assert_approx_eq!(&Matrix4, &(l + r), &Matrix4::diagonal(2.0, 3.0, 4.0, 5.0));
assert_approx_eq!(&Matrix4, &(l - r), &Matrix4::diagonal(0.0, 1.0, 2.0, 3.0));
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…
Cancel
Save