forked from 1b-agent/opencadstudio-upstream
fix(camera): prevent near-plane clipping during 3D orbit on wide strokes and flat drawings
This commit is contained in:
1 parent
9bbc5c3182
commit
f0cc4ffba8
2 files changed
+50
-30
No files matched your search
+38
-15
@@ -9,26 +9,37 @@ fn rendered_wire_bounds(
|
|||||||
) -> Option<(glam::DVec3, glam::DVec3)> {
|
) -> Option<(glam::DVec3, glam::DVec3)> {
|
||||||
let mut min = glam::DVec3::splat(f64::INFINITY);
|
let mut min = glam::DVec3::splat(f64::INFINITY);
|
||||||
let mut max = glam::DVec3::splat(f64::NEG_INFINITY);
|
let mut max = glam::DVec3::splat(f64::NEG_INFINITY);
|
||||||
let mut include = |point: glam::DVec3| {
|
macro_rules! include_bound {
|
||||||
if point.is_finite() {
|
($p:expr) => {
|
||||||
min = min.min(point);
|
let point: glam::DVec3 = $p;
|
||||||
max = max.max(point);
|
if point.is_finite() {
|
||||||
}
|
min = min.min(point);
|
||||||
};
|
max = max.max(point);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
for wire in wires {
|
for wire in wires {
|
||||||
let mut has_precise_position = false;
|
let mut has_precise_position = false;
|
||||||
|
let hw = wire.world_width as f64 * 0.5;
|
||||||
|
|
||||||
for (index, &[x, y, z]) in wire.points.iter().enumerate() {
|
for (index, &[x, y, z]) in wire.points.iter().enumerate() {
|
||||||
let low = wire.points_low.get(index).copied().unwrap_or([0.0; 3]);
|
let low = wire.points_low.get(index).copied().unwrap_or([0.0; 3]);
|
||||||
include(glam::DVec3::new(
|
let p = glam::DVec3::new(
|
||||||
x as f64 + low[0] as f64,
|
x as f64 + low[0] as f64,
|
||||||
y as f64 + low[1] as f64,
|
y as f64 + low[1] as f64,
|
||||||
z as f64 + low[2] as f64,
|
z as f64 + low[2] as f64,
|
||||||
));
|
);
|
||||||
|
if hw > 0.0 {
|
||||||
|
include_bound!(p - glam::DVec3::splat(hw));
|
||||||
|
include_bound!(p + glam::DVec3::splat(hw));
|
||||||
|
} else {
|
||||||
|
include_bound!(p);
|
||||||
|
}
|
||||||
has_precise_position = true;
|
has_precise_position = true;
|
||||||
}
|
}
|
||||||
for vertex in &wire.text_verts {
|
for vertex in &wire.text_verts {
|
||||||
include(glam::DVec3::new(
|
include_bound!(glam::DVec3::new(
|
||||||
vertex.pos[0] as f64 + vertex.pos_low[0] as f64,
|
vertex.pos[0] as f64 + vertex.pos_low[0] as f64,
|
||||||
vertex.pos[1] as f64 + vertex.pos_low[1] as f64,
|
vertex.pos[1] as f64 + vertex.pos_low[1] as f64,
|
||||||
vertex.pos[2] as f64 + vertex.pos_low[2] as f64,
|
vertex.pos[2] as f64 + vertex.pos_low[2] as f64,
|
||||||
@@ -36,11 +47,17 @@ fn rendered_wire_bounds(
|
|||||||
has_precise_position = true;
|
has_precise_position = true;
|
||||||
}
|
}
|
||||||
for &[x, y, z] in &wire.key_vertices {
|
for &[x, y, z] in &wire.key_vertices {
|
||||||
include(glam::DVec3::new(x, y, z));
|
let p = glam::DVec3::new(x, y, z);
|
||||||
|
if hw > 0.0 {
|
||||||
|
include_bound!(p - glam::DVec3::splat(hw));
|
||||||
|
include_bound!(p + glam::DVec3::splat(hw));
|
||||||
|
} else {
|
||||||
|
include_bound!(p);
|
||||||
|
}
|
||||||
has_precise_position = true;
|
has_precise_position = true;
|
||||||
}
|
}
|
||||||
for &(point, _) in &wire.snap_pts {
|
for &(point, _) in &wire.snap_pts {
|
||||||
include(point);
|
include_bound!(point);
|
||||||
has_precise_position = true;
|
has_precise_position = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,8 +68,8 @@ fn rendered_wire_bounds(
|
|||||||
&& x0 <= x1
|
&& x0 <= x1
|
||||||
&& y0 <= y1
|
&& y0 <= y1
|
||||||
{
|
{
|
||||||
include(glam::DVec3::new(x0 as f64, y0 as f64, fallback_z));
|
include_bound!(glam::DVec3::new(x0 as f64 - hw, y0 as f64 - hw, fallback_z - hw));
|
||||||
include(glam::DVec3::new(x1 as f64, y1 as f64, fallback_z));
|
include_bound!(glam::DVec3::new(x1 as f64 + hw, y1 as f64 + hw, fallback_z + hw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1455,12 +1472,18 @@ impl Scene {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let wire = &wires[c.idx];
|
let wire = &wires[c.idx];
|
||||||
|
let hw = (wire.world_width * 0.5) as f32;
|
||||||
for &[x, y, z] in &wire.points {
|
for &[x, y, z] in &wire.points {
|
||||||
if !x.is_finite() || !y.is_finite() || !z.is_finite() {
|
if !x.is_finite() || !y.is_finite() || !z.is_finite() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
min = min.min(glam::Vec3::new(x, y, z));
|
if hw > 0.0 {
|
||||||
max = max.max(glam::Vec3::new(x, y, z));
|
min = min.min(glam::Vec3::new(x - hw, y - hw, z - hw));
|
||||||
|
max = max.max(glam::Vec3::new(x + hw, y + hw, z + hw));
|
||||||
|
} else {
|
||||||
|
min = min.min(glam::Vec3::new(x, y, z));
|
||||||
|
max = max.max(glam::Vec3::new(x, y, z));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fold in 3D-solid mesh AABBs (not subject to the wire IQR reject).
|
// Fold in 3D-solid mesh AABBs (not subject to the wire IQR reject).
|
||||||
|
|||||||
+12
-15
@@ -246,23 +246,16 @@ impl Camera {
|
|||||||
/// A symmetric range gives the bias half the depth buffer of headroom on
|
/// A symmetric range gives the bias half the depth buffer of headroom on
|
||||||
/// each side; ortho permits a negative near.
|
/// each side; ortho permits a negative near.
|
||||||
fn ortho_depth_range(&self) -> (f32, f32) {
|
fn ortho_depth_range(&self) -> (f32, f32) {
|
||||||
// Prefer a drawing-sized, zoom-independent half-range so depth-buffer
|
// Generous headroom based on the current screen size so that rotating any
|
||||||
// precision stays constant as the user zooms. A distance-scaled range
|
// geometry visible on screen in 3D never penetrates the near/far planes.
|
||||||
// (the `else`) balloons the near/far span when zoomed out — at large
|
let view_extent = (self.ortho_size() * 3.0).max(10.0);
|
||||||
// `distance` the f32 depth buffer can no longer separate coincident
|
|
||||||
// solids / meshes / wires, so they flip draw order (issue: meshes drew
|
|
||||||
// in front of solids only when zoomed out).
|
|
||||||
let r = if let Some((min, max)) = self.model_bounds {
|
let r = if let Some((min, max)) = self.model_bounds {
|
||||||
// Depth extent along the CURRENT eye direction, recomputed each
|
self.depth_extent_in_view(min, max).max(view_extent)
|
||||||
// frame so orbiting a 3-D drawing never clips it (#473): as the
|
|
||||||
// view tilts off top, the box's width rotates onto the eye axis and
|
|
||||||
// the span grows to match. Same tight, zoom-independent precision as
|
|
||||||
// a fitted scalar, but always oriented to the live view.
|
|
||||||
self.depth_extent_in_view(min, max)
|
|
||||||
} else if self.depth_half_range > 0.0 {
|
} else if self.depth_half_range > 0.0 {
|
||||||
self.depth_half_range
|
self.depth_half_range.max(view_extent)
|
||||||
} else {
|
} else {
|
||||||
(self.distance * 1000.0).max(1.0)
|
(self.distance * 1000.0).max(view_extent).max(10.0)
|
||||||
};
|
};
|
||||||
(self.distance - r, self.distance + r)
|
(self.distance - r, self.distance + r)
|
||||||
}
|
}
|
||||||
@@ -590,7 +583,11 @@ impl Camera {
|
|||||||
.bounds_in_view(min, max)
|
.bounds_in_view(min, max)
|
||||||
.iter()
|
.iter()
|
||||||
.fold(0.0_f32, |m, c| m.max(c.z.abs()));
|
.fold(0.0_f32, |m, c| m.max(c.z.abs()));
|
||||||
(depth_r * 1.05).max(1.0)
|
let diag = (max - min).length() as f32;
|
||||||
|
// Generous headroom (1.5x depth extent + 25% of model diagonal + minimum clearance)
|
||||||
|
// so wide strokes, lineweights, camera orbit dynamics, and draw-order depth bias
|
||||||
|
// never penetrate the near clipping plane in orthographic 3D view.
|
||||||
|
(depth_r * 1.5 + diag * 0.25).max(10.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fit the camera to `min..max` — pose and depth both.
|
/// Fit the camera to `min..max` — pose and depth both.
|
||||||
|
|||||||
Reference in new issue
Block a user