Skip to content

Commit

Permalink
Redo zoom factor implementation + revert adding Planar camera type (#44)
Browse files Browse the repository at this point in the history
* Revert "Planar projection camera (#42)"

This reverts commit 2d34e6d.

* Revert scale camera zoom based on distance to target

* Minor clean-up

* Scale orthographic projection based on distance to target

* Zoom factor functions

* Doc

* Update Orthographic projection when position/target changes
  • Loading branch information
asny authored Nov 27, 2024
1 parent 093f66b commit 6316f4a
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 184 deletions.
115 changes: 38 additions & 77 deletions src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,6 @@ pub enum ProjectionType {
/// The field of view angle in the vertical direction.
field_of_view_y: Radians,
},
/// General planar projection
Planar {
/// The field of view angle in the vertical direction.
field_of_view_y: Radians,
},
}

///
Expand Down Expand Up @@ -222,24 +217,6 @@ impl Camera {
camera
}

///
/// New camera which projects the world with a general planar projection.
///
pub fn new_planar(
viewport: Viewport,
position: Vec3,
target: Vec3,
up: Vec3,
field_of_view_y: impl Into<Radians>,
z_near: f32,
z_far: f32,
) -> Self {
let mut camera = Camera::new(viewport);
camera.set_view(position, target, up);
camera.set_planar_projection(field_of_view_y, z_near, z_far);
camera
}

///
/// Specify the camera to use perspective projection with the given field of view in the y-direction and near and far plane.
///
Expand All @@ -249,10 +226,6 @@ impl Camera {
z_near: f32,
z_far: f32,
) {
assert!(
z_near >= 0.0 || z_near < z_far,
"Wrong perspective camera parameters"
);
self.z_near = z_near;
self.z_far = z_far;
let field_of_view_y = field_of_view_y.into();
Expand All @@ -264,50 +237,31 @@ impl Camera {
}

///
/// Specify the camera to use orthographic projection with the given height and depth.
/// Specify the camera to use orthographic projection with the given dimensions.
/// The view frustum height is `+/- height/2`.
/// The view frustum width is calculated as `height * viewport.width / viewport.height`.
/// The view frustum depth is `z_near` to `z_far`.
/// All of the above values are scaled by the zoom factor which is one over the distance between the camera position and target.
///
pub fn set_orthographic_projection(&mut self, height: f32, z_near: f32, z_far: f32) {
assert!(z_near < z_far, "Wrong orthographic camera parameters");
self.projection_type = ProjectionType::Orthographic { height };
self.z_near = z_near;
self.z_far = z_far;
let zoom = self.position.distance(self.target);
let height = zoom * height;
let width = height * self.viewport.aspect();
self.projection_type = ProjectionType::Orthographic { height };
self.projection = cgmath::ortho(
-0.5 * width,
0.5 * width,
-0.5 * height,
0.5 * height,
z_near,
z_far,
zoom * z_near,
zoom * z_far,
);
self.update_screen2ray();
self.update_frustrum();
}

///
/// Specify the camera to use planar projection with the given field of view in the y-direction and near and far plane.
/// This can be either a planar or perspective projection depending on the field of view provided, which is permitted to be zero or negative.
///
pub fn set_planar_projection(
&mut self,
field_of_view_y: impl Into<Radians>,
z_near: f32,
z_far: f32,
) {
assert!(z_near < z_far, "Wrong perspective camera parameters");
self.z_near = z_near;
self.z_far = z_far;
let field_of_view_y = field_of_view_y.into();
self.projection_type = ProjectionType::Planar { field_of_view_y };
self.projection = planar(field_of_view_y, self.viewport.aspect(), 2.0, z_near, z_far)
* Mat4::from_translation(vec3(0.0, 0.0, 1.0));
self.update_screen2ray();
self.update_frustrum();
}

///
/// Set the current viewport.
/// Returns whether or not the viewport actually changed.
Expand All @@ -322,9 +276,6 @@ impl Camera {
ProjectionType::Perspective { field_of_view_y } => {
self.set_perspective_projection(field_of_view_y, self.z_near, self.z_far);
}
ProjectionType::Planar { field_of_view_y } => {
self.set_planar_projection(field_of_view_y, self.z_near, self.z_far);
}
}
true
} else {
Expand All @@ -345,7 +296,9 @@ impl Camera {
Point3::from_vec(self.target),
self.up,
);
self.view[3][3] *= position.distance(target);
if let ProjectionType::Orthographic { height } = self.projection_type {
self.set_orthographic_projection(height, self.z_near, self.z_far);
}
self.update_screen2ray();
self.update_frustrum();
}
Expand Down Expand Up @@ -410,7 +363,7 @@ impl Camera {
///
pub fn position_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
match self.projection_type() {
ProjectionType::Orthographic { .. } | ProjectionType::Planar { .. } => {
ProjectionType::Orthographic { .. } => {
let coords = self.uv_coordinates_at_pixel(pixel);
self.position_at_uv_coordinates(coords)
}
Expand All @@ -423,10 +376,10 @@ impl Camera {
///
pub fn position_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
match self.projection_type() {
ProjectionType::Orthographic { .. } | ProjectionType::Planar { .. } => {
ProjectionType::Orthographic { .. } => {
let coords = coords.into();
let screen_pos = Point3::new(2. * coords.u - 1., 2. * coords.v - 1.0, -1.0);
self.screen2ray.transform_point(screen_pos).to_vec()
let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, -1.0, 1.);
(self.screen2ray * screen_pos).truncate()
}
ProjectionType::Perspective { .. } => self.position,
}
Expand All @@ -438,7 +391,7 @@ impl Camera {
pub fn view_direction_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
match self.projection_type() {
ProjectionType::Orthographic { .. } => self.view_direction(),
ProjectionType::Perspective { .. } | ProjectionType::Planar { .. } => {
ProjectionType::Perspective { .. } => {
let coords = self.uv_coordinates_at_pixel(pixel);
self.view_direction_at_uv_coordinates(coords)
}
Expand All @@ -456,14 +409,6 @@ impl Camera {
let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0., 1.);
(self.screen2ray * screen_pos).truncate().normalize()
}
ProjectionType::Planar { .. } => {
let coords = coords.into();
let start_pos = Point3::new(2. * coords.u - 1., 2. * coords.v - 1.0, -0.5);
let end_pos = Point3::new(2. * coords.u - 1., 2. * coords.v - 1.0, 0.5);
(self.screen2ray.transform_point(end_pos)
- self.screen2ray.transform_point(start_pos))
.normalize()
}
}
}

Expand Down Expand Up @@ -613,7 +558,6 @@ impl Camera {

fn update_screen2ray(&mut self) {
let mut v = self.view;
v /= v[3][3];
if let ProjectionType::Perspective { .. } = self.projection_type {
v[3] = vec4(0.0, 0.0, 0.0, 1.0);
}
Expand Down Expand Up @@ -751,14 +695,10 @@ impl Camera {
minimum_distance: f32,
maximum_distance: f32,
) {
let minimum_distance = minimum_distance.max(0.0);
assert!(
minimum_distance < maximum_distance,
"minimum_distance larger than maximum_distance"
);

let distance = point.distance(self.position);
if distance > f32::EPSILON {
let minimum_distance = minimum_distance.max(std::f32::EPSILON);
let maximum_distance = maximum_distance.max(minimum_distance);
let delta_clamped =
distance - (distance - delta).clamp(minimum_distance, maximum_distance);
let v = (point - self.position) * delta_clamped / distance;
Expand All @@ -769,4 +709,25 @@ impl Camera {
);
}
}

///
/// Sets the zoom factor of this camera, ie. the distance to the camera will be `1/zoom_factor`.
///
pub fn set_zoom_factor(&mut self, zoom_factor: f32) {
let zoom_factor = zoom_factor.max(std::f32::EPSILON);
let position = self.target + (self.position - self.target).normalize() / zoom_factor;
self.set_view(position, self.target, self.up);
}

///
/// The zoom factor for this camera, which is the same as one over the distance between the camera position and target.
///
pub fn zoom_factor(&self) -> f32 {
let distance = self.target.distance(self.position);
if distance > f32::EPSILON {
1.0 / distance
} else {
0.0
}
}
}
107 changes: 0 additions & 107 deletions src/prelude/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,110 +78,3 @@ pub fn rotation_matrix_from_dir_to_dir(source_dir: Vec3, target_dir: Vec3) -> Ma
source_dir, target_dir,
)))
}

// NOTE: Can be removed once https://github.com/rustgd/cgmath/pull/556 has been released.

/// Create a planar projection matrix, which can be either perspective or orthographic.
///
/// 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<S: cgmath::BaseFloat, A: Into<Rad<S>>>(
fovy: A,
aspect: S,
height: S,
near: S,
far: S,
) -> Matrix4<S> {
PlanarFov {
fovy: fovy.into(),
aspect,
height,
near,
far,
}
.into()
}

