fix(viewcube): snap a paper viewport square to the UCS, not WCS
Clicking the ViewCube inside a floating viewport set only view_direction, so the rebuilt camera fell back to WCS-up and the view opened tilted relative to the UCS-oriented cube the user clicked. Build the full UCS-aligned orientation (snap_to_direction with the ViewCube UCS) and decode it to the viewport's stored (view_direction, twist_angle). twist can't come from -Camera::roll(): camera_from_view rebuilds rotation with its own yaw convention (atan2(x,-y)) and roll = -twist, which differs from the camera's internal sync_yaw_pitch. Instead reproduce the decoder's zero-twist basis and store twist = -signed_angle(up0 -> desired_up). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3a4d842a7b
commit
7f36cf512f
2 changed files with 33 additions and 6 deletions
|
|
@ -4433,7 +4433,7 @@ impl OpenCADStudio {
|
||||||
if self.tabs[i].scene.active_viewport.is_some() {
|
if self.tabs[i].scene.active_viewport.is_some() {
|
||||||
self.tabs[i]
|
self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.snap_active_viewport_to_direction(eye_dir);
|
.snap_active_viewport_to_direction(eye_dir, r_ucs);
|
||||||
} else {
|
} else {
|
||||||
let mut cam = self.tabs[i].scene.camera.borrow_mut();
|
let mut cam = self.tabs[i].scene.camera.borrow_mut();
|
||||||
cam.snap_to_direction(eye_dir, r_ucs);
|
cam.snap_to_direction(eye_dir, r_ucs);
|
||||||
|
|
|
||||||
|
|
@ -3832,19 +3832,46 @@ impl Scene {
|
||||||
/// vector from target toward camera). Twist angle is left at its
|
/// vector from target toward camera). Twist angle is left at its
|
||||||
/// current value so the up-sense is preserved across successive
|
/// current value so the up-sense is preserved across successive
|
||||||
/// snaps. No-op when there is no active viewport or it is locked.
|
/// snaps. No-op when there is no active viewport or it is locked.
|
||||||
pub fn snap_active_viewport_to_direction(&mut self, eye_dir: glam::Vec3) {
|
pub fn snap_active_viewport_to_direction(&mut self, eye_dir: glam::Vec3, ucs: glam::Mat4) {
|
||||||
let vp_handle = match self.active_viewport {
|
let vp_handle = match self.active_viewport {
|
||||||
Some(h) => h,
|
Some(h) => h,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
let eye = eye_dir.normalize_or(glam::Vec3::Z);
|
// Build the full UCS-aligned orientation exactly as the model snap does
|
||||||
|
// (snap_to_direction picks the in-plane roll from the UCS axes), seeded
|
||||||
|
// from the viewport's current camera so the "best up" stays stable, then
|
||||||
|
// decode it back to the stored (view_direction, twist_angle). Writing
|
||||||
|
// only view_direction loses the roll and the rebuilt camera snaps to
|
||||||
|
// WCS-up instead of the UCS the clicked cube was drawn in.
|
||||||
|
let mut tmp = self.camera_for_viewport(vp_handle).unwrap_or_default();
|
||||||
|
tmp.snap_to_direction(eye_dir, ucs);
|
||||||
|
let dir = (tmp.rotation * glam::Vec3::Z).normalize_or(glam::Vec3::Z);
|
||||||
|
let desired_up = (tmp.rotation * glam::Vec3::Y).normalize_or(glam::Vec3::Y);
|
||||||
|
|
||||||
|
// camera_from_view rebuilds the rotation with its OWN yaw convention
|
||||||
|
// (atan2(x, -y)) and applies roll = -twist, which is *not* the camera's
|
||||||
|
// internal yaw/roll convention — so `-tmp.roll()` does not round-trip.
|
||||||
|
// Instead reproduce the decoder's zero-twist basis here, then measure
|
||||||
|
// the signed roll about the view axis that carries its up onto the
|
||||||
|
// desired UCS up. Store twist = -roll (the decoder negates it back).
|
||||||
|
let pitch = dir.z.clamp(-1.0, 1.0).asin();
|
||||||
|
let yaw = if dir.x.abs() < 1e-6 && dir.y.abs() < 1e-6 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
dir.x.atan2(-dir.y)
|
||||||
|
};
|
||||||
|
let up0 = (view::camera::yaw_pitch_to_quat(yaw, pitch, 0.0) * glam::Vec3::Y)
|
||||||
|
.normalize_or(glam::Vec3::Y);
|
||||||
|
let roll = up0.cross(desired_up).dot(dir).atan2(up0.dot(desired_up));
|
||||||
|
let twist = -roll as f64;
|
||||||
if let Some(acadrust::EntityType::Viewport(vp)) = self.document.get_entity_mut(vp_handle) {
|
if let Some(acadrust::EntityType::Viewport(vp)) = self.document.get_entity_mut(vp_handle) {
|
||||||
if vp.status.locked {
|
if vp.status.locked {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vp.view_direction.x = eye.x as f64;
|
vp.view_direction.x = dir.x as f64;
|
||||||
vp.view_direction.y = eye.y as f64;
|
vp.view_direction.y = dir.y as f64;
|
||||||
vp.view_direction.z = eye.z as f64;
|
vp.view_direction.z = dir.z as f64;
|
||||||
|
vp.twist_angle = twist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue