From 2c5405aa49d97e123e8e82ee5b43143a9152cc15 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 23 Apr 2026 09:10:55 +0300 Subject: [PATCH] fix: correct MSPACE orbit and pan direction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/scene/mod.rs | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 83ff7b7ff..c52d93af0 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -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; } }