use crate::camera_math::CameraState;
use crate::fixed::ftol;
use crate::opticast_prelude::OpticastPrelude;
use crate::opticast_prelude::PREC;
#[derive(Debug, Clone, Copy)]
pub struct GlineFrustum {
pub vd0: f32,
pub vd1: f32,
pub vz0: f32,
pub vx1: f32,
pub vy1: f32,
pub vz1: f32,
pub gixy: [i32; 2],
pub gpz: [i32; 2],
pub gdz: [i32; 2],
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::too_many_arguments
)]
#[must_use]
pub fn derive_gline_frustum(
cs: &CameraState,
prelude: &OpticastPrelude,
vsid: u32,
_leng: u32,
x0: f32,
y0: f32,
x1: f32,
y1: f32,
) -> GlineFrustum {
let vd0_x = x0 * cs.right[0] + y0 * cs.down[0] + cs.corn[0][0];
let vd0_y = x0 * cs.right[1] + y0 * cs.down[1] + cs.corn[0][1];
let vz0 = x0 * cs.right[2] + y0 * cs.down[2] + cs.corn[0][2];
let vx1 = x1 * cs.right[0] + y1 * cs.down[0] + cs.corn[0][0];
let vy1 = x1 * cs.right[1] + y1 * cs.down[1] + cs.corn[0][1];
let vz1 = x1 * cs.right[2] + y1 * cs.down[2] + cs.corn[0][2];
let f = (vx1 * vx1 + vy1 * vy1).sqrt();
let f1 = f / vx1;
let f2 = f / vy1;
let mut vd0 = if vx1.abs() > vy1.abs() {
vd0_x * f1
} else {
vd0_y * f2
};
if vd0.is_sign_negative() {
vd0 = 0.0;
}
let vd1 = f;
let f1_abs = f1.abs();
let gdz_0 = if f1_abs.is_finite() {
ftol(f1_abs * PREC as f32)
} else {
-1
};
let f2_abs = f2.abs();
let gdz_1 = if f2_abs.is_finite() {
ftol(f2_abs * PREC as f32)
} else {
-1
};
let vsid_signed = vsid as i32;
let gixy_0 = if vx1.is_sign_negative() { -1 } else { 1 };
let gixy_1 = if vy1.is_sign_negative() {
-vsid_signed
} else {
vsid_signed
};
let xfrac_idx = (vx1.to_bits() >> 31) as usize;
let yfrac_idx = (vy1.to_bits() >> 31) as usize;
let (gdz_clamped_0, gpz_0) = if gdz_0 <= 0 {
(0, i32::MAX)
} else {
let gp = ftol(prelude.pos_xfrac[xfrac_idx] * gdz_0 as f32);
(gdz_0, gp)
};
let (gdz_clamped_1, gpz_1) = if gdz_1 <= 0 {
(0, i32::MAX)
} else {
let gp = ftol(prelude.pos_yfrac[yfrac_idx] * gdz_1 as f32);
(gdz_1, gp)
};
GlineFrustum {
vd0,
vd1,
vz0,
vx1,
vy1,
vz1,
gixy: [gixy_0, gixy_1],
gpz: [gpz_0, gpz_1],
gdz: [gdz_clamped_0, gdz_clamped_1],
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::camera_math;
use crate::opticast_prelude;
use crate::Camera;
fn looking_down_state() -> (CameraState, OpticastPrelude) {
let cam = Camera {
pos: [0.0, 0.0, 0.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 1.0, 0.0],
forward: [0.0, 0.0, 1.0],
};
let cs = camera_math::derive(&cam, 640, 480, 320.0, 240.0, 320.0);
let prelude = opticast_prelude::derive_prelude(&cs, 2048, 1, 4, 1024);
(cs, prelude)
}
fn bit(v: f32) -> u32 {
v.to_bits()
}
#[test]
fn world_space_ray_endpoints_for_top_scanline() {
let (cs, prelude) = looking_down_state();
let g = derive_gline_frustum(&cs, &prelude, 2048, 640, 0.0, 0.0, 640.0, 0.0);
assert_eq!(bit(g.vx1), bit(320.0));
assert_eq!(bit(g.vy1), bit(-240.0));
assert_eq!(bit(g.vz1), bit(320.0));
assert_eq!(bit(g.vz0), bit(320.0));
}
#[test]
fn vd1_equals_ground_plane_magnitude() {
let (cs, prelude) = looking_down_state();
let g = derive_gline_frustum(&cs, &prelude, 2048, 640, 0.0, 0.0, 640.0, 0.0);
assert_eq!(bit(g.vd1), bit(400.0));
}
#[test]
fn vd0_clamped_to_zero_when_negative() {
let (cs, prelude) = looking_down_state();
let g = derive_gline_frustum(&cs, &prelude, 2048, 640, 0.0, 0.0, 640.0, 0.0);
assert_eq!(bit(g.vd0), bit(0.0));
}
#[test]
fn gixy_signs_match_ray_direction() {
let (cs, prelude) = looking_down_state();
let g = derive_gline_frustum(&cs, &prelude, 2048, 640, 0.0, 0.0, 640.0, 0.0);
assert_eq!(g.gixy[0], 1);
let cam = Camera {
pos: [10.0, 10.0, 0.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 1.0, 0.0],
forward: [0.0, 0.0, 1.0],
};
let cs2 = camera_math::derive(&cam, 640, 480, 320.0, 240.0, 320.0);
let prelude2 = opticast_prelude::derive_prelude(&cs2, 2048, 1, 4, 1024);
let g2 = derive_gline_frustum(&cs2, &prelude2, 2048, 640, 0.0, 0.0, 640.0, 0.0);
assert!(
g2.gixy[1] < 0,
"gixy[1] = {}, expected negative",
g2.gixy[1]
);
assert_eq!(g2.gixy[1].unsigned_abs(), 2048);
}
#[test]
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
fn gdz_lanes_for_known_scanline() {
let (cs, prelude) = looking_down_state();
let g = derive_gline_frustum(&cs, &prelude, 2048, 640, 0.0, 0.0, 640.0, 0.0);
assert_eq!(g.gdz[0], (1.25_f32 * PREC as f32).round_ties_even() as i32);
assert_eq!(
g.gdz[1],
((400.0_f32 / 240.0_f32) * PREC as f32).round_ties_even() as i32
);
}
#[test]
fn gdz_clamps_to_zero_on_overflow() {
let (cs, prelude) = looking_down_state();
let g = derive_gline_frustum(&cs, &prelude, 2048, 640, 320.0, 0.0, 320.0, 480.0);
assert_eq!(g.gdz[0], 0);
assert_eq!(g.gpz[0], i32::MAX);
}
}