diff --git a/src/app/update.rs b/src/app/update.rs index ba72e71e5..4378f1911 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -1062,9 +1062,19 @@ impl H7CAD { if sel.right_dragging { if let Some(last) = sel.right_last_pos { let (dx, dy) = (p.x - last.x, p.y - last.y); - self.tabs[i].scene.camera.borrow_mut().orbit(dx, dy); + if self.tabs[i].scene.active_viewport.is_some() { + // Update position before dropping the borrow. + sel.right_last_pos = Some(p); + drop(sel); + self.tabs[i].scene.orbit_active_viewport(dx, dy); + return Task::none(); + } else { + self.tabs[i].scene.camera.borrow_mut().orbit(dx, dy); + sel.right_last_pos = Some(p); + } + } else { + sel.right_last_pos = Some(p); } - sel.right_last_pos = Some(p); } } @@ -1803,10 +1813,10 @@ impl H7CAD { Message::ViewportClick => { let i = self.active_tab; - let cam = self.tabs[i].scene.camera.borrow(); + let rot = self.tabs[i].scene.active_view_rotation_mat(); let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size; if let Some(region) = scene::hit_test( - self.cursor_pos.x, self.cursor_pos.y, vw, vh, cam.view_rotation_mat(), VIEWCUBE_PX, + self.cursor_pos.x, self.cursor_pos.y, vw, vh, rot, VIEWCUBE_PX, ) { return Task::done(Message::ViewCubeSnap(region)); } @@ -1821,15 +1831,31 @@ impl H7CAD { Message::ViewCubeSnap(region) => { let i = self.active_tab; let mut region = region; - { - let mut cam = self.tabs[i].scene.camera.borrow_mut(); + let (yaw, pitch) = { let (target_yaw, target_pitch) = region.snap_angles(); - if angle_close(cam.yaw, target_yaw, 0.01) - && angle_close(cam.pitch, target_pitch, 0.01) + // Check current orientation to detect "already there → flip to opposite". + let already_there = if let Some((cur_yaw, cur_pitch)) = + self.tabs[i].scene.active_viewport_yaw_pitch() { + angle_close(cur_yaw, target_yaw, 0.01) + && angle_close(cur_pitch, target_pitch, 0.01) + } else if self.tabs[i].scene.active_viewport.is_some() { + false + } else { + let cam = self.tabs[i].scene.camera.borrow(); + angle_close(cam.yaw, target_yaw, 0.01) + && angle_close(cam.pitch, target_pitch, 0.01) + }; + if already_there { region = region.opposite(); } - let (yaw, pitch) = region.snap_angles(); + region.snap_angles() + }; + + if self.tabs[i].scene.active_viewport.is_some() { + self.tabs[i].scene.snap_active_viewport_to_angles(yaw, pitch); + } else { + let mut cam = self.tabs[i].scene.camera.borrow_mut(); cam.snap_to_angles(yaw, pitch); } self.tabs[i].scene.camera_generation += 1; diff --git a/src/scene/mod.rs b/src/scene/mod.rs index b48dd1029..83ff7b7ff 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -976,6 +976,72 @@ impl Scene { } } + /// Orbit the active viewport's view direction by the given screen-pixel delta. + /// No-op when there is no active viewport or it is locked. + pub fn orbit_active_viewport(&mut self, delta_x: f32, delta_y: f32) { + let vp_handle = match self.active_viewport { + Some(h) => h, + None => return, + }; + let mut cam = match self.camera_for_viewport(vp_handle) { + Some(c) => c, + None => return, + }; + cam.orbit(delta_x, delta_y); + // Write the new view direction back to the viewport entity. + let eye = cam.rotation * glam::Vec3::Z; + if let Some(acadrust::EntityType::Viewport(vp)) = + self.document.get_entity_mut(vp_handle) + { + if vp.status.locked { + return; + } + vp.view_direction.x = eye.x as f64; + vp.view_direction.y = eye.y as f64; + vp.view_direction.z = eye.z as f64; + } + } + + /// Snap the active viewport's view direction to a canonical yaw/pitch. + /// No-op when there is no active viewport or it is locked. + pub fn snap_active_viewport_to_angles(&mut self, yaw: f32, pitch: f32) { + let vp_handle = match self.active_viewport { + Some(h) => h, + None => return, + }; + let cos_p = pitch.cos(); + let eye = glam::Vec3::new(cos_p * yaw.sin(), cos_p * yaw.cos(), pitch.sin()); + if let Some(acadrust::EntityType::Viewport(vp)) = + self.document.get_entity_mut(vp_handle) + { + if vp.status.locked { + return; + } + vp.view_direction.x = eye.x as f64; + vp.view_direction.y = eye.y as f64; + vp.view_direction.z = eye.z as f64; + } + } + + /// View-rotation matrix for the active viewport (MSPACE), or the + /// paper-space camera's matrix when not in MSPACE. + /// Used by ViewCube hit-testing so clicks map to the correct camera. + pub fn active_view_rotation_mat(&self) -> glam::Mat4 { + if let Some(h) = self.active_viewport { + if let Some(cam) = self.camera_for_viewport(h) { + return cam.view_rotation_mat(); + } + } + self.camera.borrow().view_rotation_mat() + } + + /// Return (yaw, pitch) of the active viewport's camera, or None if PSPACE. + pub fn active_viewport_yaw_pitch(&self) -> Option<(f32, f32)> { + let h = self.active_viewport?; + let cam = self.camera_for_viewport(h)?; + Some((cam.yaw, cam.pitch)) + } + /// Return the handle of the user viewport whose bounding rectangle contains /// the given paper-space point, or `None` if no viewport matches. pub fn viewport_at_paper_point(&self, px: f32, py: f32) -> Option {