From a01a6cb0afb07e766fc9d8fdb9adb2a0fd2e307a Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Thu, 24 Oct 2024 23:07:05 -0400 Subject: [PATCH 1/4] Add planar projection --- src/projection.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/projection.rs b/src/projection.rs index a11c4399..4786c6c3 100644 --- a/src/projection.rs +++ b/src/projection.rs @@ -76,6 +76,21 @@ pub fn ortho(left: S, right: S, bottom: S, top: S, near: S, far: S .into() } +/// Create a planar projection matrix, which can be either perspective or orthographic. +/// +/// The projection frustum is always two units high one unit along the view direction, +/// making the focal point at `1.0 - cot(fovy / 2.0)`. Unlike a standard perspective +/// projection, this allows `fovy` to be zero or negative. +pub fn planar>>(fovy: A, aspect: S, near: S, far: S) -> Matrix4 { + PlanarFov { + fovy: fovy.into(), + aspect, + near, + far, + } + .into() +} + /// A perspective projection based on a vertical field-of-view angle. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] @@ -283,3 +298,82 @@ impl From> for Matrix4 { ) } } + +/// A planar projection based on a vertical field-of-view angle. +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PlanarFov { + pub fovy: Rad, + pub aspect: S, + pub near: S, + pub far: S, +} + +impl From> for Matrix4 { + fn from(persp: PlanarFov) -> Matrix4 { + assert!( + persp.fovy > -Rad::turn_div_2(), + "The vertical field of view cannot be less than a negative half turn, found: {:?}", + persp.fovy + ); + assert!( + persp.fovy < Rad::turn_div_2(), + "The vertical field of view cannot be greater than a half turn, found: {:?}", + persp.fovy + ); + + let two: S = cast(2).unwrap(); + let inv_f = Rad::tan(persp.fovy / two); + + let focal_point = S::one() - inv_f.recip(); + + assert!( + abs_diff_ne!(persp.aspect.abs(), S::zero()), + "The absolute aspect ratio cannot be zero, found: {:?}", + persp.aspect.abs() + ); + assert!( + abs_diff_ne!(persp.far, persp.near), + "The far plane and near plane are too close, found: far: {:?}, near: {:?}", + persp.far, + persp.near + ); + assert!( + focal_point < S::min(persp.far, persp.near) || focal_point > S::max(persp.far, persp.near), + "The focal point cannot be between the far and near planes, found: focal: {:?} far: {:?}, near: {:?}", + focal_point, + persp.far, + persp.near, + ); + + let c0r0 = S::one() / persp.aspect; + let c0r1 = S::zero(); + let c0r2 = S::zero(); + let c0r3 = S::zero(); + + let c1r0 = S::zero(); + let c1r1 = S::one(); + let c1r2 = S::zero(); + let c1r3 = S::zero(); + + let c2r0 = S::zero(); + let c2r1 = S::zero(); + let c2r2 = ((persp.far + persp.near - two) * inv_f + two) / (persp.near - persp.far); + let c2r3 = -inv_f; + + let c3r0 = S::zero(); + let c3r1 = S::zero(); + let c3r2 = (two * persp.far * persp.near * inv_f + + (S::one() - inv_f) * (persp.far + persp.near)) + / (persp.near - persp.far); + let c3r3 = S::one() - inv_f; + + #[cfg_attr(rustfmt, rustfmt_skip)] + Matrix4::new( + c0r0, c0r1, c0r2, c0r3, + c1r0, c1r1, c1r2, c1r3, + c2r0, c2r1, c2r2, c2r3, + c3r0, c3r1, c3r2, c3r3, + ) + } +} From 65aac0524e3bf5f3688c8f35ce2b994d8b489949 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Fri, 25 Oct 2024 14:22:45 -0400 Subject: [PATCH 2/4] Move fixed plane of planar projection to zero depth --- src/projection.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/projection.rs b/src/projection.rs index 4786c6c3..32476b30 100644 --- a/src/projection.rs +++ b/src/projection.rs @@ -77,9 +77,9 @@ pub fn ortho(left: S, right: S, bottom: S, top: S, near: S, far: S } /// Create a planar projection matrix, which can be either perspective or orthographic. -/// -/// The projection frustum is always two units high one unit along the view direction, -/// making the focal point at `1.0 - cot(fovy / 2.0)`. Unlike a standard perspective +/// +/// The projection frustum is always two units high at the origin along the view direction, +/// making the focal point located at `(0.0, 0.0, cot(fovy / 2.0))`. Unlike a standard perspective /// projection, this allows `fovy` to be zero or negative. pub fn planar>>(fovy: A, aspect: S, near: S, far: S) -> Matrix4 { PlanarFov { @@ -325,7 +325,7 @@ impl From> for Matrix4 { let two: S = cast(2).unwrap(); let inv_f = Rad::tan(persp.fovy / two); - let focal_point = S::one() - inv_f.recip(); + let focal_point = -inv_f.recip(); assert!( abs_diff_ne!(persp.aspect.abs(), S::zero()), @@ -340,7 +340,7 @@ impl From> for Matrix4 { ); assert!( focal_point < S::min(persp.far, persp.near) || focal_point > S::max(persp.far, persp.near), - "The focal point cannot be between the far and near planes, found: focal: {:?} far: {:?}, near: {:?}", + "The focal point cannot be between the far and near planes, found: focal: {:?}, far: {:?}, near: {:?}", focal_point, persp.far, persp.near, @@ -358,15 +358,14 @@ impl From> for Matrix4 { let c2r0 = S::zero(); let c2r1 = S::zero(); - let c2r2 = ((persp.far + persp.near - two) * inv_f + two) / (persp.near - persp.far); + let c2r2 = ((persp.far + persp.near) * inv_f + two) / (persp.near - persp.far); let c2r3 = -inv_f; let c3r0 = S::zero(); let c3r1 = S::zero(); - let c3r2 = (two * persp.far * persp.near * inv_f - + (S::one() - inv_f) * (persp.far + persp.near)) + let c3r2 = (two * persp.far * persp.near * inv_f + (persp.far + persp.near)) / (persp.near - persp.far); - let c3r3 = S::one() - inv_f; + let c3r3 = S::one(); #[cfg_attr(rustfmt, rustfmt_skip)] Matrix4::new( From 946dfc9a64266f93b47ece77bd58ade0b5acf4f7 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Fri, 25 Oct 2024 14:27:16 -0400 Subject: [PATCH 3/4] Add `PlanarFOV` to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d247a7f3..870ce665 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The library provides: - rotation matrices: `Basis2`, `Basis3` - angle units: `Rad`, `Deg` - points: `Point2`, `Point3` -- perspective projections: `Perspective`, `PerspectiveFov`, `Ortho` +- perspective projections: `Perspective`, `PerspectiveFov`, `Ortho`, `PlanarFov` - spatial transformations: `AffineMatrix3`, `Transform3` Not all of the functionality has been implemented yet, and the existing code From 8c29a7efb96a34a6c44b86ee3eddab4c140021b6 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 26 Oct 2024 14:26:10 -0400 Subject: [PATCH 4/4] Make planar projection height configurable --- src/projection.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/projection.rs b/src/projection.rs index 32476b30..5e8f67bd 100644 --- a/src/projection.rs +++ b/src/projection.rs @@ -78,13 +78,20 @@ pub fn ortho(left: S, right: S, bottom: S, top: S, near: S, far: S /// Create a planar projection matrix, which can be either perspective or orthographic. /// -/// The projection frustum is always two units high at the origin along the view direction, -/// making the focal point located at `(0.0, 0.0, cot(fovy / 2.0))`. Unlike a standard perspective -/// projection, this allows `fovy` to be zero or negative. -pub fn planar>>(fovy: A, aspect: S, near: S, far: S) -> Matrix4 { +/// The projection frustum is always `height` units high at the origin along the view direction, +/// making the focal point located at `(0.0, 0.0, cot(fovy / 2.0)) * height / 2.0`. Unlike +/// a standard perspective projection, this allows `fovy` to be zero or negative. +pub fn planar>>( + fovy: A, + aspect: S, + height: S, + near: S, + far: S, +) -> Matrix4 { PlanarFov { fovy: fovy.into(), aspect, + height, near, far, } @@ -305,6 +312,7 @@ impl From> for Matrix4 { pub struct PlanarFov { pub fovy: Rad, pub aspect: S, + pub height: S, pub near: S, pub far: S, } @@ -321,6 +329,11 @@ impl From> for Matrix4 { "The vertical field of view cannot be greater than a half turn, found: {:?}", persp.fovy ); + assert! { + persp.height >= S::zero(), + "The projection plane height cannot be negative, found: {:?}", + persp.height + } let two: S = cast(2).unwrap(); let inv_f = Rad::tan(persp.fovy / two); @@ -346,13 +359,13 @@ impl From> for Matrix4 { persp.near, ); - let c0r0 = S::one() / persp.aspect; + let c0r0 = two / (persp.aspect * persp.height); let c0r1 = S::zero(); let c0r2 = S::zero(); let c0r3 = S::zero(); let c1r0 = S::zero(); - let c1r1 = S::one(); + let c1r1 = two / persp.height; let c1r2 = S::zero(); let c1r3 = S::zero();