|
|
@ -16,67 +16,61 @@ pub struct Quaternion(citro3d_sys::C3D_FQuat); |
|
|
|
/// A 4x4 row-major matrix of `f32`s.
|
|
|
|
/// A 4x4 row-major matrix of `f32`s.
|
|
|
|
pub struct Matrix(citro3d_sys::C3D_Mtx); |
|
|
|
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 { |
|
|
|
impl Matrix { |
|
|
|
// TODO: this could probably be generalized with something like builder or options
|
|
|
|
// TODO: does it make sense to have a helper that builds both left and right
|
|
|
|
// pattern. Should look and see what the different citro3d implementations look like
|
|
|
|
// eyes for stereoscopic at the same time?
|
|
|
|
pub fn perspective_stereo_tilt( |
|
|
|
|
|
|
|
fov_y: f32, |
|
|
|
/// Construct a projection matrix suitable for projecting 3D world space onto
|
|
|
|
|
|
|
|
/// the 3DS screens.
|
|
|
|
|
|
|
|
pub fn perspective_projection( |
|
|
|
|
|
|
|
vertical_fov: f32, |
|
|
|
aspect_ratio: AspectRatio, |
|
|
|
aspect_ratio: AspectRatio, |
|
|
|
near: f32, |
|
|
|
orientation: Orientation, |
|
|
|
far: f32, |
|
|
|
clip_plane: ClipPlane, |
|
|
|
interocular_distance: f32, |
|
|
|
stereo: Stereoscopic, |
|
|
|
/* better name ?? */ screen_depth: f32, |
|
|
|
|
|
|
|
coordinates: CoordinateSystem, |
|
|
|
coordinates: CoordinateSystem, |
|
|
|
) -> Self { |
|
|
|
) -> Self { |
|
|
|
let mut result = MaybeUninit::uninit(); |
|
|
|
let (make_mtx_persp, make_mtx_stereo); |
|
|
|
|
|
|
|
|
|
|
|
let inner = unsafe { |
|
|
|
let initialize_mtx: &dyn Fn(_, _, _, _, _, _) -> _ = match stereo { |
|
|
|
citro3d_sys::Mtx_PerspStereoTilt( |
|
|
|
Stereoscopic::Mono => { |
|
|
|
result.as_mut_ptr(), |
|
|
|
let make_mtx = match orientation { |
|
|
|
fov_y, |
|
|
|
Orientation::Natural => citro3d_sys::Mtx_PerspTilt, |
|
|
|
aspect_ratio.into(), |
|
|
|
Orientation::HardwareDefault => citro3d_sys::Mtx_Persp, |
|
|
|
near, |
|
|
|
}; |
|
|
|
far, |
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
|
interocular_distance, |
|
|
|
screen_depth, |
|
|
|
screen_depth, |
|
|
|
matches!(coordinates, CoordinateSystem::LeftHanded), |
|
|
|
} => { |
|
|
|
); |
|
|
|
let make_mtx = match orientation { |
|
|
|
|
|
|
|
Orientation::Natural => citro3d_sys::Mtx_PerspStereoTilt, |
|
|
|
result.assume_init() |
|
|
|
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(); |
|
|
|
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 { |
|
|
|
let inner = unsafe { result.assume_init() }; |
|
|
|
citro3d_sys::Mtx_PerspTilt( |
|
|
|
|
|
|
|
result.as_mut_ptr(), |
|
|
|
|
|
|
|
fov_y, |
|
|
|
|
|
|
|
aspect_ratio.into(), |
|
|
|
|
|
|
|
near, |
|
|
|
|
|
|
|
far, |
|
|
|
|
|
|
|
matches!(coordinates, CoordinateSystem::LeftHanded), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result.assume_init() |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Self(inner) |
|
|
|
Self(inner) |
|
|
|
} |
|
|
|
} |
|
|
@ -85,3 +79,56 @@ impl Matrix { |
|
|
|
&self.0 |
|
|
|
&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, |
|
|
|
|
|
|
|
} |
|
|
|