diff --git a/citro3d/Cargo.toml b/citro3d/Cargo.toml index 0762a4d..4147662 100644 --- a/citro3d/Cargo.toml +++ b/citro3d/Cargo.toml @@ -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"] diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 0ba340d..985db2a 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -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; diff --git a/citro3d/src/math/fvec.rs b/citro3d/src/math/fvec.rs index ba77954..5813424 100644 --- a/citro3d/src/math/fvec.rs +++ b/citro3d/src/math/fvec.rs @@ -20,8 +20,41 @@ impl fmt::Debug for FVec { } } +impl FVec { + /// 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 { /// ``` /// # 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 FVec { - /// 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 { 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 { 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[..]); } } diff --git a/citro3d/src/math/ops.rs b/citro3d/src/math/ops.rs index e66ff82..311c473 100644 --- a/citro3d/src/math/ops.rs +++ b/citro3d/src/math/ops.rs @@ -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 PartialEq for FVec { impl Eq for FVec {} -impl ApproxEq for FVec -where - f32: ApproxEq, -{ - type Margin = Margin; +#[cfg(feature = "approx")] +impl AbsDiffEq for FVec { + 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>(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, const M: usize, const N: usize> PartialEq for Matri impl Eq for Matrix {} -impl ApproxEq for &Matrix -where - Margin: Copy + Default, - f32: ApproxEq, -{ - type Margin = Margin; +#[cfg(feature = "approx")] +#[doc(cfg(feature = "approx"))] +impl AbsDiffEq for Matrix { + 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>(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 #[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 { 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 { 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)); } }