use crate::builder; use crate::data_types::dimens::Dimens; use crate::matrix::{CellPos as MatrixCellPos, Matrix}; use crate::reflection::Reflection; use crate::vector::Vec2; #[derive(Debug, Clone, Reflection)] #[non_exhaustive] pub enum Projection { Perspective(Perspective), Orthographic(Orthographic), } /// Perspective projection parameters. #[derive(Debug, Clone, Reflection)] pub struct Perspective { pub fov_radians: f32, pub far: f32, pub near: f32, } impl Perspective { /// Creates a perspective projection matrix using right-handed coordinates. #[inline] pub fn to_matrix_rh(&self, aspect: f32, clip_volume: ClipVolume) -> Matrix { let mut out = Matrix::new(); match clip_volume { ClipVolume::NegOneToOne => { out[MatrixCellPos { row: 0, col: 0 }] = (1.0 / (self.fov_radians / 2.0).tan()) / aspect; out[MatrixCellPos { row: 1, col: 1 }] = 1.0 / (self.fov_radians / 2.0).tan(); out[MatrixCellPos { row: 2, col: 2 }] = (self.near + self.far) / (self.near - self.far); out[MatrixCellPos { row: 2, col: 3 }] = (2.0 * self.near * self.far) / (self.near - self.far); out[MatrixCellPos { row: 3, col: 2 }] = -1.0; } } out } } impl Default for Perspective { fn default() -> Self { Self { fov_radians: 80.0f32.to_radians(), far: 100.0, near: 0.1, } } } #[derive(Debug, Default, Clone, Reflection)] pub enum OrthographicSize { FixedSize(Dimens), #[default] WindowSize, } builder! { #[builder(name = OrthographicBuilder, derives=(Debug, Clone))] #[derive(Debug, Clone, Reflection)] #[non_exhaustive] pub struct Orthographic { pub near: f32, pub far: f32, pub viewport_origin: Vec2, pub size: OrthographicSize, } } impl Orthographic { pub fn builder() -> OrthographicBuilder { OrthographicBuilder::default() } /// Creates a orthographic projection matrix using right-handed coordinates. pub fn to_matrix_rh( &self, window_size: Dimens, clip_volume: ClipVolume, ) -> Matrix { let mut result = Matrix::::new(); let size = match self.size { OrthographicSize::FixedSize(fixed_size) => fixed_size, OrthographicSize::WindowSize => window_size, }; let origin_x = size.width * self.viewport_origin.x; let origin_y = size.height * self.viewport_origin.y; let left = -origin_x; let right = size.width - origin_x; let bottom = -origin_y; let top = size.height - origin_y; let near = self.near; let far = self.far; match clip_volume { ClipVolume::NegOneToOne => { result[MatrixCellPos { row: 0, col: 0 }] = 2.0 / (right - left); result[MatrixCellPos { row: 1, col: 1 }] = 2.0 / (top - bottom); result[MatrixCellPos { row: 2, col: 2 }] = -2.0 / (far - near); result[MatrixCellPos { row: 0, col: 3 }] = -(right + left) / (right - left); result[MatrixCellPos { row: 1, col: 3 }] = -(top + bottom) / (top - bottom); result[MatrixCellPos { row: 2, col: 3 }] = -(far + near) / (far - near); result[MatrixCellPos { row: 3, col: 3 }] = 1.0; } } result } } impl Default for Orthographic { fn default() -> Self { Self::builder().build() } } impl Default for OrthographicBuilder { fn default() -> Self { Self { near: 0.0, far: 1000.0, viewport_origin: Vec2 { x: 0.5, y: 0.5 }, size: OrthographicSize::WindowSize, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] pub enum ClipVolume { /// -1 to +1. This is the OpenGL clip volume definition. NegOneToOne, }