//! The guard-band clip test, from the algorithm documented for R&C1 in //! `docs/plan/tfrag_rac1.md` ยง3b.2. //! //! Attribution matters here: the algorithm is taken from the reference and //! **verified against the game only in part**. The geometry it needs is parsed //! and tested (`TfragGeometry::bsphere`, `clip_corners`, `draw_mode`); the camera //! and frustum are per-frame game state that no savestate on hand provides //! consecutively, so steps 1-4 are implemented from the reference and **have not //! been compared against a real frame**. //! //! It is written as a pure function over explicit camera and frustum values so //! that the moment those are located the test can be run for real, and so the //! parts that can be checked now are checked now. /// A point in the 12.4 fixed-point units the tfrag data uses. pub type P3 = [i32; 3]; /// Near and far clip distances in the same units. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Frustum { /// Near plane distance, `n` in the reference. pub near: i32, /// Far plane distance, `f` in the reference. pub far: i32, /// Horizontal and vertical tangent of the side planes. `(tan_x, tan_y)`. pub tan: [i32; 2], } /// What the test decided to do with a patch. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DrawMode { /// Frustum, no clipping program, normal LOD and morphing. Normal, /// Guard band hit: send to the clipping program at LOD 0 with no morphing. ClipAtLod0, /// Frustum hit: do not draw at all. Cull, } /// Run the documented algorithm for one patch. /// /// `radius` is `bsphere[3]`, `centre` is `bsphere[0..3]`, and `corners` is /// `clip_corners`. `R` is the camera rotation: row-major, applied to a camera /// space offset. The reference writes step 1 as `R * (bsphere - camera * 1024)` /// -- the 1024 is the fixed-point scale, so `camera` is in world units and the /// subtraction happens in fixed point. /// /// Step 5's second volume is the reference's guard band: the same frustum with /// its x and y **halved**, which in `tan` terms is a 4x guard band, and it is /// the band that decides whether the clipping program is needed at all. #[must_use] pub fn guard_band( centre: P3, radius: i32, corners: &[P3], camera: P3, r: [[i32; 3]; 3], f: Frustum, ) -> DrawMode { // 1. to camera space, fixed point. let dx = centre[0] - camera[0]; let dy = centre[1] - camera[1]; let dz = centre[2] - camera[2]; let d = [ r[0][0] * dx + r[0][1] * dy + r[0][2] * dz, r[1][0] * dx + r[1][1] * dy + r[1][2] * dz, r[2][0] * dx + r[2][1] * dy + r[2][2] * dz, ]; // 2. near/far for the bounding sphere, truncated toward zero. let far = d[2] + radius; let near = d[2] - radius; // 3. depth cull. if near > 512_000 || far <= f.near { return DrawMode::Cull; } // 3. side planes: tan*z - |x| -+ r*sqrt(1 + tan^2), sign per axis. // `isqrt` is the reference's integer square root; both margins non-negative // is step 4's "wholly inside". let mut inside = [true; 2]; for (i, m) in [d[0].abs(), d[1].abs()].iter().enumerate() { let t = f.tan[i]; let s = isqrt(1 + (t as i64) * (t as i64)) * radius; inside[i] = t * d[2] + s >= *m; } // 4. wholly inside: both margins, and strictly within near/far. if inside[0] && inside[1] && far < f.far && near >= f.near { return DrawMode::Normal; } // 5/6. re-test the 8 corners: against the frustum, then the guard band. if corners.is_empty() { return DrawMode::Normal; } // The reference culls when *one* plane has every corner outside it. That is // not "every corner is outside some plane" -- the first version conflated the // two and culled patches that only straddle different planes. // // Per corner, a 4-bit mask of which planes it is outside of. let masks: Vec = corners .iter() .map(|c| { let mut m = 0u8; if c[2] - radius < f.near { m |= 1; } if c[2] + radius > f.far { m |= 2; } for (i, x) in [c[0].abs(), c[1].abs()].iter().enumerate() { let t = if i == 0 { f.tan[0] } else { f.tan[1] }; if t * c[2] + isqrt(1 + (t as i64) * (t as i64)) * radius < *x { m |= 1 << (2 + i); } } m }) .collect(); // All corners outside one *and the same* plane. let all_outside_one_plane = [1u8, 2, 4, 8].iter().any(|p| { let p = *p; !masks.is_empty() && masks.iter().all(|m| m & p != 0) }); // And the same against the guard band: any corner outside it needs the // clipping program. let band = Frustum { near: f.near, far: f.far, tan: [f.tan[0] / 4, f.tan[1] / 4], }; let any_outside_band = corners.iter().any(|c| { if c[2] - radius < band.near || c[2] + radius > band.far { return true; } [c[0].abs(), c[1].abs()].iter().enumerate().any(|(i, x)| { let t = band.tan[i]; t * c[2] + isqrt(1 + (t as i64) * (t as i64)) * radius < *x }) }); if all_outside_one_plane { return DrawMode::Cull; } if any_outside_band { return DrawMode::ClipAtLod0; } DrawMode::Normal } /// Integer square root, floor. The reference truncates rather than rounds. fn isqrt(v: i64) -> i32 { if v <= 0 { return 0; } let mut n = (v as f64).sqrt() as i64; while n * n > v { n -= 1; } while (n + 1) * (n + 1) <= v { n += 1; } n as i32 } #[cfg(test)] mod tests { use super::*; const R_ID: [[i32; 3]; 3] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; fn frustum() -> Frustum { Frustum { near: 1000, far: 200_000, tan: [400, 400], } } #[test] fn a_patch_directly_ahead_is_drawn_normally() { let m = guard_band( [0, 0, 50_000], 1000, &[ [-500, -500, 50_000], [500, -500, 50_000], [500, 500, 50_000], [-500, 500, 50_000], ], [0, 0, 0], R_ID, frustum(), ); assert_eq!(m, DrawMode::Normal); } #[test] fn a_patch_behind_the_camera_is_culled() { let m = guard_band( [0, 0, -50_000], 1000, &[[0, 0, -50_000]], [0, 0, 0], R_ID, frustum(), ); assert_eq!(m, DrawMode::Cull); } #[test] fn a_patch_beyond_the_far_plane_is_culled() { let m = guard_band( [0, 0, 900_000], 1000, &[[0, 0, 900_000]], [0, 0, 0], R_ID, frustum(), ); assert_eq!(m, DrawMode::Cull); } #[test] fn a_patch_on_the_side_uses_the_guard_band_not_an_immediate_cull() { // Far off to the side: outside the side planes, but its corners are not // all outside one plane, so it goes to the clipping program, not Cull. // tan is 1024 (1.0 in 10.10), so at z = 1,000 the side plane sits at // x = 1,024,000. This patch is at x = 5,000,000: well outside the side // plane, but straddling near/far differently across its corners, so it // must reach the guard band rather than cull outright. let c = [ [5_000_000, 0, 1_000], [5_000_400, 0, 1_000], [5_000_000, 0, 1_400], [5_000_000, 400, 1_000], ]; let m = guard_band([5_000_200, 0, 1_100], 100, &c, [0, 0, 0], R_ID, frustum()); assert_ne!(m, DrawMode::Normal, "a side patch must not draw normally"); } #[test] fn corners_all_outside_one_plane_cull() { // Every corner beyond the far plane -> cull outright. let c = [ [0, 0, 300_000], [100, 0, 300_000], [0, 100, 300_000], [100, 100, 300_000], ]; let m = guard_band([50, 50, 300_000], 10, &c, [0, 0, 0], R_ID, frustum()); assert_eq!(m, DrawMode::Cull); } #[test] fn isqrt_floors() { assert_eq!(isqrt(0), 0); assert_eq!(isqrt(1), 1); assert_eq!(isqrt(15), 3, "floor(3.87)"); assert_eq!(isqrt(16), 4); assert_eq!(isqrt(17), 4); } #[test] fn the_rotation_moves_the_patch_into_camera_space() { // 90 degrees about z: world +x should end up on the camera's +y. let r = [[0, -1, 0], [1, 0, 0], [0, 0, 1]]; let m = guard_band([50_000, 0, 50_000], 1000, &[], [0, 0, 0], r, frustum()); // Directly ahead after rotation, so Normal rather than Cull. assert_eq!(m, DrawMode::Normal); } }