use crate::camera_math::CameraState;
use crate::fixed::{ftol, isshldiv16safe, lbound0, mulshr16, shldiv16};
use crate::opticast_prelude::OpticastPrelude;
use crate::projection::ProjectionRect;
use crate::rasterizer::{Rasterizer, ScanScratch};
use crate::ray_step::RayStep;
#[derive(Debug, Clone, Copy)]
pub struct ScanContext<'a> {
pub proj: &'a ProjectionRect,
pub rs: &'a RayStep,
pub prelude: &'a OpticastPrelude,
pub xres: i32,
pub y_start: i32,
pub y_end: i32,
pub anginc: i32,
pub camera_state: &'a CameraState,
pub camera_gstartz0: i32,
pub camera_gstartz1: i32,
pub camera_vptr_offset: usize,
}
#[allow(
clippy::cast_possible_truncation,
// wx0/wx1 vs iwy0/iwy1 trip similar_names; voxlap names.
clippy::similar_names
)]
#[must_use]
pub fn vline_clip(x0: f32, y0: f32, x1: f32, y1: f32, grd: f32, p: &ProjectionRect) -> (i32, i32) {
let dxy = (x1 - x0) * grd; let project_y = |x_target: f32| ((x_target - x0) / dxy + y0).round_ties_even() as i32;
let identity_y = |y_in: f32| y_in.round_ties_even() as i32;
let iy0_raw = if x0 < p.wx0 {
project_y(p.wx0)
} else if x0 > p.wx1 {
project_y(p.wx1)
} else {
identity_y(y0)
};
let iy1 = if x1 < p.wx0 {
project_y(p.wx0)
} else if x1 > p.wx1 {
project_y(p.wx1)
} else {
identity_y(y1)
};
let iy0 = iy0_raw.clamp(p.iwy0, p.iwy1);
(iy0, iy1)
}
#[allow(clippy::cast_possible_truncation, clippy::similar_names)]
#[must_use]
pub fn hline_clip(x0: f32, y0: f32, x1: f32, y1: f32, grd: f32, p: &ProjectionRect) -> (i32, i32) {
let dyx = (y1 - y0) * grd; let project_x = |y_target: f32| ((y_target - y0) / dyx + x0).round_ties_even() as i32;
let identity_x = |x_in: f32| x_in.round_ties_even() as i32;
let ix0_raw = if y0 < p.wy0 {
project_x(p.wy0)
} else if y0 > p.wy1 {
project_x(p.wy1)
} else {
identity_x(x0)
};
let ix1 = if y1 < p.wy0 {
project_x(p.wy0)
} else if y1 > p.wy1 {
project_x(p.wy1)
} else {
identity_x(x1)
};
let ix0 = ix0_raw.clamp(p.iwx0, p.iwx1);
(ix0, ix1)
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::too_many_lines
)]
pub fn top_quadrant<R: Rasterizer>(
rasterizer: &mut R,
scratch: &mut ScanScratch,
ctx: &ScanContext<'_>,
) {
let p = ctx.proj;
let rs = ctx.rs;
let forward_z_sign = ctx.prelude.forward_z_sign;
let j_count = ((p.x1 - p.x0) / ctx.anginc as f32).round_ties_even() as i32;
if p.fy >= 0.0 || j_count <= 0 {
return;
}
let ff_x = (p.x1 - p.x0) / j_count as f32;
let grd = 1.0 / (p.wy0 - p.cy);
scratch.reset_for_quadrant(-forward_z_sign);
let mut f_ray = p.x0 + ff_x * 0.5;
for i in 0..j_count as usize {
let (iy0, iy1) = vline_clip(p.cx, p.cy, f_ray, p.wy0, grd, p);
let gscan = scratch.gscanptr as isize;
scratch.angstart[i] = if forward_z_sign < 0 {
gscan + iy0 as isize
} else {
gscan - iy1 as isize
};
let length = iy1.abs_diff(iy0);
let dxy = (f_ray - p.cx) * grd;
let cast_x0 = (iy0 as f32 - p.wy0) * dxy + f_ray;
let cast_x1 = (iy1 as f32 - p.wy0) * dxy + f_ray;
rasterizer.gline(scratch, length, cast_x0, iy0 as f32, cast_x1, iy1 as f32);
scratch.gscanptr += length as usize + 1;
f_ray += ff_x;
}
let j_fixed = j_count << 16;
let f_scale = j_fixed as f32 / ((p.x1 - p.x0) * grd);
let kadd = ftol((p.cx - p.x0) * grd * f_scale);
let p1_init = (p.cx - 0.5).round_ties_even() as i32;
let mut p0 = lbound0(p1_init + 1, ctx.xres);
let mut p1 = lbound0(p1_init, ctx.xres);
let mut sy = (p.cy - 0.50005).round_ties_even() as i32;
if sy >= ctx.y_end {
sy = ctx.y_end - 1;
}
let ff_check = ((p1 as f32 - p.cx).abs() + 1.0) * f_scale / 2_147_483_647.0 + p.cy;
while ff_check < sy as f32 && sy >= ctx.y_start {
sy -= 1;
}
if sy < ctx.y_start {
return;
}
let kmul = ftol(f_scale);
while sy >= ctx.y_start {
if isshldiv16safe(kmul, (sy << 16) - rs.cy16) != 0 {
break;
}
sy -= 1;
}
let mut i = if forward_z_sign < 0 { -sy } else { sy };
while sy >= ctx.y_start {
let ui = shldiv16(kmul, (sy << 16) - rs.cy16);
let mut u = mulshr16((p0 << 16) - rs.cx16, ui) + kadd;
while p0 > 0 && u >= ui {
u -= ui;
p0 -= 1;
}
let mut u1 = (p1 - p0) * ui + u;
while p1 < ctx.xres && u1 < j_fixed {
u1 += ui;
p1 += 1;
}
if p0 < p1 {
rasterizer.hrend(scratch, p0, sy, p1, u, ui, i);
}
sy -= 1;
i -= forward_z_sign;
}
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::too_many_lines
)]
pub fn bottom_quadrant<R: Rasterizer>(
rasterizer: &mut R,
scratch: &mut ScanScratch,
ctx: &ScanContext<'_>,
) {
let p = ctx.proj;
let rs = ctx.rs;
let forward_z_sign = ctx.prelude.forward_z_sign;
let j_count = ((p.x2 - p.x3) / ctx.anginc as f32).round_ties_even() as i32;
if p.gy <= 0.0 || j_count <= 0 {
return;
}
let ff_x = (p.x2 - p.x3) / j_count as f32;
let grd = 1.0 / (p.wy1 - p.cy);
scratch.reset_for_quadrant(forward_z_sign);
let mut f_ray = p.x3 + ff_x * 0.5;
for i in 0..j_count as usize {
let (iy0, iy1) = vline_clip(p.cx, p.cy, f_ray, p.wy1, grd, p);
let gscan = scratch.gscanptr as isize;
scratch.angstart[i] = if forward_z_sign < 0 {
gscan - iy0 as isize
} else {
gscan + iy1 as isize
};
let length = iy1.abs_diff(iy0);
let dxy = (f_ray - p.cx) * grd;
let cast_x0 = (iy0 as f32 - p.wy1) * dxy + f_ray;
let cast_x1 = (iy1 as f32 - p.wy1) * dxy + f_ray;
rasterizer.gline(scratch, length, cast_x0, iy0 as f32, cast_x1, iy1 as f32);
scratch.gscanptr += length as usize + 1;
f_ray += ff_x;
}
let j_fixed = j_count << 16;
let f_scale = j_fixed as f32 / ((p.x2 - p.x3) * grd);
let kadd = ftol((p.cx - p.x3) * grd * f_scale);
let p1_init = (p.cx - 0.5).round_ties_even() as i32;
let mut p0 = lbound0(p1_init + 1, ctx.xres);
let mut p1 = lbound0(p1_init, ctx.xres);
let mut sy = (p.cy + 0.50005).round_ties_even() as i32;
if sy < ctx.y_start {
sy = ctx.y_start;
}
let ff_check = ((p1 as f32 - p.cx).abs() + 1.0) * f_scale / 2_147_483_647.0 + p.cy;
while ff_check > sy as f32 && sy < ctx.y_end {
sy += 1;
}
if sy >= ctx.y_end {
return;
}
let kmul = ftol(f_scale);
while sy < ctx.y_end {
if isshldiv16safe(kmul, (sy << 16) - rs.cy16) != 0 {
break;
}
sy += 1;
}
let mut i = if forward_z_sign < 0 { sy } else { -sy };
while sy < ctx.y_end {
let ui = shldiv16(kmul, (sy << 16) - rs.cy16);
let mut u = mulshr16((p0 << 16) - rs.cx16, ui) + kadd;
while p0 > 0 && u >= ui {
u -= ui;
p0 -= 1;
}
let mut u1 = (p1 - p0) * ui + u;
while p1 < ctx.xres && u1 < j_fixed {
u1 += ui;
p1 += 1;
}
if p0 < p1 {
rasterizer.hrend(scratch, p0, sy, p1, u, ui, i);
}
sy += 1;
i -= forward_z_sign;
}
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::too_many_lines
)]
pub fn right_quadrant<R: Rasterizer>(
rasterizer: &mut R,
scratch: &mut ScanScratch,
ctx: &ScanContext<'_>,
) {
let p = ctx.proj;
let rs = ctx.rs;
let forward_z_sign = ctx.prelude.forward_z_sign;
let j_count = ((p.y2 - p.y1) / ctx.anginc as f32).round_ties_even() as i32;
if p.gx <= 0.0 || j_count <= 0 {
return;
}
let ff_y = (p.y2 - p.y1) / j_count as f32;
let grd = 1.0 / (p.wx1 - p.cx);
scratch.reset_for_quadrant(-forward_z_sign);
let mut f_ray = p.y1 + ff_y * 0.5;
for i in 0..j_count as usize {
let (ix0, ix1) = hline_clip(p.cx, p.cy, p.wx1, f_ray, grd, p);
let gscan = scratch.gscanptr as isize;
scratch.angstart[i] = if forward_z_sign < 0 {
gscan - ix0 as isize
} else {
gscan + ix1 as isize
};
let length = ix1.abs_diff(ix0);
let dyx = (f_ray - p.cy) * grd;
let cast_y0 = (ix0 as f32 - p.wx1) * dyx + f_ray;
let cast_y1 = (ix1 as f32 - p.wx1) * dyx + f_ray;
rasterizer.gline(scratch, length, ix0 as f32, cast_y0, ix1 as f32, cast_y1);
scratch.gscanptr += length as usize + 1;
f_ray += ff_y;
}
let j_fixed = j_count << 16;
let f_scale = j_fixed as f32 / ((p.y2 - p.y1) * grd);
let kadd = ftol((p.cy - p.y1) * grd * f_scale);
let p1_init = (p.cy - 0.5).round_ties_even() as i32;
let mut p0 = lbound0(p1_init + 1, ctx.y_end).max(ctx.y_start);
let mut p1 = lbound0(p1_init, ctx.y_end).max(ctx.y_start);
let mut sx = (p.cx + 0.50005).round_ties_even() as i32;
if sx < 0 {
sx = 0;
}
let ff_check = ((p1 as f32 - p.cy).abs() + 1.0) * f_scale / 2_147_483_647.0 + p.cx;
while ff_check > sx as f32 && sx < ctx.xres {
sx += 1;
}
if sx >= ctx.xres {
return;
}
let kmul = ftol(f_scale);
while sx < ctx.xres {
if isshldiv16safe(kmul, (sx << 16) - rs.cx16) != 0 {
break;
}
sx += 1;
}
while sx < ctx.xres {
let ui = shldiv16(kmul, (sx << 16) - rs.cx16);
let mut u = mulshr16((p0 << 16) - rs.cy16, ui) + kadd;
while p0 > ctx.y_start && u >= ui {
u -= ui;
p0 -= 1;
scratch.lastx[p0 as usize] = sx;
}
scratch.uurend[sx as usize] = u;
scratch.uurend[sx as usize + scratch.uurend_half_stride] = ui;
u += (p1 - p0) * ui;
while p1 < ctx.y_end && u < j_fixed {
u += ui;
scratch.lastx[p1 as usize] = sx;
p1 += 1;
}
sx += 1;
}
if forward_z_sign < 0 {
for sy in p0..p1 {
let lx = scratch.lastx[sy as usize];
rasterizer.vrend(scratch, lx, sy, ctx.xres, lx, 1);
}
} else {
for sy in p0..p1 {
let lx = scratch.lastx[sy as usize];
rasterizer.vrend(scratch, lx, sy, ctx.xres, -lx, -1);
}
}
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::too_many_lines
)]
pub fn left_quadrant<R: Rasterizer>(
rasterizer: &mut R,
scratch: &mut ScanScratch,
ctx: &ScanContext<'_>,
) {
let p = ctx.proj;
let rs = ctx.rs;
let forward_z_sign = ctx.prelude.forward_z_sign;
let j_count = ((p.y3 - p.y0) / ctx.anginc as f32).round_ties_even() as i32;
if p.fx >= 0.0 || j_count <= 0 {
return;
}
let ff_y = (p.y3 - p.y0) / j_count as f32;
let grd = 1.0 / (p.wx0 - p.cx);
scratch.reset_for_quadrant(forward_z_sign);
let mut f_ray = p.y0 + ff_y * 0.5;
for i in 0..j_count as usize {
let (ix0, ix1) = hline_clip(p.cx, p.cy, p.wx0, f_ray, grd, p);
let gscan = scratch.gscanptr as isize;
scratch.angstart[i] = if forward_z_sign < 0 {
gscan + ix0 as isize
} else {
gscan - ix1 as isize
};
let length = ix1.abs_diff(ix0);
let dyx = (f_ray - p.cy) * grd;
let cast_y0 = (ix0 as f32 - p.wx0) * dyx + f_ray;
let cast_y1 = (ix1 as f32 - p.wx0) * dyx + f_ray;
rasterizer.gline(scratch, length, ix0 as f32, cast_y0, ix1 as f32, cast_y1);
scratch.gscanptr += length as usize + 1;
f_ray += ff_y;
}
let j_fixed = j_count << 16;
let f_scale = j_fixed as f32 / ((p.y3 - p.y0) * grd);
let kadd = ftol((p.cy - p.y0) * grd * f_scale);
let p1_init = (p.cy - 0.5).round_ties_even() as i32;
let mut p0 = lbound0(p1_init + 1, ctx.y_end).max(ctx.y_start);
let mut p1 = lbound0(p1_init, ctx.y_end).max(ctx.y_start);
let mut sx = (p.cx - 0.50005).round_ties_even() as i32;
if sx >= ctx.xres {
sx = ctx.xres - 1;
}
let ff_check = ((p1 as f32 - p.cy).abs() + 1.0) * f_scale / 2_147_483_647.0 + p.cx;
while ff_check < sx as f32 && sx >= 0 {
sx -= 1;
}
if sx < 0 {
return;
}
let kmul = ftol(f_scale);
while sx >= 0 {
if isshldiv16safe(kmul, (sx << 16) - rs.cx16) != 0 {
break;
}
sx -= 1;
}
while sx >= 0 {
let ui = shldiv16(kmul, (sx << 16) - rs.cx16);
let mut u = mulshr16((p0 << 16) - rs.cy16, ui) + kadd;
while p0 > ctx.y_start && u >= ui {
u -= ui;
p0 -= 1;
scratch.lastx[p0 as usize] = sx;
}
scratch.uurend[sx as usize] = u;
scratch.uurend[sx as usize + scratch.uurend_half_stride] = ui;
u += (p1 - p0) * ui;
while p1 < ctx.y_end && u < j_fixed {
u += ui;
scratch.lastx[p1 as usize] = sx;
p1 += 1;
}
sx -= 1;
}
for sy in p0..p1 {
let lx = scratch.lastx[sy as usize];
rasterizer.vrend(scratch, 0, sy, lx + 1, 0, forward_z_sign);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::camera_math;
use crate::projection;
use crate::Camera;
fn looking_down_projection() -> ProjectionRect {
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 s = camera_math::derive(&cam, 640, 480, 320.0, 240.0, 320.0);
projection::derive_projection(&s, 640, 480, 320.0, 240.0, 320.0, 1)
}
#[test]
fn vline_clip_line_inside_viewport_uses_y0_y1() {
let p = looking_down_projection();
let grd = 1.0 / (400.0 - 50.0); let (iy0, iy1) = vline_clip(100.0, 50.0, 100.0, 400.0, grd, &p);
assert_eq!((iy0, iy1), (50, 400));
}
#[test]
#[allow(clippy::cast_possible_truncation)]
fn vline_clip_x0_left_of_viewport_projects() {
let p = looking_down_projection();
let grd = 1.0 / (200.0 - 100.0);
let (iy0, iy1) = vline_clip(-100.0, 100.0, 320.0, 200.0, grd, &p);
let want_iy0 = (((-1.0 - -100.0_f32) / 4.2_f32) + 100.0).round_ties_even() as i32;
assert_eq!(iy0, want_iy0);
assert_eq!(iy1, 200);
}
#[test]
fn vline_clip_iy0_clamped_to_viewport_y_range() {
let p = looking_down_projection();
let grd = 1.0 / (-50.0 - -100.0);
let (iy0, _) = vline_clip(-10.0, -100.0, 100.0, -50.0, grd, &p);
assert_eq!(iy0, p.iwy0);
}
#[test]
fn vline_clip_iy1_not_clamped_even_outside_range() {
let p = looking_down_projection();
let grd = 1.0 / (-500.0 - -1000.0);
let (iy0, iy1) = vline_clip(10.0, -1000.0, 20.0, -500.0, grd, &p);
assert_eq!(iy0, p.iwy0); assert_eq!(iy1, -500); }
#[test]
fn vline_clip_line_entirely_outside_left_zero_length() {
let p = looking_down_projection();
let grd = 1.0 / (300.0 - 100.0); let (iy0, iy1) = vline_clip(-50.0, 100.0, -10.0, 300.0, grd, &p);
assert_eq!(iy0, iy1);
}
#[test]
fn hline_clip_mirrors_vline_clip_for_horizontal_lines() {
let p = looking_down_projection();
let grd = 1.0 / (400.0 - 50.0);
let (ix0, ix1) = hline_clip(50.0, 100.0, 400.0, 100.0, grd, &p);
assert_eq!((ix0, ix1), (50, 400));
}
#[test]
#[allow(clippy::cast_possible_truncation)]
fn hline_clip_y0_above_viewport_projects() {
let p = looking_down_projection();
let grd = 1.0 / (200.0 - 100.0);
let (ix0, ix1) = hline_clip(100.0, -100.0, 200.0, 320.0, grd, &p);
let want_ix0 = (((-1.0 - -100.0_f32) / 4.2_f32) + 100.0).round_ties_even() as i32;
assert_eq!(ix0, want_ix0);
assert_eq!(ix1, 200);
}
#[test]
fn hline_clip_ix0_clamped_to_viewport_x_range() {
let p = looking_down_projection();
let grd = 1.0 / (-500.0 - -1000.0);
let (ix0, ix1) = hline_clip(-1000.0, 10.0, -500.0, 20.0, grd, &p);
assert_eq!(ix0, p.iwx0);
assert_eq!(ix1, -500);
}
use crate::opticast_prelude;
use crate::rasterizer::{Rasterizer, ScanScratch};
use crate::ray_step;
#[derive(Debug, Default)]
struct Recorder {
gline_calls: u32,
hrend_calls: u32,
vrend_calls: u32,
first_hrend: Option<(i32, i32, i32)>, }
impl Rasterizer for Recorder {
fn gline(&mut self, _: &mut ScanScratch, _: u32, _: f32, _: f32, _: f32, _: f32) {
self.gline_calls += 1;
}
fn hrend(
&mut self,
_: &mut ScanScratch,
sx: i32,
sy: i32,
p1: i32,
_: i32,
_: i32,
_: i32,
) {
if self.first_hrend.is_none() {
self.first_hrend = Some((sx, sy, p1));
}
self.hrend_calls += 1;
}
fn vrend(&mut self, _: &mut ScanScratch, _: i32, _: i32, _: i32, _: i32, _: i32) {
self.vrend_calls += 1;
}
}
fn looking_down_context() -> (
crate::camera_math::CameraState,
crate::projection::ProjectionRect,
crate::ray_step::RayStep,
crate::opticast_prelude::OpticastPrelude,
) {
let cam = crate::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],
};
let s = camera_math::derive(&cam, 640, 480, 320.0, 240.0, 320.0);
let proj = crate::projection::derive_projection(&s, 640, 480, 320.0, 240.0, 320.0, 1);
let rs = ray_step::derive_ray_step(&s, proj.cx, proj.cy, 320.0);
let prelude = opticast_prelude::derive_prelude(&s, 2048, 1, 4, 1024);
(s, proj, rs, prelude)
}
#[test]
fn top_quadrant_skips_when_centre_below_top_edge() {
let (cs, proj_lookdown, rs, prelude) = looking_down_context();
let mut proj = proj_lookdown;
proj.cy = -1000.0;
proj.fy = proj.wy0 - proj.cy;
let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
top_quadrant(&mut rec, &mut scratch, &ctx);
assert_eq!(rec.gline_calls, 0);
assert_eq!(rec.hrend_calls, 0);
}
#[test]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn top_quadrant_casts_one_ray_per_anginc_step() {
let (cs, proj, rs, prelude) = looking_down_context();
let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
top_quadrant(&mut rec, &mut scratch, &ctx);
let expected = ((proj.x1 - proj.x0) / 1.0).round_ties_even() as u32;
assert_eq!(rec.gline_calls, expected);
}
#[test]
fn top_quadrant_emits_at_least_one_hrend() {
let (cs, proj, rs, prelude) = looking_down_context();
let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
top_quadrant(&mut rec, &mut scratch, &ctx);
assert!(rec.hrend_calls > 0, "expected ≥ 1 hrend, got 0");
let (_, sy, _) = rec.first_hrend.expect("first hrend recorded");
assert!(sy <= 239, "first hrend sy = {sy}, expected ≤ 239");
}
#[test]
fn bottom_quadrant_skips_when_centre_below_bottom_edge() {
let (cs, proj_lookdown, rs, prelude) = looking_down_context();
let mut proj = proj_lookdown;
proj.cy = 1000.0;
proj.gy = proj.wy1 - proj.cy; let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
bottom_quadrant(&mut rec, &mut scratch, &ctx);
assert_eq!(rec.gline_calls, 0);
assert_eq!(rec.hrend_calls, 0);
}
#[test]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn bottom_quadrant_casts_one_ray_per_anginc_step() {
let (cs, proj, rs, prelude) = looking_down_context();
let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
bottom_quadrant(&mut rec, &mut scratch, &ctx);
let expected = ((proj.x2 - proj.x3) / 1.0).round_ties_even() as u32;
assert_eq!(rec.gline_calls, expected);
}
#[test]
fn bottom_quadrant_emits_at_least_one_hrend() {
let (cs, proj, rs, prelude) = looking_down_context();
let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
bottom_quadrant(&mut rec, &mut scratch, &ctx);
assert!(rec.hrend_calls > 0, "expected ≥ 1 hrend, got 0");
let (_, sy, _) = rec.first_hrend.expect("first hrend recorded");
assert!(sy >= 240, "first hrend sy = {sy}, expected ≥ 240");
}
#[test]
fn right_quadrant_skips_when_centre_right_of_viewport() {
let (cs, proj_lookdown, rs, prelude) = looking_down_context();
let mut proj = proj_lookdown;
proj.cx = 1000.0;
proj.gx = proj.wx1 - proj.cx; let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
right_quadrant(&mut rec, &mut scratch, &ctx);
assert_eq!(rec.gline_calls + rec.vrend_calls, 0);
}
#[test]
fn left_quadrant_skips_when_centre_left_of_viewport() {
let (cs, proj_lookdown, rs, prelude) = looking_down_context();
let mut proj = proj_lookdown;
proj.cx = -1000.0;
proj.fx = proj.wx0 - proj.cx; let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
left_quadrant(&mut rec, &mut scratch, &ctx);
assert_eq!(rec.gline_calls + rec.vrend_calls, 0);
}
#[test]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn right_quadrant_casts_one_ray_per_anginc_step() {
let (cs, proj, rs, prelude) = looking_down_context();
let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
right_quadrant(&mut rec, &mut scratch, &ctx);
let expected = ((proj.y2 - proj.y1) / 1.0).round_ties_even() as u32;
assert_eq!(rec.gline_calls, expected);
}
#[test]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn left_quadrant_casts_one_ray_per_anginc_step() {
let (cs, proj, rs, prelude) = looking_down_context();
let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
left_quadrant(&mut rec, &mut scratch, &ctx);
let expected = ((proj.y3 - proj.y0) / 1.0).round_ties_even() as u32;
assert_eq!(rec.gline_calls, expected);
}
#[test]
fn right_and_left_quadrants_emit_vrend() {
let (cs, proj, rs, prelude) = looking_down_context();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
let mut rec_right = Recorder::default();
right_quadrant(&mut rec_right, &mut scratch, &ctx);
assert!(
rec_right.vrend_calls > 0,
"right quadrant: expected ≥ 1 vrend"
);
scratch = ScanScratch::new_for_size(640, 480, 2048);
let mut rec_left = Recorder::default();
left_quadrant(&mut rec_left, &mut scratch, &ctx);
assert!(
rec_left.vrend_calls > 0,
"left quadrant: expected ≥ 1 vrend"
);
}
#[test]
fn top_quadrant_advances_gscanptr_by_ray_lengths() {
let (cs, proj, rs, prelude) = looking_down_context();
let mut rec = Recorder::default();
let mut scratch = ScanScratch::new_for_size(640, 480, 2048);
let ctx = ScanContext {
proj: &proj,
rs: &rs,
prelude: &prelude,
xres: 640,
y_start: 0,
y_end: 480,
anginc: 1,
camera_state: &cs,
camera_gstartz0: 0,
camera_gstartz1: 0,
camera_vptr_offset: 0,
};
top_quadrant(&mut rec, &mut scratch, &ctx);
assert!(scratch.gscanptr >= rec.gline_calls as usize);
}
}