fix(pipeline): hatch frustum cull tests AABB in wrong space
`HatchGpu.world_aabb` was built by iterating `model.boundary` — but those vertices are stored as f32 offsets from `model.world_origin` (precision anchor for UTM-scale WCS). So the AABB was in *origin-relative* space, not the local space `view_proj` projects from. `aabb_offscreen` (Phase 2.3) then asked "is this rect near (0,0) in NDC?" instead of "is this rect at the hatch's actual position?" — which culled in-view hatches whenever the camera moved off the origin and rendered out-of-view ones near it. Sub-pixel LOD (`aabb_below_pixel`, Phase 3.3) survived because it measures a projected diagonal, which is translation-invariant. Fixes: - Add `world_origin` back when building `HatchGpu.world_aabb`, so the stored rect is in absolute local space. - 25 % margin on the viewport rect in `aabb_offscreen`, matching the wire path's `view_world_aabb` margin — keeps hatches at the edge rendered while panning. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
38f9733eb1
commit
6ae3a9d91d
2 changed files with 35 additions and 6 deletions
|
|
@ -1327,9 +1327,16 @@ fn aabb_diagonal_pixels(
|
|||
}
|
||||
|
||||
/// `true` when the world-XY AABB projects entirely outside the
|
||||
/// viewport rect. Phase 2.2 mesh-frustum cull. Equivalent to a 2D
|
||||
/// bounding-box rejection test in NDC; same 4-corner projection used
|
||||
/// for LOD picking, so the extra cost is negligible.
|
||||
/// viewport rect (extended by `MARGIN_FRAC` to absorb pan inertia and
|
||||
/// avoid edge pop-in). Phase 2.2 mesh-frustum / Phase 2.3 hatch +
|
||||
/// wipeout cull. Equivalent to a 2D bounding-box rejection test in
|
||||
/// NDC; uses the same 4-corner projection that LOD picking already
|
||||
/// does, so the extra cost is negligible.
|
||||
///
|
||||
/// IMPORTANT: the AABB must be in the same local space (world_offset
|
||||
/// subtracted) that `view_proj` expects. `HatchGpu.world_aabb` rebuilds
|
||||
/// the absolute local-space rect from `model.world_origin + boundary
|
||||
/// extents` for this reason; meshes already store an absolute rect.
|
||||
fn aabb_offscreen(
|
||||
aabb: [f32; 4],
|
||||
view_proj: glam::Mat4,
|
||||
|
|
@ -1360,8 +1367,13 @@ fn aabb_offscreen(
|
|||
if py < min_py { min_py = py; }
|
||||
if py > max_py { max_py = py; }
|
||||
}
|
||||
// Reject if projected AABB sits fully to one side of the viewport.
|
||||
max_px < 0.0 || min_px > w || max_py < 0.0 || min_py > h
|
||||
// 25% pad on each side — matches `view_world_aabb` (wire path),
|
||||
// keeps edge geometry rendered while panning before the next
|
||||
// upload reaches the GPU.
|
||||
const MARGIN_FRAC: f32 = 0.25;
|
||||
let mx = w * MARGIN_FRAC;
|
||||
let my = h * MARGIN_FRAC;
|
||||
max_px < -mx || min_px > w + mx || max_py < -my || min_py > h + my
|
||||
}
|
||||
|
||||
/// Return `true` when the world-XY AABB's screen-space size is below the
|
||||
|
|
|
|||
Loading…
Reference in a new issue