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 <noreply@anthropic.com>
This commit is contained in:
parent
2507a8fe55
commit
3a4d842a7b
2 changed files with 36 additions and 18 deletions
|
|
@ -2386,7 +2386,7 @@ impl OpenCADStudio {
|
||||||
Some(rect) => rect,
|
Some(rect) => rect,
|
||||||
None => self.tabs[i].scene.active_model_tile_bounds(vw, vh),
|
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(
|
let hover = hover_id(
|
||||||
self.cursor_pos.x - tile.x,
|
self.cursor_pos.x - tile.x,
|
||||||
self.cursor_pos.y - tile.y,
|
self.cursor_pos.y - tile.y,
|
||||||
|
|
@ -2443,7 +2443,7 @@ impl OpenCADStudio {
|
||||||
Some(rect) => rect,
|
Some(rect) => rect,
|
||||||
None => self.tabs[i].scene.active_model_tile_bounds(svw, svh),
|
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(
|
self.tabs[i].scene.viewcube_hover.set(hover_id(
|
||||||
p.x - cube_tile.x,
|
p.x - cube_tile.x,
|
||||||
p.y - cube_tile.y,
|
p.y - cube_tile.y,
|
||||||
|
|
@ -2643,10 +2643,10 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let (view_rot, eye) = match &edit_cam {
|
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 => {
|
None => {
|
||||||
let cam = self.tabs[i].scene.camera.borrow();
|
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.
|
// main model view — no paper projection, tracks pan/zoom/twist.
|
||||||
let cursor_world = self.cursor_model_point(i, &edit_cam, p, bounds);
|
let cursor_world = self.cursor_model_point(i, &edit_cam, p, bounds);
|
||||||
let (view_rot, eye) = match &edit_cam {
|
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 => {
|
None => {
|
||||||
let cam = self.tabs[i].scene.camera.borrow();
|
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.
|
// Sync grid-snap spacing to the adaptive spacing of the visible grid.
|
||||||
|
|
@ -3217,9 +3217,23 @@ impl OpenCADStudio {
|
||||||
let (vw, vh) = vp_size;
|
let (vw, vh) = vp_size;
|
||||||
|
|
||||||
if vw > 1.0 && vh > 1.0 {
|
if vw > 1.0 && vh > 1.0 {
|
||||||
let rot = self.tabs[i].scene.camera.borrow().view_rotation_mat()
|
let rot = self.tabs[i].scene.active_view_rotation_mat();
|
||||||
* self.tabs[i].scene.viewcube_ucs_mat();
|
// Map the cursor into whichever area owns the cube (active
|
||||||
if scene::hit_test(p.x, p.y, vw, vh, rot, VIEWCUBE_PX).is_some() {
|
// 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();
|
return Task::none();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3306,7 +3320,7 @@ impl OpenCADStudio {
|
||||||
p,
|
p,
|
||||||
&self.tabs[i].selected_grips,
|
&self.tabs[i].selected_grips,
|
||||||
cam.view_proj_rte(bounds),
|
cam.view_proj_rte(bounds),
|
||||||
cam.eye_f64(),
|
cam.eye(),
|
||||||
bounds,
|
bounds,
|
||||||
)
|
)
|
||||||
} else if is_paper {
|
} else if is_paper {
|
||||||
|
|
@ -3478,10 +3492,10 @@ impl OpenCADStudio {
|
||||||
// else paper sheet → model). Model space throughout.
|
// else paper sheet → model). Model space throughout.
|
||||||
let raw = self.cursor_model_point(i, &edit_cam, p, bounds);
|
let raw = self.cursor_model_point(i, &edit_cam, p, bounds);
|
||||||
let (view_rot, eye) = match &edit_cam {
|
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 => {
|
None => {
|
||||||
let c = self.tabs[i].scene.camera.borrow();
|
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;
|
let snap_cursor = raw;
|
||||||
|
|
@ -4115,7 +4129,7 @@ impl OpenCADStudio {
|
||||||
width: vw,
|
width: vw,
|
||||||
height: vh,
|
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();
|
let all_wires = self.tabs[i].scene.hit_test_wires();
|
||||||
// Resolve the double-clicked object — its wire, or (for a
|
// Resolve the double-clicked object — its wire, or (for a
|
||||||
// block/solid with no wire under the cursor) its shaded
|
// 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.
|
// 1) Try direct wire hit — works when the border is clicked.
|
||||||
let hit_vp: Option<acadrust::Handle> = {
|
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();
|
let all_wires = self.tabs[i].scene.hit_test_wires();
|
||||||
scene::pick::hit_test::click_hit(p, &all_wires[..], view_rot, eye, bounds)
|
scene::pick::hit_test::click_hit(p, &all_wires[..], view_rot, eye, bounds)
|
||||||
.and_then(|s| Scene::handle_from_wire_name(s))
|
.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),
|
Some(h) => self.tabs[i].scene.model_wires_for_viewport_arc(h, bounds.height),
|
||||||
None => self.tabs[i].scene.hit_test_wires(),
|
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 => {
|
None => {
|
||||||
let (view_rot, eye) = {
|
let (view_rot, eye) = {
|
||||||
let c = self.tabs[i].scene.camera.borrow();
|
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())
|
(view_rot, eye, self.tabs[i].scene.hit_test_wires())
|
||||||
}
|
}
|
||||||
|
|
@ -8419,7 +8433,7 @@ impl OpenCADStudio {
|
||||||
p_local,
|
p_local,
|
||||||
&self.tabs[i].selected_grips,
|
&self.tabs[i].selected_grips,
|
||||||
cam.view_proj_rte(local),
|
cam.view_proj_rte(local),
|
||||||
cam.eye_f64(),
|
cam.eye(),
|
||||||
local,
|
local,
|
||||||
)
|
)
|
||||||
} else if is_paper {
|
} else if is_paper {
|
||||||
|
|
|
||||||
|
|
@ -3906,9 +3906,13 @@ impl Scene {
|
||||||
/// paper-space camera's matrix when not in MSPACE.
|
/// paper-space camera's matrix when not in MSPACE.
|
||||||
/// Used by ViewCube hit-testing so clicks map to the correct camera.
|
/// Used by ViewCube hit-testing so clicks map to the correct camera.
|
||||||
pub fn active_view_rotation_mat(&self) -> glam::Mat4 {
|
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(h) = self.active_viewport {
|
||||||
if let Some(cam) = self.camera_for_viewport(h) {
|
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()
|
self.camera.borrow().view_rotation_mat() * self.viewcube_ucs_mat()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue