fix(viewcube): hover/click hit-test must match the drawn cube under UCS

The hover hit-tests used the bare camera rotation (no UCS) and the main
camera even inside a floating viewport, so the highlighted face diverged
from the cursor whenever a UCS was active or in a paper viewport — while
the snap path was correct. Route hover, click-consume and snap through
active_view_rotation_mat(), and compose the ViewCube UCS in its viewport
branch so all three match ViewportData's drawn cam_rotation exactly.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-24 21:38:51 +03:00
1 parent 2507a8fe55
commit 3a4d842a7b
2 files changed
+36 -18

No files matched your search

+31 -17
View File
@@ -2386,7 +2386,7 @@ impl OpenCADStudio {
Some(rect) => rect,
None => self.tabs[i].scene.active_model_tile_bounds(vw, vh),
};
let cam_rot = self.tabs[i].scene.camera.borrow().view_rotation_mat();
let cam_rot = self.tabs[i].scene.active_view_rotation_mat();
let hover = hover_id(
self.cursor_pos.x - tile.x,
self.cursor_pos.y - tile.y,
@@ -2443,7 +2443,7 @@ impl OpenCADStudio {
Some(rect) => rect,
None => self.tabs[i].scene.active_model_tile_bounds(svw, svh),
};
let cam_rot = self.tabs[i].scene.camera.borrow().view_rotation_mat();
let cam_rot = self.tabs[i].scene.active_view_rotation_mat();
self.tabs[i].scene.viewcube_hover.set(hover_id(
p.x - cube_tile.x,
p.y - cube_tile.y,
@@ -2643,10 +2643,10 @@ impl OpenCADStudio {
}
};
let (view_rot, eye) = match &edit_cam {
Some(cam) => (cam.view_proj_rte(bounds), cam.eye_f64()),
Some(cam) => (cam.view_proj_rte(bounds), cam.eye()),
None => {
let cam = self.tabs[i].scene.camera.borrow();
(cam.view_proj_rte(bounds), cam.eye_f64())
(cam.view_proj_rte(bounds), cam.eye())
}
};
@@ -2781,10 +2781,10 @@ impl OpenCADStudio {
// main model view — no paper projection, tracks pan/zoom/twist.
let cursor_world = self.cursor_model_point(i, &edit_cam, p, bounds);
let (view_rot, eye) = match &edit_cam {
Some(cam) => (cam.view_proj_rte(bounds), cam.eye_f64()),
Some(cam) => (cam.view_proj_rte(bounds), cam.eye()),
None => {
let cam = self.tabs[i].scene.camera.borrow();
(cam.view_proj_rte(bounds), cam.eye_f64())
(cam.view_proj_rte(bounds), cam.eye())
}
};
// Sync grid-snap spacing to the adaptive spacing of the visible grid.
@@ -3217,9 +3217,23 @@ impl OpenCADStudio {
let (vw, vh) = vp_size;
if vw > 1.0 && vh > 1.0 {
let rot = self.tabs[i].scene.camera.borrow().view_rotation_mat()
* self.tabs[i].scene.viewcube_ucs_mat();
if scene::hit_test(p.x, p.y, vw, vh, rot, VIEWCUBE_PX).is_some() {
let rot = self.tabs[i].scene.active_view_rotation_mat();
// Map the cursor into whichever area owns the cube (active
// viewport rect in paper, active tile in model) so the
// consume-check lines up with the gizmo — same framing as
// ViewportClick's snap hit-test.
let (cx, cy, w, h) = match self.tabs[i]
.scene
.active_viewport
.and_then(|hndl| self.tabs[i].scene.viewport_screen_rect(hndl, (vw, vh)))
{
Some(rect) => (p.x - rect.x, p.y - rect.y, rect.width, rect.height),
None => {
let tb = self.tabs[i].scene.active_model_tile_bounds(vw, vh);
(p.x - tb.x, p.y - tb.y, tb.width, tb.height)
}
};
if scene::hit_test(cx, cy, w, h, rot, VIEWCUBE_PX).is_some() {
return Task::none();
}
}
@@ -3306,7 +3320,7 @@ impl OpenCADStudio {
p,
&self.tabs[i].selected_grips,
cam.view_proj_rte(bounds),
cam.eye_f64(),
cam.eye(),
bounds,
)
} else if is_paper {
@@ -3478,10 +3492,10 @@ impl OpenCADStudio {
// else paper sheet → model). Model space throughout.
let raw = self.cursor_model_point(i, &edit_cam, p, bounds);
let (view_rot, eye) = match &edit_cam {
Some(cam) => (cam.view_proj_rte(bounds), cam.eye_f64()),
Some(cam) => (cam.view_proj_rte(bounds), cam.eye()),
None => {
let c = self.tabs[i].scene.camera.borrow();
(c.view_proj_rte(bounds), c.eye_f64())
(c.view_proj_rte(bounds), c.eye())
}
};
let snap_cursor = raw;
@@ -4115,7 +4129,7 @@ impl OpenCADStudio {
width: vw,
height: vh,
};
let (view_rot, eye) = { let c = self.tabs[i].scene.camera.borrow(); (c.view_proj_rte(bounds), c.eye_f64()) };
let (view_rot, eye) = { let c = self.tabs[i].scene.camera.borrow(); (c.view_proj_rte(bounds), c.eye()) };
let all_wires = self.tabs[i].scene.hit_test_wires();
// Resolve the double-clicked object — its wire, or (for a
// block/solid with no wire under the cursor) its shaded
@@ -4187,7 +4201,7 @@ impl OpenCADStudio {
// 1) Try direct wire hit — works when the border is clicked.
let hit_vp: Option<acadrust::Handle> = {
let (view_rot, eye) = { let c = self.tabs[i].scene.camera.borrow(); (c.view_proj_rte(bounds), c.eye_f64()) };
let (view_rot, eye) = { let c = self.tabs[i].scene.camera.borrow(); (c.view_proj_rte(bounds), c.eye()) };
let all_wires = self.tabs[i].scene.hit_test_wires();
scene::pick::hit_test::click_hit(p, &all_wires[..], view_rot, eye, bounds)
.and_then(|s| Scene::handle_from_wire_name(s))
@@ -8368,12 +8382,12 @@ impl OpenCADStudio {
Some(h) => self.tabs[i].scene.model_wires_for_viewport_arc(h, bounds.height),
None => self.tabs[i].scene.hit_test_wires(),
};
(cam.view_proj_rte(bounds), cam.eye_f64(), wires)
(cam.view_proj_rte(bounds), cam.eye(), wires)
}
None => {
let (view_rot, eye) = {
let c = self.tabs[i].scene.camera.borrow();
(c.view_proj_rte(bounds), c.eye_f64())
(c.view_proj_rte(bounds), c.eye())
};
(view_rot, eye, self.tabs[i].scene.hit_test_wires())
}
@@ -8419,7 +8433,7 @@ impl OpenCADStudio {
p_local,
&self.tabs[i].selected_grips,
cam.view_proj_rte(local),
cam.eye_f64(),
cam.eye(),
local,
)
} else if is_paper {
+5 -1
View File
@@ -3906,9 +3906,13 @@ impl Scene {
/// 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 {
// Must match exactly what the drawn cube uses (see ViewportData's
// `cam_rotation`): the active context's camera composed with the
// ViewCube UCS. Inside a floating viewport that's the viewport's own
// camera; the UCS factor applies in both cases.
if let Some(h) = self.active_viewport {
if let Some(cam) = self.camera_for_viewport(h) {
return cam.view_rotation_mat();
return cam.view_rotation_mat() * self.viewcube_ucs_mat();
}
}
self.camera.borrow().view_rotation_mat() * self.viewcube_ucs_mat()