Merge pull request #1853 from gianlucafiore/fix/extension-guide-visual-anchor

fix(osnap): keep Extension guides anchored when moving the cursor
This commit is contained in:
gianlucafiore authored and GitHub committed 2026-10-16 16:16:29 -03:00
commit 590ca17f94
3 files changed
+239 -85

No files matched your search

+62 -8
View File
@@ -914,6 +914,60 @@ bg={bg_ms:.1}ms n={view_count}"
ob.y + (1.0 - ndc.y) * 0.5 * ob.height,
)
};
// Project a *ray* using a screen-space baseline long enough to
// survive f32 pixel rounding. Projecting base + one world unit can
// produce a subpixel delta at low zoom: normalizing that delta in
// the overlay magnifies the rounding error into a visibly wandering
// guide, even when the acquired world-space ray is fixed.
let ost_project_ray = |origin: glam::DVec3,
dir: glam::DVec3,
view_rot: glam::Mat4,
eye: glam::DVec3,
ob: iced::Rectangle|
-> Option<(iced::Point, iced::Point)> {
let b = ost_project(origin, view_rot, eye, ob);
if !b.x.is_finite() || !b.y.is_finite() {
return None;
}
let unit = dir.try_normalize()?;
let mut distance = 1.0_f64;
for _ in 0..24 {
let tip = ost_project(origin + unit * distance, view_rot, eye, ob);
let length = (tip.x - b.x).hypot(tip.y - b.y);
if length.is_finite() && length >= 64.0 {
return Some((b, tip));
}
distance *= 2.0;
}
None
};
// Draw the Extension start at the actual acquired *world* point,
// projected through the same camera/rect as the entity and guides.
// The snap engine's screen-space base is only pane-local and can
// differ from the final canvas coordinates used by the overlay.
// Keep that older value as a fallback for snaps without origin.
let snap_ext_base = tab
.snap_result
.filter(|hit| hit.snap_type == crate::snap::SnapType::Extension)
.and_then(|hit| hit.extension_origin)
.and_then(|origin| {
let (rot, eye, rect) = otrack_proj?;
let pixel = ost_project(origin, rot, eye, rect);
(pixel.x.is_finite() && pixel.y.is_finite()).then_some(pixel)
})
.or(snap_ext_base);
// OSNAP Extension is also active with F11 / OTRACK turned off.
// Give its marker the actual acquired geometric direction instead
// of reconstructing a new ray from the moving snap cursor.
let snap_ext_dir: Option<iced::Point> = tab
.snap_result
.filter(|hit| hit.snap_type == crate::snap::SnapType::Extension)
.and_then(|hit| hit.extension_origin.zip(hit.extension_dir))
.and_then(|(origin, dir)| {
let (rot, eye, rect) = otrack_proj?;
let (base, tip) = ost_project_ray(origin, dir, rot, eye, rect)?;
Some(iced::Point::new(tip.x - base.x, tip.y - base.y))
});
let ost_points: Vec<crate::ui::overlay::OstTrackPoint> =
if let Some((view_rot, eye, ob)) = otrack_proj {
self.snapper
@@ -935,17 +989,16 @@ bg={bg_ms:.1}ms n={view_count}"
// both are drawn — one guide alone hides what the point is (#1313).
let otrack_lines: Vec<(iced::Point, iced::Point)> = match otrack_proj {
Some((view_rot, eye, ob)) => {
let a = ost_project(tab.last_cursor_world, view_rot, eye, ob);
self.otrack_active
.into_iter()
.chain(self.otrack_cross)
.filter_map(|(base, _dir)| {
let b = ost_project(base, view_rot, eye, ob);
(b.x.is_finite()
&& b.y.is_finite()
&& a.x.is_finite()
&& a.y.is_finite())
.then_some((b, a))
.filter_map(|(base, dir)| {
// Draw the acquired geometrical ray, not the line
// from its origin to the moving cursor. The latter
// rotates slightly as the pointer moves away from
// the exact ray, making the Extension guide appear
// to slide across its acquired endpoint.
ost_project_ray(base, dir, view_rot, eye, ob)
})
.collect()
}
@@ -1227,6 +1280,7 @@ bg={bg_ms:.1}ms n={view_count}"
snap_info,
snap_ext_base,
snap_ext_base2,
snap_ext_dir,
grips,
control_polygon,
grip_clip,