diff --git a/citro3d/src/math.rs b/citro3d/src/math.rs index 64fa613..949bb42 100644 --- a/citro3d/src/math.rs +++ b/citro3d/src/math.rs @@ -3,15 +3,17 @@ // 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; - -mod projection; - -pub use projection::{Orthographic, Perspective, Projection}; mod fvec; +mod matrix; mod ops; +mod projection; 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. #[doc(alias = "C3D_IVec")] @@ -20,322 +22,3 @@ pub struct IVec(citro3d_sys::C3D_IVec); /// A quaternion, internally represented the same way as [`FVec`]. #[doc(alias = "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(citro3d_sys::C3D_Mtx); - - impl fmt::Debug for Matrix { - 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::()) - .field(&inner) - .finish() - } - } - - impl Matrix { - 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 Matrix { - /// 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 { - 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 Matrix { - /// 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 { - 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 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 diff --git a/citro3d/src/math/fvec.rs b/citro3d/src/math/fvec.rs index 76d1c2d..5b2df95 100644 --- a/citro3d/src/math/fvec.rs +++ b/citro3d/src/math/fvec.rs @@ -14,7 +14,7 @@ pub type FVec4 = FVec<4>; impl fmt::Debug for FVec { 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::()) .field(&inner) .finish() diff --git a/citro3d/src/math/matrix.rs b/citro3d/src/math/matrix.rs new file mode 100644 index 0000000..f863784 --- /dev/null +++ b/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(citro3d_sys::C3D_Mtx); + + impl Matrix { + 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 fmt::Debug for Matrix { + 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::()) + .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 Matrix { + /// 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 { + 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 Matrix { + /// 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 { + 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()) + } + } +} diff --git a/citro3d/src/math/ops.rs b/citro3d/src/math/ops.rs index 0cb51a4..e8c648a 100644 --- a/citro3d/src/math/ops.rs +++ b/citro3d/src/math/ops.rs @@ -166,7 +166,6 @@ impl Eq for Matrix {} #[cfg(test)] mod tests { use super::*; - use crate::math::{Matrix3, Matrix4}; #[test] fn vec3() { @@ -198,7 +197,7 @@ mod tests { let r = Matrix3::identity(); 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(0.0, 1.0, 2.0)); } @@ -209,7 +208,7 @@ mod tests { let r = Matrix4::identity(); 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(0.0, 1.0, 2.0, 3.0)); } diff --git a/citro3d/src/math/projection.rs b/citro3d/src/math/projection.rs index 44bc6e1..278a776 100644 --- a/citro3d/src/math/projection.rs +++ b/citro3d/src/math/projection.rs @@ -1,9 +1,7 @@ use std::mem::MaybeUninit; use std::ops::Range; -use super::{ - AspectRatio, ClipPlanes, CoordinateOrientation, Matrix4, ScreenOrientation, StereoDisplacement, -}; +use super::Matrix4; /// Configuration for a 3D [projection](https://en.wikipedia.org/wiki/3D_projection). /// See specific `Kind` implementations for constructors, e.g. @@ -262,3 +260,134 @@ impl From> 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 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