forked from 1b-agent/opencadstudio-upstream
fix(view): ZOOM Extents fills the window at the real aspect (#364)
fit_to_bounds fitted the bounding box's circumscribed DIAGONAL into the viewport height regardless of shape, so a wide drawing filled only a small central patch. Fit against the actual viewport aspect instead — required half-height = max(|y|, |x|/aspect) over the view-space corners — and trim the border margin from 20% to 10%. ZOOM EXTENTS/ALL and ZOOM WINDOW both pass the live pane aspect. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ac14e0041d
commit
ea860cab72
2 files changed
+21
-13
No files matched your search
@@ -107,7 +107,9 @@ impl Scene {
|
|||||||
if min == max {
|
if min == max {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.camera.borrow_mut().fit_to_bounds(min, max);
|
self.camera
|
||||||
|
.borrow_mut()
|
||||||
|
.fit_to_bounds(min, max, self.last_render_aspect.get().max(0.01));
|
||||||
self.camera_generation += 1;
|
self.camera_generation += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -850,7 +852,9 @@ impl Scene {
|
|||||||
if min == max {
|
if min == max {
|
||||||
max += glam::Vec3::splat(1.0);
|
max += glam::Vec3::splat(1.0);
|
||||||
}
|
}
|
||||||
self.camera.borrow_mut().fit_to_bounds(min, max);
|
self.camera
|
||||||
|
.borrow_mut()
|
||||||
|
.fit_to_bounds(min, max, self.last_render_aspect.get().max(0.01));
|
||||||
self.camera_generation += 1;
|
self.camera_generation += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-11
@@ -396,18 +396,22 @@ impl Camera {
|
|||||||
/// horizontal zoom out with it: a 140-unit drawing carrying a single entity
|
/// horizontal zoom out with it: a 140-unit drawing carrying a single entity
|
||||||
/// 800 km below its plane zoomed out to 800 km and became a dot. The two
|
/// 800 km below its plane zoomed out to 800 km and became a dot. The two
|
||||||
/// agree on a flat drawing, which is why it went unnoticed.
|
/// agree on a flat drawing, which is why it went unnoticed.
|
||||||
pub fn fit_to_bounds(&mut self, min: Vec3, max: Vec3) {
|
pub fn fit_to_bounds(&mut self, min: Vec3, max: Vec3, aspect: f32) {
|
||||||
self.target = ((min + max) * 0.5).as_dvec3();
|
self.target = ((min + max) * 0.5).as_dvec3();
|
||||||
let corners = self.bounds_in_view(min, max);
|
let corners = self.bounds_in_view(min, max);
|
||||||
// Circumscribed radius across the screen plane — aspect-agnostic, so the
|
// Half-height the view needs so the box fits BOTH axes at the given
|
||||||
// content fits whatever the viewport's shape turns out to be.
|
// viewport aspect. The old circumscribed-radius rule fitted the box's
|
||||||
let screen_r = corners
|
// DIAGONAL into the height regardless of shape, which left a wide
|
||||||
|
// drawing filling only a small central patch of the window (#364).
|
||||||
|
let a = aspect.max(0.01);
|
||||||
|
let half_h = corners
|
||||||
.iter()
|
.iter()
|
||||||
.fold(0.0_f32, |m, c| m.max(c.x.hypot(c.y)))
|
.fold(0.0_f32, |m, c| m.max(c.y.abs()).max(c.x.abs() / a))
|
||||||
.max(1e-6);
|
.max(1e-6);
|
||||||
// `ortho_size` (the half-height) is `distance * tan(fov/2)`, so invert
|
// `ortho_size` (the half-height) is `distance * tan(fov/2)`, so invert
|
||||||
// that to get the distance which just contains `screen_r`, plus margin.
|
// that to get the distance which just contains `half_h`, plus a small
|
||||||
self.distance = (screen_r / (self.fov_y * 0.5).tan() * 1.2).max(1e-3);
|
// border margin.
|
||||||
|
self.distance = (half_h / (self.fov_y * 0.5).tan() * 1.1).max(1e-3);
|
||||||
self.fit_depth_to_bounds(min, max);
|
self.fit_depth_to_bounds(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,11 +592,11 @@ mod tests {
|
|||||||
let flat_max = Vec3::new(-1199960.0, -800000.0, 10.0);
|
let flat_max = Vec3::new(-1199960.0, -800000.0, 10.0);
|
||||||
|
|
||||||
let mut flat = Camera::default();
|
let mut flat = Camera::default();
|
||||||
flat.fit_to_bounds(flat_min, flat_max);
|
flat.fit_to_bounds(flat_min, flat_max, 1.0);
|
||||||
|
|
||||||
// Same drawing, plus the benchmark's entity 800 km down.
|
// Same drawing, plus the benchmark's entity 800 km down.
|
||||||
let mut deep = Camera::default();
|
let mut deep = Camera::default();
|
||||||
deep.fit_to_bounds(Vec3::new(flat_min.x, flat_min.y, -800017.5), flat_max);
|
deep.fit_to_bounds(Vec3::new(flat_min.x, flat_min.y, -800017.5), flat_max, 1.0);
|
||||||
|
|
||||||
// Top view: the outlier is along the eye direction, so the on-screen
|
// Top view: the outlier is along the eye direction, so the on-screen
|
||||||
// framing must barely move.
|
// framing must barely move.
|
||||||
@@ -612,7 +616,7 @@ mod tests {
|
|||||||
let min = Vec3::new(-1200100.0, -800081.5, -800017.5);
|
let min = Vec3::new(-1200100.0, -800081.5, -800017.5);
|
||||||
let max = Vec3::new(-1199960.0, -800000.0, 10.0);
|
let max = Vec3::new(-1199960.0, -800000.0, 10.0);
|
||||||
let mut cam = Camera::default();
|
let mut cam = Camera::default();
|
||||||
cam.fit_to_bounds(min, max);
|
cam.fit_to_bounds(min, max, 1.0);
|
||||||
|
|
||||||
let (near, far) = cam.ortho_depth_range();
|
let (near, far) = cam.ortho_depth_range();
|
||||||
// Depth of a point from the eye, along the view direction. Top view, so
|
// Depth of a point from the eye, along the view direction. Top view, so
|
||||||
@@ -633,7 +637,7 @@ mod tests {
|
|||||||
let min = Vec3::new(-70.0, -40.0, 0.0);
|
let min = Vec3::new(-70.0, -40.0, 0.0);
|
||||||
let max = Vec3::new(70.0, 40.0, 0.0);
|
let max = Vec3::new(70.0, 40.0, 0.0);
|
||||||
let mut cam = Camera::default();
|
let mut cam = Camera::default();
|
||||||
cam.fit_to_bounds(min, max);
|
cam.fit_to_bounds(min, max, 1.0);
|
||||||
// Old rule: distance = 3-D diagonal * 1.5.
|
// Old rule: distance = 3-D diagonal * 1.5.
|
||||||
let old = (max - min).length() * 1.5;
|
let old = (max - min).length() * 1.5;
|
||||||
let ratio = cam.distance / old;
|
let ratio = cam.distance / old;
|
||||||
|
|||||||
Reference in new issue
Block a user