forked from 1b-agent/opencadstudio-upstream
fix(view): stabilize 3D navigation and hidden lines
Refresh stale depth bounds after geometry edits, allow orbiting through camera poles, and preserve hidden-line occlusion for selected solids.\n\nFixes #957
This commit is contained in:
1 parent
a3d85f6483
commit
3088e641aa
6 files changed
+72
-16
No files matched your search
+36
-10
@@ -486,17 +486,8 @@ impl Camera {
|
||||
let yaw = Quat::from_rotation_z(-delta_x * speed);
|
||||
self.rotation = (yaw * self.rotation).normalize();
|
||||
let cam_right = self.rotation * Vec3::X;
|
||||
let cur_z = (self.rotation * Vec3::Z).z;
|
||||
let pitched =
|
||||
self.rotation =
|
||||
(Quat::from_axis_angle(cam_right, -delta_y * speed) * self.rotation).normalize();
|
||||
let new_z = (pitched * Vec3::Z).z;
|
||||
// Accept the pitch while the gaze stays clear of the poles, OR when it
|
||||
// moves back AWAY from a pole — so the straight-down default view (z = 1)
|
||||
// can still be tilted out of (a hard `< 0.9995` gate alone would freeze
|
||||
// small drags there). Only a step deeper INTO a pole is rejected.
|
||||
if new_z.abs() < 0.9995 || new_z.abs() < cur_z.abs() {
|
||||
self.rotation = pitched;
|
||||
}
|
||||
|
||||
// Revolve the target around the pivot by the same rotation delta so the
|
||||
// view orbits the chosen centre instead of the fixed file centre (#229).
|
||||
@@ -651,6 +642,15 @@ impl Camera {
|
||||
self.depth_half_range = self.depth_extent_in_view(min, max);
|
||||
}
|
||||
|
||||
/// Drop a fitted depth range after the drawing changes. Keeping either the
|
||||
/// old box or its scalar fallback can clip newly-created geometry until the
|
||||
/// next Zoom Extents. The unset projection uses its conservative fallback
|
||||
/// until the scene refreshes the current model bounds.
|
||||
pub(crate) fn invalidate_model_bounds(&mut self) {
|
||||
self.model_bounds = None;
|
||||
self.depth_half_range = 0.0;
|
||||
}
|
||||
|
||||
pub(crate) fn fitted_model_bounds(&self) -> Option<(DVec3, DVec3)> {
|
||||
self.model_bounds
|
||||
}
|
||||
@@ -874,6 +874,32 @@ mod tests {
|
||||
"half-height {half_h} no longer contains the drawing"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orbit_crosses_a_pole_without_stalling() {
|
||||
let mut cam = Camera::default();
|
||||
for step in 0..200 {
|
||||
let before = cam.rotation;
|
||||
cam.orbit(0.0, 5.0, None);
|
||||
assert!(
|
||||
before.dot(cam.rotation).abs() < 0.999_999,
|
||||
"vertical orbit stalled at step {step}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalidating_model_bounds_also_drops_the_stale_scalar_range() {
|
||||
let mut cam = Camera::default();
|
||||
cam.fit_depth_to_bounds_f64(DVec3::splat(-1.0), DVec3::splat(1.0));
|
||||
assert!(cam.model_bounds.is_some());
|
||||
assert!(cam.depth_half_range > 0.0);
|
||||
|
||||
cam.invalidate_model_bounds();
|
||||
|
||||
assert!(cam.model_bounds.is_none());
|
||||
assert_eq!(cam.depth_half_range, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user