Browse Source

Refactor matrix into new module

Fix up a couple test bugs and do some other refactor / cleanup
pull/28/head
Ian Chamberlain 1 year ago
parent
commit
8b7e8fe71b
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 331
      citro3d/src/math.rs
  2. 2
      citro3d/src/math/fvec.rs
  3. 202
      citro3d/src/math/matrix.rs
  4. 5
      citro3d/src/math/ops.rs
  5. 135
      citro3d/src/math/projection.rs

331
citro3d/src/math.rs

@ -3,15 +3,17 @@
// TODO: bench FFI calls into `inline statics` generated by bindgen, vs // TODO: bench FFI calls into `inline statics` generated by bindgen, vs
// reimplementing some of those calls. Many of them are pretty trivial impls // reimplementing some of those calls. Many of them are pretty trivial impls
use std::mem::MaybeUninit;
mod projection;
pub use projection::{Orthographic, Perspective, Projection};
mod fvec; mod fvec;
mod matrix;
mod ops; mod ops;
mod projection;
pub use fvec::{FVec, FVec3, FVec4}; pub use fvec::{FVec, FVec3, FVec4};
pub use matrix::{Matrix, Matrix3, Matrix4};
pub use projection::{
AspectRatio, ClipPlanes, CoordinateOrientation, Orthographic, Perspective, Projection,
ScreenOrientation, StereoDisplacement,
};
/// A 4-vector of `u8`s. /// A 4-vector of `u8`s.
#[doc(alias = "C3D_IVec")] #[doc(alias = "C3D_IVec")]
@ -20,322 +22,3 @@ pub struct IVec(citro3d_sys::C3D_IVec);
/// 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);
mod mtx {
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> fmt::Debug for Matrix<M, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let inner = unsafe { self.0.r.map(|v| v.c) };
f.debug_tuple(std::any::type_name::<Self>())
.field(&inner)
.finish()
}
}
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
}
}
}
pub use mtx::Matrix;
/// 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.
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`].
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.
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.
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())
}
}
}
// region: Projection configuration
//
// TODO: maybe move into `mod projection`, or hoist `projection::*` into here.
// it will probably mostly depend on how big all the matrices/vec impls get.
// Also worth considering is whether `mod projection` should be pub.
/// The [orientation](https://en.wikipedia.org/wiki/Orientation_(geometry))
/// (or "handedness") of the coordinate system. Coordinates are always +Y-up,
/// +X-right.
#[derive(Clone, Copy, Debug)]
pub enum CoordinateOrientation {
/// A left-handed coordinate system. +Z points into the screen.
LeftHanded,
/// A right-handed coordinate system. +Z points out of the screen.
RightHanded,
}
impl CoordinateOrientation {
pub(crate) fn is_left_handed(self) -> bool {
matches!(self, Self::LeftHanded)
}
}
impl Default for CoordinateOrientation {
/// This is an opinionated default, but [`RightHanded`](Self::RightHanded)
/// seems to be the preferred coordinate system for most
/// [examples](https://github.com/devkitPro/3ds-examples)
/// from upstream, and is also fairly common in other applications.
fn default() -> Self {
Self::RightHanded
}
}
/// Whether to rotate a projection to account for the 3DS screen orientation.
/// Both screens on the 3DS are oriented such that the "top-left" of the screen
/// in framebuffer coordinates is the physical bottom-left of the screen
/// (i.e. the "width" is smaller than the "height").
#[derive(Clone, Copy, Debug)]
pub enum ScreenOrientation {
/// Rotate 90° clockwise to account for the 3DS screen rotation. Most
/// applications will use this variant.
Rotated,
/// Do not apply any extra rotation to the projection.
None,
}
impl Default for ScreenOrientation {
fn default() -> Self {
Self::Rotated
}
}
/// Configuration for calculating stereoscopic projections.
// TODO: not totally happy with this name + API yet, but it works for now.
#[derive(Clone, Copy, Debug)]
pub struct StereoDisplacement {
/// The horizontal offset of the eye from center. Negative values
/// correspond to the left eye, and positive values to the right eye.
pub displacement: f32,
/// The position of the screen, which determines the focal length. Objects
/// closer than this depth will appear to pop out of the screen, and objects
/// further than this will appear inside the screen.
pub screen_depth: f32,
}
impl StereoDisplacement {
/// Construct displacement for the left and right eyes simulataneously.
/// The given `interocular_distance` describes the distance between the two
/// rendered "eyes". A negative value will be treated the same as a positive
/// value of the same magnitude.
///
/// See struct documentation for details about the
/// [`screen_depth`](Self::screen_depth) parameter.
pub fn new(interocular_distance: f32, screen_depth: f32) -> (Self, Self) {
let displacement = interocular_distance.abs() / 2.0;
let left_eye = Self {
displacement: -displacement,
screen_depth,
};
let right_eye = Self {
displacement,
screen_depth,
};
(left_eye, right_eye)
}
}
/// Configuration for the clipping planes of a projection.
///
/// For [`Perspective`] projections, this is used for the near and far clip planes
/// of the [view frustum](https://en.wikipedia.org/wiki/Viewing_frustum).
///
/// For [`Orthographic`] projections, this is used for the Z clipping planes of
/// the projection.
///
/// Note that the `near` value should always be less than `far`, regardless of
/// [`CoordinateOrientation`]. In other words, these values will be negated
/// when used with a [`RightHanded`](CoordinateOrientation::RightHanded)
/// orientation.
#[derive(Clone, Copy, Debug)]
pub struct ClipPlanes {
/// The Z-depth of the near clip plane, usually close or equal to zero.
pub near: f32,
/// The Z-depth of the far clip plane, usually greater than zero.
pub far: f32,
}
/// The aspect ratio of a projection plane.
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum AspectRatio {
/// The aspect ratio of the 3DS' top screen (per-eye).
#[doc(alias = "C3D_AspectRatioTop")]
TopScreen,
/// The aspect ratio of the 3DS' bottom screen.
#[doc(alias = "C3D_AspectRatioBot")]
BottomScreen,
/// A custom aspect ratio (should be calcualted as `width / height`).
Other(f32),
}
impl From<AspectRatio> for f32 {
fn from(ratio: AspectRatio) -> Self {
match ratio {
AspectRatio::TopScreen => citro3d_sys::C3D_AspectRatioTop as f32,
AspectRatio::BottomScreen => citro3d_sys::C3D_AspectRatioBot as f32,
AspectRatio::Other(ratio) => ratio,
}
}
}
// endregion

2
citro3d/src/math/fvec.rs

@ -14,7 +14,7 @@ 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.c }; let inner = unsafe { self.0.__bindgen_anon_1 };
f.debug_tuple(std::any::type_name::<Self>()) f.debug_tuple(std::any::type_name::<Self>())
.field(&inner) .field(&inner)
.finish() .finish()

202
citro3d/src/math/matrix.rs

@ -0,0 +1,202 @@
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
}
}
impl<const M: usize, const N: usize> fmt::Debug for Matrix<M, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let rows = unsafe { self.0.r }.map(|row| {
// UNWRAP: N ≤ 4, so slicing to a smaller array should always work
let mut row: [f32; N] = unsafe { row.c[..N].try_into() }.unwrap();
// Rows are stored in WZYX order which is opposite of how most people
// probably expect, so we reverse each row in-place as well.
row.reverse();
row
});
// UNWRAP: M ≤ 4, so slicing to a smaller array should always work
let inner: [_; M] = rows[..M].try_into().unwrap();
f.debug_tuple(std::any::type_name::<Self>())
.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())
}
}
}

5
citro3d/src/math/ops.rs

@ -166,7 +166,6 @@ impl<const M: usize, const N: usize> Eq for Matrix<M, N> {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::math::{Matrix3, Matrix4};
#[test] #[test]
fn vec3() { fn vec3() {
@ -198,7 +197,7 @@ mod tests {
let r = Matrix3::identity(); let r = Matrix3::identity();
let (l, r) = (&l, &r); let (l, r) = (&l, &r);
assert_eq!(l * r, r); assert_eq!(l * r, l);
assert_eq!(l + r, Matrix3::diagonal(2.0, 3.0, 4.0)); assert_eq!(l + r, Matrix3::diagonal(2.0, 3.0, 4.0));
assert_eq!(l - r, Matrix3::diagonal(0.0, 1.0, 2.0)); assert_eq!(l - r, Matrix3::diagonal(0.0, 1.0, 2.0));
} }
@ -209,7 +208,7 @@ mod tests {
let r = Matrix4::identity(); let r = Matrix4::identity();
let (l, r) = (&l, &r); let (l, r) = (&l, &r);
assert_eq!(l * r, r); assert_eq!(l * r, l);
assert_eq!(l + r, Matrix4::diagonal(2.0, 3.0, 4.0, 5.0)); assert_eq!(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_eq!(l - r, Matrix4::diagonal(0.0, 1.0, 2.0, 3.0));
} }

135
citro3d/src/math/projection.rs

@ -1,9 +1,7 @@
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::ops::Range; use std::ops::Range;
use super::{ use super::Matrix4;
AspectRatio, ClipPlanes, CoordinateOrientation, Matrix4, ScreenOrientation, StereoDisplacement,
};
/// Configuration for a 3D [projection](https://en.wikipedia.org/wiki/3D_projection). /// Configuration for a 3D [projection](https://en.wikipedia.org/wiki/3D_projection).
/// See specific `Kind` implementations for constructors, e.g. /// See specific `Kind` implementations for constructors, e.g.
@ -262,3 +260,134 @@ impl From<Projection<Orthographic>> for Matrix4 {
} }
} }
} }
// region: Projection configuration
/// The [orientation](https://en.wikipedia.org/wiki/Orientation_(geometry))
/// (or "handedness") of the coordinate system. Coordinates are always +Y-up,
/// +X-right.
#[derive(Clone, Copy, Debug)]
pub enum CoordinateOrientation {
/// A left-handed coordinate system. +Z points into the screen.
LeftHanded,
/// A right-handed coordinate system. +Z points out of the screen.
RightHanded,
}
impl CoordinateOrientation {
pub(crate) fn is_left_handed(self) -> bool {
matches!(self, Self::LeftHanded)
}
}
impl Default for CoordinateOrientation {
/// This is an opinionated default, but [`RightHanded`](Self::RightHanded)
/// seems to be the preferred coordinate system for most
/// [examples](https://github.com/devkitPro/3ds-examples)
/// from upstream, and is also fairly common in other applications.
fn default() -> Self {
Self::RightHanded
}
}
/// Whether to rotate a projection to account for the 3DS screen orientation.
/// Both screens on the 3DS are oriented such that the "top-left" of the screen
/// in framebuffer coordinates is the physical bottom-left of the screen
/// (i.e. the "width" is smaller than the "height").
#[derive(Clone, Copy, Debug)]
pub enum ScreenOrientation {
/// Rotate 90° clockwise to account for the 3DS screen rotation. Most
/// applications will use this variant.
Rotated,
/// Do not apply any extra rotation to the projection.
None,
}
impl Default for ScreenOrientation {
fn default() -> Self {
Self::Rotated
}
}
/// Configuration for calculating stereoscopic projections.
// TODO: not totally happy with this name + API yet, but it works for now.
#[derive(Clone, Copy, Debug)]
pub struct StereoDisplacement {
/// The horizontal offset of the eye from center. Negative values
/// correspond to the left eye, and positive values to the right eye.
pub displacement: f32,
/// The position of the screen, which determines the focal length. Objects
/// closer than this depth will appear to pop out of the screen, and objects
/// further than this will appear inside the screen.
pub screen_depth: f32,
}
impl StereoDisplacement {
/// Construct displacement for the left and right eyes simulataneously.
/// The given `interocular_distance` describes the distance between the two
/// rendered "eyes". A negative value will be treated the same as a positive
/// value of the same magnitude.
///
/// See struct documentation for details about the
/// [`screen_depth`](Self::screen_depth) parameter.
pub fn new(interocular_distance: f32, screen_depth: f32) -> (Self, Self) {
let displacement = interocular_distance.abs() / 2.0;
let left_eye = Self {
displacement: -displacement,
screen_depth,
};
let right_eye = Self {
displacement,
screen_depth,
};
(left_eye, right_eye)
}
}
/// Configuration for the clipping planes of a projection.
///
/// For [`Perspective`] projections, this is used for the near and far clip planes
/// of the [view frustum](https://en.wikipedia.org/wiki/Viewing_frustum).
///
/// For [`Orthographic`] projections, this is used for the Z clipping planes of
/// the projection.
///
/// Note that the `near` value should always be less than `far`, regardless of
/// [`CoordinateOrientation`]. In other words, these values will be negated
/// when used with a [`RightHanded`](CoordinateOrientation::RightHanded)
/// orientation.
#[derive(Clone, Copy, Debug)]
pub struct ClipPlanes {
/// The Z-depth of the near clip plane, usually close or equal to zero.
pub near: f32,
/// The Z-depth of the far clip plane, usually greater than zero.
pub far: f32,
}
/// The aspect ratio of a projection plane.
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum AspectRatio {
/// The aspect ratio of the 3DS' top screen (per-eye).
#[doc(alias = "C3D_AspectRatioTop")]
TopScreen,
/// The aspect ratio of the 3DS' bottom screen.
#[doc(alias = "C3D_AspectRatioBot")]
BottomScreen,
/// A custom aspect ratio (should be calcualted as `width / height`).
Other(f32),
}
impl From<AspectRatio> for f32 {
fn from(ratio: AspectRatio) -> Self {
match ratio {
AspectRatio::TopScreen => citro3d_sys::C3D_AspectRatioTop as f32,
AspectRatio::BottomScreen => citro3d_sys::C3D_AspectRatioBot as f32,
AspectRatio::Other(ratio) => ratio,
}
}
}
// endregion

Loading…
Cancel
Save