From 974ee2b742385c3ac8193dd653cb01a1f4b1ea76 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Thu, 5 Oct 2023 21:19:49 -0400 Subject: [PATCH] Consolidate matrix construction API --- citro3d/examples/triangle.rs | 63 ++++++++------- citro3d/src/math.rs | 147 +++++++++++++++++++++++------------ 2 files changed, 132 insertions(+), 78 deletions(-) diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index e8ca2c2..61d4620 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -4,7 +4,7 @@ #![feature(allocator_api)] use citro3d::macros::include_shader; -use citro3d::math::{CoordinateSystem, Matrix}; +use citro3d::math::{ClipPlane, CoordinateSystem, Matrix, Orientation, Stereoscopic}; use citro3d::render::{self, ClearFlags}; use citro3d::{attrib, buffer, shader, AspectRatio}; use ctru::prelude::*; @@ -117,8 +117,8 @@ fn main() { }; let Projections { - left, - right, + left_eye: left, + right_eye: right, center, } = calculate_projections(); @@ -158,8 +158,8 @@ where } struct Projections { - left: Matrix, - right: Matrix, + left_eye: Matrix, + right_eye: Matrix, center: Matrix, } @@ -167,44 +167,51 @@ fn calculate_projections() -> Projections { // TODO: it would be cool to allow playing around with these parameters on // the fly with D-pad, etc. let slider_val = unsafe { ctru_sys::osGet3DSliderState() }; - let iod = slider_val / 4.0; + let interocular_distance = slider_val / 4.0; - let near = 0.01; - let far = 100.0; - let fov_y = 40.0_f32.to_radians(); - let screen = 2.0; + let vertical_fov = 40.0_f32.to_radians(); + let screen_depth = 2.0; - let left_eye = Matrix::perspective_stereo_tilt( - fov_y, + let clip_plane = ClipPlane { + near: 0.01, + far: 100.0, + }; + + let stereoscopic = Stereoscopic::Stereo { + interocular_distance, + screen_depth, + }; + + let left_eye = Matrix::perspective_projection( + vertical_fov, AspectRatio::TopScreen, - near, - far, - -iod, - screen, + Orientation::Natural, + clip_plane, + stereoscopic, CoordinateSystem::LeftHanded, ); - let right_eye = Matrix::perspective_stereo_tilt( - fov_y, + let right_eye = Matrix::perspective_projection( + vertical_fov, AspectRatio::TopScreen, - near, - far, - iod, - screen, + Orientation::Natural, + clip_plane, + stereoscopic.invert(), CoordinateSystem::LeftHanded, ); - let center = Matrix::perspective_tilt( - fov_y, + let center = Matrix::perspective_projection( + vertical_fov, AspectRatio::BottomScreen, - near, - far, + Orientation::Natural, + clip_plane, + Stereoscopic::Mono, CoordinateSystem::LeftHanded, ); Projections { - left: left_eye, - right: right_eye, + left_eye, + right_eye, center, } } diff --git a/citro3d/src/math.rs b/citro3d/src/math.rs index 2ce9dd6..734e4f2 100644 --- a/citro3d/src/math.rs +++ b/citro3d/src/math.rs @@ -16,67 +16,61 @@ pub struct Quaternion(citro3d_sys::C3D_FQuat); /// A 4x4 row-major matrix of `f32`s. pub struct Matrix(citro3d_sys::C3D_Mtx); -/// Whether to use left-handed or right-handed coordinates for calculations. -#[derive(Clone, Copy, Debug)] -#[non_exhaustive] // This probably is exhaustive, but just in case -pub enum CoordinateSystem { - LeftHanded, - RightHanded, -} - impl Matrix { - // TODO: this could probably be generalized with something like builder or options - // pattern. Should look and see what the different citro3d implementations look like - pub fn perspective_stereo_tilt( - fov_y: f32, + // TODO: does it make sense to have a helper that builds both left and right + // eyes for stereoscopic at the same time? + + /// Construct a projection matrix suitable for projecting 3D world space onto + /// the 3DS screens. + pub fn perspective_projection( + vertical_fov: f32, aspect_ratio: AspectRatio, - near: f32, - far: f32, - interocular_distance: f32, - /* better name ?? */ screen_depth: f32, + orientation: Orientation, + clip_plane: ClipPlane, + stereo: Stereoscopic, coordinates: CoordinateSystem, ) -> Self { - let mut result = MaybeUninit::uninit(); - - let inner = unsafe { - citro3d_sys::Mtx_PerspStereoTilt( - result.as_mut_ptr(), - fov_y, - aspect_ratio.into(), - near, - far, + let (make_mtx_persp, make_mtx_stereo); + + let initialize_mtx: &dyn Fn(_, _, _, _, _, _) -> _ = match stereo { + Stereoscopic::Mono => { + let make_mtx = match orientation { + Orientation::Natural => citro3d_sys::Mtx_PerspTilt, + Orientation::HardwareDefault => citro3d_sys::Mtx_Persp, + }; + + make_mtx_persp = move |a, b, c, d, e, f| unsafe { make_mtx(a, b, c, d, e, f) }; + &make_mtx_persp + } + Stereoscopic::Stereo { interocular_distance, screen_depth, - matches!(coordinates, CoordinateSystem::LeftHanded), - ); - - result.assume_init() + } => { + let make_mtx = match orientation { + Orientation::Natural => citro3d_sys::Mtx_PerspStereoTilt, + Orientation::HardwareDefault => citro3d_sys::Mtx_PerspStereo, + }; + + make_mtx_stereo = move |a, b, c, d, e, f| unsafe { + make_mtx(a, b, c, d, interocular_distance, screen_depth, e, f) + }; + &make_mtx_stereo + } }; - Self(inner) - } + let left_handed = matches!(coordinates, CoordinateSystem::LeftHanded); - pub fn perspective_tilt( - fov_y: f32, - aspect_ratio: AspectRatio, - near: f32, - far: f32, - coordinates: CoordinateSystem, - ) -> Self { let mut result = MaybeUninit::uninit(); + initialize_mtx( + result.as_mut_ptr(), + vertical_fov, + aspect_ratio.into(), + clip_plane.near, + clip_plane.far, + left_handed, + ); - let inner = unsafe { - citro3d_sys::Mtx_PerspTilt( - result.as_mut_ptr(), - fov_y, - aspect_ratio.into(), - near, - far, - matches!(coordinates, CoordinateSystem::LeftHanded), - ); - - result.assume_init() - }; + let inner = unsafe { result.assume_init() }; Self(inner) } @@ -85,3 +79,56 @@ impl Matrix { &self.0 } } + +/// Whether to use left-handed or right-handed coordinates for calculations. +#[derive(Clone, Copy, Debug)] +pub enum CoordinateSystem { + LeftHanded, + RightHanded, +} + +/// Whether to rotate a projection to account for the 3DS screen configuration. +/// Both screens on the 3DS are oriented such that the "top" of the screen is +/// on the [left | right] ? side of the device when it's held normally, so +/// projections must account for this extra rotation to display in the correct +/// orientation. +#[derive(Clone, Copy, Debug)] +pub enum Orientation { + /// Rotate the projection 90° to account for the 3DS screen rotation. + Natural, + /// Don't rotate the projection at all. + HardwareDefault, +} + +#[derive(Clone, Copy, Debug)] +// TODO: better name +pub enum Stereoscopic { + Mono, + Stereo { + interocular_distance: f32, + // TODO: better name? At least docstring + screen_depth: f32, + }, +} + +impl Stereoscopic { + /// Flip the stereoscopic projection for the opposite eye. + pub fn invert(self) -> Self { + match self { + Self::Stereo { + interocular_distance, + screen_depth, + } => Self::Stereo { + interocular_distance: -interocular_distance, + screen_depth, + }, + mono => mono, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct ClipPlane { + pub near: f32, + pub far: f32, +}