diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index b367906..f8585e4 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::{AspectRatio, ClipPlanes, Matrix, Projection, StereoDisplacement}; +use citro3d::math::{AspectRatio, ClipPlanes, Matrix4, Projection, StereoDisplacement}; use citro3d::render::ClearFlags; use citro3d::{attrib, buffer, render, shader}; use ctru::prelude::*; @@ -158,9 +158,9 @@ where } struct Projections { - left_eye: Matrix, - right_eye: Matrix, - center: Matrix, + left_eye: Matrix4, + right_eye: Matrix4, + center: Matrix4, } fn calculate_projections() -> Projections { diff --git a/citro3d/src/math.rs b/citro3d/src/math.rs index 22634ab..976ddc7 100644 --- a/citro3d/src/math.rs +++ b/citro3d/src/math.rs @@ -21,11 +21,40 @@ pub struct IVec(citro3d_sys::C3D_IVec); #[doc(alias = "C3D_FQuat")] pub struct FQuat(citro3d_sys::C3D_FQuat); +mod mtx { + /// An `M`x`N` row-major matrix of `f32`s. + #[doc(alias = "C3D_Mtx")] + 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 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. -#[doc(alias = "C3D_Mtx")] -pub struct Matrix(citro3d_sys::C3D_Mtx); +pub type Matrix4 = Matrix<4, 4>; -impl Matrix { +impl Matrix { /// Construct the zero matrix. #[doc(alias = "Mtx_Zeros")] pub fn zero() -> Self { @@ -33,23 +62,21 @@ impl Matrix { let mut out = MaybeUninit::uninit(); unsafe { citro3d_sys::Mtx_Zeros(out.as_mut_ptr()); - Self(out.assume_init()) + Self::new(out.assume_init()) } } +} +impl Matrix { /// 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(out.assume_init()) + Self::new(out.assume_init()) } } - - pub(crate) fn as_raw(&self) -> *const citro3d_sys::C3D_Mtx { - &self.0 - } } // region: Projection configuration diff --git a/citro3d/src/math/projection.rs b/citro3d/src/math/projection.rs index 6833fb9..44bc6e1 100644 --- a/citro3d/src/math/projection.rs +++ b/citro3d/src/math/projection.rs @@ -2,14 +2,14 @@ use std::mem::MaybeUninit; use std::ops::Range; use super::{ - AspectRatio, ClipPlanes, CoordinateOrientation, Matrix, ScreenOrientation, StereoDisplacement, + AspectRatio, ClipPlanes, CoordinateOrientation, Matrix4, ScreenOrientation, StereoDisplacement, }; /// Configuration for a 3D [projection](https://en.wikipedia.org/wiki/3D_projection). /// See specific `Kind` implementations for constructors, e.g. /// [`Projection::perspective`] and [`Projection::orthographic`]. /// -/// To use the resulting projection, convert it to a [`Matrix`] with [`From`]/[`Into`]. +/// To use the resulting projection, convert it to a [`Matrix`](super::Matrix) with [`From`]/[`Into`]. #[derive(Clone, Debug)] pub struct Projection { coordinates: CoordinateOrientation, @@ -122,7 +122,7 @@ impl Projection { self, left_eye: StereoDisplacement, right_eye: StereoDisplacement, - ) -> (Matrix, Matrix) { + ) -> (Matrix4, Matrix4) { // TODO: we might be able to avoid this clone if there was a conversion // from &Self to Matrix instead of Self... but it's probably fine for now let left = self.clone().stereo(left_eye); @@ -137,7 +137,7 @@ impl Projection { } } -impl From> for Matrix { +impl From> for Matrix4 { fn from(projection: Projection) -> Self { let Perspective { vertical_fov_radians, @@ -182,7 +182,7 @@ impl From> for Matrix { } } - unsafe { Self(result.assume_init()) } + unsafe { Self::new(result.assume_init()) } } } @@ -233,7 +233,7 @@ impl Projection { } } -impl From> for Matrix { +impl From> for Matrix4 { fn from(projection: Projection) -> Self { let make_mtx = match projection.rotation { ScreenOrientation::Rotated => citro3d_sys::Mtx_OrthoTilt, @@ -258,7 +258,7 @@ impl From> for Matrix { clip_planes_z.far, projection.coordinates.is_left_handed(), ); - Self(out.assume_init()) + Self::new(out.assume_init()) } } } diff --git a/citro3d/src/uniform.rs b/citro3d/src/uniform.rs index c6a1d75..22c7092 100644 --- a/citro3d/src/uniform.rs +++ b/citro3d/src/uniform.rs @@ -24,7 +24,8 @@ mod private { use crate::math::Matrix; pub trait Sealed {} - impl Sealed for &Matrix {} + + impl Sealed for &Matrix {} } /// A shader uniform. This trait is implemented for types that can be bound to @@ -36,9 +37,21 @@ pub trait Uniform: private::Sealed { fn bind(self, instance: &mut Instance, shader_type: shader::Type, index: Index); } -impl Uniform for &Matrix { - #[doc(alias = "C3D_FVUnifMtx4x4")] +impl Uniform for &Matrix { + #[doc(alias = "C34_FVUnifMtxNx4")] + #[doc(alias = "C34_FVUnifMtx4x4")] + #[doc(alias = "C34_FVUnifMtx3x4")] + #[doc(alias = "C34_FVUnifMtx2x4")] fn bind(self, _instance: &mut Instance, type_: shader::Type, index: Index) { - unsafe { citro3d_sys::C3D_FVUnifMtx4x4(type_.into(), index.into(), self.as_raw()) } + unsafe { + citro3d_sys::C3D_FVUnifMtxNx4( + type_.into(), + index.into(), + self.as_raw(), + // UNWRAP: it should be impossible for end users to construct + // a matrix with M > i32::MAX + M.try_into().unwrap(), + ); + } } }