forked from 1b-agent/opencadstudio-upstream
fix(renderer): keep draw-order offsets within clipping planes
This commit is contained in:
1 parent
0e42e3bf28
commit
e52e565b1f
26 files changed
+525
-90
No files matched your search
+40
-15
@@ -84,13 +84,6 @@ impl Default for Camera {
|
||||
}
|
||||
}
|
||||
|
||||
pub const OPENGL_TO_WGPU: Mat4 = glam::mat4(
|
||||
glam::vec4(1.0, 0.0, 0.0, 0.0),
|
||||
glam::vec4(0.0, 1.0, 0.0, 0.0),
|
||||
glam::vec4(0.0, 0.0, 0.5, 0.0),
|
||||
glam::vec4(0.0, 0.0, 0.5, 1.0),
|
||||
);
|
||||
|
||||
impl Camera {
|
||||
// ── Eye position ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -238,13 +231,9 @@ impl Camera {
|
||||
|
||||
/// Orthographic near/far that CENTRE the target plane at ndc-z ≈ 0.5.
|
||||
///
|
||||
/// The draw-order depth bias shifts clip-z by ±`DRAW_ORDER_BIAS` (0.001).
|
||||
/// The old range (`near = distance*0.001`, `far = distance*1000`) parked the
|
||||
/// geometry at ndc-z ≈ 0.001 — right on the near plane — so a front-biased
|
||||
/// entity landed exactly at z = 0 and got clipped the moment f32 rounding
|
||||
/// (worse at high zoom) tipped it past the plane, making the drawing vanish.
|
||||
/// A symmetric range gives the bias half the depth buffer of headroom on
|
||||
/// each side; ortho permits a negative near.
|
||||
/// A symmetric range gives draw-order offsets headroom on each side and
|
||||
/// keeps the target away from both clipping planes. The shared shader
|
||||
/// bounds those offsets near either plane; ortho permits a negative near.
|
||||
fn ortho_depth_range(&self) -> (f32, f32) {
|
||||
// Generous headroom based on the current screen size so that rotating any
|
||||
// geometry visible on screen in 3D never penetrates the near/far planes.
|
||||
@@ -296,7 +285,9 @@ impl Camera {
|
||||
orthographic(-w, w, -h, h, near, far)
|
||||
}
|
||||
};
|
||||
OPENGL_TO_WGPU * proj * view
|
||||
// The DirectX projection functions already produce WebGPU's [0, 1]
|
||||
// depth range. An OpenGL remap here would compress it to [0.5, 1].
|
||||
proj * view
|
||||
}
|
||||
|
||||
/// Project a world point to screen pixels with full f64 precision: the
|
||||
@@ -798,6 +789,40 @@ pub fn yaw_pitch_to_quat(yaw: f32, pitch: f32, roll: f32) -> Quat {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn projection_uses_the_webgpu_depth_range_once() {
|
||||
let bounds = Rectangle::with_size(iced::Size::new(800., 600.));
|
||||
for projection in [Projection::Orthographic, Projection::Perspective] {
|
||||
let camera = Camera {
|
||||
projection,
|
||||
rotation: Quat::IDENTITY,
|
||||
..Camera::default()
|
||||
};
|
||||
let (near, far) = match projection {
|
||||
Projection::Perspective => (camera.distance * 0.001, camera.distance * 1000.),
|
||||
Projection::Orthographic => camera.ortho_depth_range(),
|
||||
};
|
||||
let matrix = camera.view_proj_rte(bounds);
|
||||
let depth = |distance: f32| {
|
||||
let clip = matrix * glam::vec4(0., 0., -distance, 1.);
|
||||
clip.z / clip.w
|
||||
};
|
||||
assert!(depth(near).abs() < 1e-5, "{projection:?}: near must map to 0");
|
||||
assert!(
|
||||
(depth(far) - 1.).abs() < 1e-5,
|
||||
"{projection:?}: far must map to 1"
|
||||
);
|
||||
assert!(
|
||||
depth(near - (far - near) * 1e-7) < 0.,
|
||||
"near clipping boundary moved"
|
||||
);
|
||||
assert!(depth(far * 2.) > 1., "far clipping boundary moved");
|
||||
if projection == Projection::Orthographic {
|
||||
assert!((depth(camera.distance) - 0.5).abs() < 1e-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A drawing 140 units wide carrying one entity 800 km below its plane must
|
||||
/// still zoom to the 140 units — the outlier belongs to the depth range, not
|
||||
/// to the zoom. (Sizing both from the 3-D diagonal made the drawing a dot.)
|
||||
|
||||
Reference in new issue
Block a user