use crate::camera_math::CameraState;
const F_CLAMP: f32 = 32000.0;
#[derive(Debug, Clone, Copy)]
pub struct ProjectionRect {
pub cx: f32,
pub cy: f32,
pub wx0: f32,
pub wx1: f32,
pub wy0: f32,
pub wy1: f32,
pub iwx0: i32,
pub iwx1: i32,
pub iwy0: i32,
pub iwy1: i32,
pub fx: f32,
pub fy: f32,
pub gx: f32,
pub gy: f32,
pub x0: f32,
pub x1: f32,
pub x2: f32,
pub x3: f32,
pub y0: f32,
pub y1: f32,
pub y2: f32,
pub y3: f32,
}
#[allow(dead_code)]
#[must_use]
pub fn derive_projection(
camera_state: &CameraState,
xres: u32,
yres: u32,
hx: f32,
hy: f32,
hz: f32,
anginc: i32,
) -> ProjectionRect {
derive_projection_with_y_range(camera_state, xres, yres, 0, yres, hx, hy, hz, anginc)
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::float_cmp,
clippy::similar_names,
clippy::too_many_arguments,
clippy::too_many_lines
)]
#[must_use]
pub fn derive_projection_with_y_range(
camera_state: &CameraState,
xres: u32,
_yres: u32,
y_start: u32,
y_end: u32,
hx: f32,
hy: f32,
hz: f32,
anginc: i32,
) -> ProjectionRect {
let forward_z = camera_state.forward[2];
let f = if forward_z == 0.0 {
F_CLAMP
} else {
(hz / forward_z).clamp(-F_CLAMP, F_CLAMP)
};
let cx = camera_state.right[2] * f + hx;
let cy = camera_state.down[2] * f + hy;
let anginc_f = anginc as f32;
let wx0 = -anginc_f;
let wx1 = (xres as i32 - 1) as f32 + anginc_f;
let wy0 = (y_start as i32) as f32 - anginc_f;
let wy1 = (y_end as i32 - 1) as f32 + anginc_f;
let iwx0 = wx0.round_ties_even() as i32;
let iwx1 = wx1.round_ties_even() as i32;
let iwy0 = wy0.round_ties_even() as i32;
let iwy1 = wy1.round_ties_even() as i32;
let fx = wx0 - cx;
let fy = wy0 - cy;
let gx = wx1 - cx;
let gy = wy1 - cy;
let mut x0 = wx0;
let mut x3 = wx0;
let mut x1 = wx1;
let mut x2 = wx1;
let mut y0 = wy0;
let mut y1 = wy0;
let mut y2 = wy1;
let mut y3 = wy1;
if fy < 0.0 {
if fx < 0.0 {
let s = (fx * fy).sqrt();
x0 = cx - s;
y0 = cy - s;
}
if gx > 0.0 {
let s = (-gx * fy).sqrt();
x1 = cx + s;
y1 = cy - s;
}
}
if gy > 0.0 {
if gx > 0.0 {
let s = (gx * gy).sqrt();
x2 = cx + s;
y2 = cy + s;
}
if fx < 0.0 {
let s = (-fx * gy).sqrt();
x3 = cx - s;
y3 = cy + s;
}
}
if x0 > x1 {
if fx < 0.0 {
y0 = fx / gx * fy + cy;
} else {
y1 = gx / fx * fy + cy;
}
}
if y1 > y2 {
if fy < 0.0 {
x1 = fy / gy * gx + cx;
} else {
x2 = gy / fy * gx + cx;
}
}
if x2 < x3 {
if fx < 0.0 {
y3 = fx / gx * gy + cy;
} else {
y2 = gx / fx * gy + cy;
}
}
if y3 < y0 {
if fy < 0.0 {
x0 = fy / gy * fx + cx;
} else {
x3 = gy / fy * fx + cx;
}
}
x0 -= 0.01;
x1 += 0.01;
y1 -= 0.01;
y2 += 0.01;
x3 -= 0.01;
x2 += 0.01;
y0 -= 0.01;
y3 += 0.01;
ProjectionRect {
cx,
cy,
wx0,
wx1,
wy0,
wy1,
iwx0,
iwx1,
iwy0,
iwy1,
fx,
fy,
gx,
gy,
x0,
x1,
x2,
x3,
y0,
y1,
y2,
y3,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::camera_math;
use crate::Camera;
fn level_north_camera_state() -> CameraState {
let cam = Camera {
pos: [1024.0, 1024.0, 128.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, 1.0, 0.0],
};
camera_math::derive(&cam, 640, 480, 320.0, 240.0, 320.0)
}
fn looking_down_camera_state() -> CameraState {
let cam = Camera {
pos: [1024.0, 1024.0, 128.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 1.0, 0.0],
forward: [0.0, 0.0, 1.0],
};
camera_math::derive(&cam, 640, 480, 320.0, 240.0, 320.0)
}
fn bit(v: f32) -> u32 {
v.to_bits()
}
#[test]
fn level_camera_takes_forward_z_zero_branch() {
let s = level_north_camera_state();
let p = derive_projection(&s, 640, 480, 320.0, 240.0, 320.0, 1);
assert_eq!(bit(p.cx), bit(320.0));
assert_eq!(bit(p.cy), bit(32000.0 + 240.0));
}
#[test]
fn looking_down_camera_centre_at_viewport_centre() {
let s = looking_down_camera_state();
let p = derive_projection(&s, 640, 480, 320.0, 240.0, 320.0, 1);
assert_eq!(bit(p.cx), bit(320.0));
assert_eq!(bit(p.cy), bit(240.0));
}
#[test]
fn viewport_bounds_with_anginc() {
let s = looking_down_camera_state();
let p = derive_projection(&s, 640, 480, 320.0, 240.0, 320.0, 1);
assert_eq!(bit(p.wx0), bit(-1.0));
assert_eq!(bit(p.wx1), bit(640.0));
assert_eq!(bit(p.wy0), bit(-1.0));
assert_eq!(bit(p.wy1), bit(480.0));
assert_eq!(p.iwx0, -1);
assert_eq!(p.iwx1, 640);
assert_eq!(p.iwy0, -1);
assert_eq!(p.iwy1, 480);
let q = derive_projection(&s, 640, 480, 320.0, 240.0, 320.0, 4);
assert_eq!(bit(q.wx0), bit(-4.0));
assert_eq!(bit(q.wx1), bit(643.0));
assert_eq!(q.iwx0, -4);
assert_eq!(q.iwx1, 643);
}
#[test]
fn relative_corner_offsets_around_centre() {
let s = looking_down_camera_state();
let p = derive_projection(&s, 640, 480, 320.0, 240.0, 320.0, 1);
assert_eq!(bit(p.fx), bit(-1.0 - 320.0));
assert_eq!(bit(p.fy), bit(-1.0 - 240.0));
assert_eq!(bit(p.gx), bit(640.0 - 320.0));
assert_eq!(bit(p.gy), bit(480.0 - 240.0));
}
#[test]
#[allow(clippy::similar_names)]
fn corner_cut_quadrilateral_for_centre_inside_viewport() {
let s = looking_down_camera_state();
let p = derive_projection(&s, 640, 480, 320.0, 240.0, 320.0, 1);
let s_topleft = ((-321.0_f32) * (-241.0_f32)).sqrt();
assert_eq!(bit(p.x0), bit(320.0 - s_topleft - 0.01));
assert_eq!(bit(p.y0), bit(240.0 - s_topleft - 0.01));
let s_botright = (320.0_f32 * 240.0_f32).sqrt();
assert_eq!(bit(p.x2), bit(320.0 + s_botright + 0.01));
assert_eq!(bit(p.y2), bit(240.0 + s_botright + 0.01));
}
#[test]
fn forward_z_clamps_at_positive_extreme() {
let cam = Camera {
pos: [0.0, 0.0, 0.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, 0.99999, 0.001],
};
let s = camera_math::derive(&cam, 640, 480, 320.0, 240.0, 320.0);
let p = derive_projection(&s, 640, 480, 320.0, 240.0, 320.0, 1);
assert_eq!(bit(p.cx), bit(320.0));
assert_eq!(bit(p.cy), bit(32000.0 + 240.0));
}
}