Browse Source

Implement FVec types + operators

pull/28/head
Ian Chamberlain 1 year ago
parent
commit
a120178a6c
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 11
      citro3d/src/math.rs
  2. 177
      citro3d/src/math/fvec.rs
  3. 114
      citro3d/src/math/ops.rs

11
citro3d/src/math.rs

@ -1,19 +1,22 @@
//! Safe wrappers for working with matrix and vector types provided by `citro3d`. //! Safe wrappers for working with matrix and vector types provided by `citro3d`.
// TODO: bench FFI calls into `inline statics` generated by bindgen, vs
// reimplementing some of those calls. Many of them are pretty trivial impls
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
mod projection; mod projection;
pub use projection::{Orthographic, Perspective, Projection}; pub use projection::{Orthographic, Perspective, Projection};
mod fvec;
mod ops;
pub use fvec::{FVec, FVec3, FVec4};
/// A 4-vector of `u8`s. /// A 4-vector of `u8`s.
#[doc(alias = "C3D_IVec")] #[doc(alias = "C3D_IVec")]
pub struct IVec(citro3d_sys::C3D_IVec); pub struct IVec(citro3d_sys::C3D_IVec);
/// A 4-vector of `f32`s.
#[doc(alias = "C3D_FVec")]
pub struct FVec(citro3d_sys::C3D_FVec);
/// A quaternion, internally represented the same way as [`FVec`]. /// A quaternion, internally represented the same way as [`FVec`].
#[doc(alias = "C3D_FQuat")] #[doc(alias = "C3D_FQuat")]
pub struct FQuat(citro3d_sys::C3D_FQuat); pub struct FQuat(citro3d_sys::C3D_FQuat);

177
citro3d/src/math/fvec.rs

@ -0,0 +1,177 @@
//! Floating-point vectors.
use std::fmt;
/// A vector of `f32`s.
#[derive(Clone, Copy)]
pub struct FVec<const N: usize>(pub(super) 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.c };
f.debug_tuple(std::any::type_name::<Self>())
.field(&inner)
.finish()
}
}
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 {
Self(unsafe { citro3d_sys::FVec4_New(x, y, z, w) })
}
/// Create a new [`FVec4`], setting each component to `v`.
pub fn splat(v: f32) -> Self {
Self::new(v, v, v, v)
}
/// The vector's `w` component (sometimes also called the `r` component of `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.
pub fn perspective_divide(&self) -> Self {
Self(unsafe { citro3d_sys::FVec4_PerspDivide(self.0) })
}
/// The dot product of two vectors.
pub fn dot(&self, rhs: &Self) -> f32 {
unsafe { citro3d_sys::FVec3_Dot(self.0, rhs.0) }
}
/// The magnitude of the vector.
pub fn magnitude(&self) -> f32 {
unsafe { citro3d_sys::FVec3_Magnitude(self.0) }
}
/// Normalize the vector to a magnitude of `1.0`.
pub fn normalize(&self) -> Self {
Self(unsafe { citro3d_sys::FVec3_Normalize(self.0) })
}
}
impl FVec3 {
/// Create a new [`FVec3`] from its components.
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`.
pub fn splat(v: f32) -> Self {
Self::new(v, v, v)
}
/// The distance between two points in 3D space.
pub fn distance(&self, rhs: &Self) -> f32 {
unsafe { citro3d_sys::FVec3_Distance(self.0, rhs.0) }
}
/// The cross product of two 3D vectors.
pub fn cross(&self, rhs: &Self) -> Self {
Self(unsafe { citro3d_sys::FVec3_Cross(self.0, rhs.0) })
}
/// The dot product of two vectors.
pub fn dot(&self, rhs: &Self) -> f32 {
unsafe { citro3d_sys::FVec3_Dot(self.0, rhs.0) }
}
/// The magnitude of the vector.
pub fn magnitude(&self) -> f32 {
unsafe { citro3d_sys::FVec3_Magnitude(self.0) }
}
/// Normalize the vector to a magnitude of `1.0`.
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 super::*;
#[test]
fn fvec4() {
let l = FVec4::new(2.0, 2.0, 2.0, 2.0);
assert_eq!(l, FVec4::splat(2.0));
for component in [l.x(), l.y(), l.z(), l.w()] {
assert!((component - 2.0).abs() < f32::EPSILON);
}
assert_eq!(l.perspective_divide(), FVec4::splat(1.0));
let dot = l.dot(&FVec4::splat(3.0));
assert!((dot - 24.0).abs() < f32::EPSILON);
assert!((l.magnitude() - 8.0).abs() < f32::EPSILON);
let norm = l.normalize();
assert!((norm.magnitude() - 1.0).abs() < f32::EPSILON);
for component in [l.y(), l.z(), l.w()] {
assert!((component - l.x()).abs() < f32::EPSILON);
}
}
#[test]
fn fvec3() {
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!((component - 2.0).abs() < f32::EPSILON);
}
let dot = l.dot(&FVec3::splat(3.0));
assert!((dot - 18.0).abs() < f32::EPSILON);
assert!((l.magnitude() - 8.0).abs() < f32::EPSILON);
let norm = l.normalize();
assert!((norm.magnitude() - 1.0).abs() < f32::EPSILON);
for component in [l.y(), l.z()] {
assert!((l.x() - component).abs() < f32::EPSILON);
}
}
}

114
citro3d/src/math/ops.rs

@ -0,0 +1,114 @@
use std::borrow::Borrow;
use std::ops::{Add, Div, Mul, Neg, Sub};
use super::{FVec, FVec3, FVec4};
impl<Rhs: Borrow<Self>> Add<Rhs> for FVec4 {
type Output = Self;
fn add(self, rhs: Rhs) -> Self::Output {
Self(unsafe { citro3d_sys::FVec4_Add(self.0, rhs.borrow().0) })
}
}
impl<Rhs: Borrow<Self>> Sub<Rhs> for FVec4 {
type Output = Self;
fn sub(self, rhs: Rhs) -> Self::Output {
Self(unsafe { citro3d_sys::FVec4_Add(self.0, rhs.borrow().0) })
}
}
impl Neg for FVec4 {
type Output = Self;
fn neg(self) -> Self::Output {
Self(unsafe { citro3d_sys::FVec4_Negate(self.0) })
}
}
impl Mul<f32> for FVec4 {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self(unsafe { citro3d_sys::FVec4_Scale(self.0, rhs) })
}
}
impl<Rhs: Borrow<Self>> Add<Rhs> for FVec3 {
type Output = Self;
fn add(self, rhs: Rhs) -> Self::Output {
Self(unsafe { citro3d_sys::FVec3_Add(self.0, rhs.borrow().0) })
}
}
impl<Rhs: Borrow<Self>> Sub<Rhs> for FVec3 {
type Output = Self;
fn sub(self, rhs: Rhs) -> Self::Output {
Self(unsafe { citro3d_sys::FVec3_Add(self.0, rhs.borrow().0) })
}
}
impl Neg for FVec3 {
type Output = Self;
fn neg(self) -> Self::Output {
Self(unsafe { citro3d_sys::FVec3_Negate(self.0) })
}
}
impl Mul<f32> for FVec3 {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self(unsafe { citro3d_sys::FVec3_Scale(self.0, rhs) })
}
}
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)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::op_ref)]
use super::*;
#[test]
fn vec3_ops() {
let l = FVec3::splat(1.0);
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));
}
#[test]
fn vec4_ops() {
let l = FVec4::splat(1.0);
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));
}
}
Loading…
Cancel
Save