fix: correct MSPACE orbit and pan direction

orbit_active_viewport was writing cam.rotation*Z directly to
view_direction, but yaw_pitch_to_quat(y,p)*Z has its Y component
negated relative to the snap convention: (cos(p)*sin(y), -cos(p)*cos(y),
sin(p)) vs (cos(p)*sin(y), +cos(p)*cos(y), sin(p)).  This caused
camera_for_viewport to reconstruct yaw = π - original_yaw, making every
orbit appear reversed.  Fix: negate Y when writing view_direction back.

pan_active_viewport was using the paper-space camera to convert screen
pixels to model-space delta.  This gives wrong pan axes for tilted
MSPACE views (e.g. front/side views where cam_up is Z, not Y).  Fix:
use camera_for_viewport so the pan axes match the 3-D view orientation,
and update all three view_target components (X/Y/Z) for tilted views.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-04-23 09:10:55 +03:00
commit 2c5405aa49

View file

@ -897,28 +897,23 @@ impl Scene {
Some(h) => h,
None => return,
};
// Convert screen pixels → paper-space delta using the camera.
let cam = self.camera.borrow();
let paper_delta = cam.screen_delta_to_world(screen_dx, screen_dy, bounds);
drop(cam);
// Use the viewport's own camera so that the pan axes match the 3-D view
// orientation (important for tilted/rotated MSPACE views).
// camera_for_viewport already encodes the correct distance/scale via view_height,
// so no additional scale division is needed.
let vp_cam = match self.camera_for_viewport(vp_handle) {
Some(c) => c,
None => return,
};
let model_delta = vp_cam.screen_delta_to_world(screen_dx, screen_dy, bounds);
if let Some(acadrust::EntityType::Viewport(vp)) =
self.document.get_entity_mut(vp_handle)
{
if vp.status.locked { return; }
let scale = if vp.custom_scale.abs() > 1e-9 {
vp.custom_scale
} else if vp.view_height.abs() > 1e-9 {
vp.height / vp.view_height
} else {
1.0
};
if scale.abs() < 1e-12 { return; }
// screen_delta_to_world returns the same delta that cam.pan() ADDS to its
// target, so we add it here too (dividing by viewport scale to convert from
// paper-space to model-space). Using -= would invert the drag direction.
vp.view_target.x += (paper_delta.x / scale as f32) as f64;
vp.view_target.y += (paper_delta.y / scale as f32) as f64;
vp.view_target.x += model_delta.x as f64;
vp.view_target.y += model_delta.y as f64;
vp.view_target.z += model_delta.z as f64;
}
}
@ -988,7 +983,10 @@ impl Scene {
None => return,
};
cam.orbit(delta_x, delta_y);
// Write the new view direction back to the viewport entity.
// yaw_pitch_to_quat(y,p)*Z = (cos(p)*sin(y), -cos(p)*cos(y), sin(p))
// but view_direction convention (matching snap_active_viewport_to_angles) is
// (cos(p)*sin(y), +cos(p)*cos(y), sin(p)) ← Y has opposite sign.
// Negate Y when writing back so camera_for_viewport round-trips correctly.
let eye = cam.rotation * glam::Vec3::Z;
if let Some(acadrust::EntityType::Viewport(vp)) =
self.document.get_entity_mut(vp_handle)
@ -997,7 +995,7 @@ impl Scene {
return;
}
vp.view_direction.x = eye.x as f64;
vp.view_direction.y = eye.y as f64;
vp.view_direction.y = -eye.y as f64;
vp.view_direction.z = eye.z as f64;
}
}