/// A planar projection based on a vertical field-of-view angle.
#[derive(Copy, Clone, Debug, PartialEq)]
struct PlanarFov<S> {
pub fovy: Rad<S>,
pub aspect: S,
pub height: S,
pub near: S,
pub far: S,
}

impl<S: cgmath::BaseFloat> From<PlanarFov<S>> for Matrix4<S> {
fn from(persp: PlanarFov<S>) -> Matrix4<S> {
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
);
assert! {
persp.height >= S::zero(),
"The projection plane height cannot be negative, found: {:?}",
persp.height
}

let two: S = cgmath::num_traits::cast(2).unwrap();
let inv_f = Rad::tan(persp.fovy / two);

let focal_point = -inv_f.recip();

assert!(
cgmath::abs_diff_ne!(persp.aspect.abs(), S::zero()),
"The absolute aspect ratio cannot be zero, found: {:?}",
persp.aspect.abs()
);
assert!(
cgmath::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 = two / (persp.aspect * persp.height);
let c0r1 = S::zero();
let c0r2 = S::zero();
let c0r3 = S::zero();

let c1r0 = S::zero();
let c1r1 = two / persp.height;
let c1r2 = S::zero();
let c1r3 = S::zero();

let c2r0 = S::zero();
let c2r1 = S::zero();
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 + (persp.far + persp.near))
/ (persp.near - persp.far);
let c3r3 = S::one();

#[cfg_attr(rustfmt, rustfmt_skip)]
Matrix4::new(
c0r0, c0r1, c0r2, c0r3,
c1r0, c1r1, c1r2, c1r3,
c2r0, c2r1, c2r2, c2r3,
c3r0, c3r1, c3r2, c3r3,
)
}
}

0 comments on commit 6316f4a

Please sign in to comment.