forked from 1b-agent/opencadstudio-upstream
Merge pull request #854 from ramox81/codex/align-arc-behavior
This commit is contained in:
commit
a87ca09fa4
15 files changed
+628
-651
No files matched your search
+111
-61
@@ -1607,7 +1607,7 @@ impl Snapper {
|
||||
let d2 = dist2_to_segment(cursor_screen, sp0, sp1);
|
||||
let t = t_on_segment(cursor_screen, sp0, sp1);
|
||||
let w = Vec3::from(*p1) + t * (Vec3::from(*p2) - Vec3::from(*p1));
|
||||
(w, d2)
|
||||
(w.as_dvec3(), d2)
|
||||
}
|
||||
TangentGeom::Circle { center, radius } => {
|
||||
let cv = Vec3::from(*center);
|
||||
@@ -1621,52 +1621,83 @@ impl Snapper {
|
||||
);
|
||||
let sr = dist2(sc, rim).sqrt();
|
||||
let dc = dist2(cursor_screen, sc).sqrt();
|
||||
// Trigger by proximity to the circle EDGE (hovering
|
||||
// the circle), independent of where the tangent point
|
||||
// lands.
|
||||
let edge_d = (dc - sr).abs();
|
||||
// A TRUE tangent: from the point the command draws
|
||||
// from (P), the tangent point T on the circle has the
|
||||
// radius CT perpendicular to PT, so T lies at the
|
||||
// half-angle acos(r/|CP|) either side of the C→P
|
||||
// direction. Two solutions — take the one nearer the
|
||||
// cursor. Without P (a deferred tangent picked first)
|
||||
// or with P inside the circle, fall back to the circle
|
||||
// point facing the cursor. Previously this always used
|
||||
// the facing point, so Tangent behaved like Nearest
|
||||
// (#274).
|
||||
let w = self
|
||||
.from_point
|
||||
.and_then(|p| circle_tangent_points(p, cv, r))
|
||||
.map(|(t0, t1)| {
|
||||
// Two tangents — take the one nearer the cursor.
|
||||
let s0 = world_to_screen(t0.as_dvec3(), view_rot, eye, bounds);
|
||||
let s1 = world_to_screen(t1.as_dvec3(), view_rot, eye, bounds);
|
||||
if dist2(s0, cursor_screen) <= dist2(s1, cursor_screen) {
|
||||
t0
|
||||
} else {
|
||||
t1
|
||||
}
|
||||
let w = if let Some(from) = self.from_point {
|
||||
let Some((t0, t1)) = circle_tangent_points(from, cv, r) else {
|
||||
continue;
|
||||
};
|
||||
let s0 = world_to_screen(t0.as_dvec3(), view_rot, eye, bounds);
|
||||
let s1 = world_to_screen(t1.as_dvec3(), view_rot, eye, bounds);
|
||||
if dist2(s0, cursor_screen) <= dist2(s1, cursor_screen) {
|
||||
t0
|
||||
} else {
|
||||
t1
|
||||
}
|
||||
} else {
|
||||
let dx = cursor_screen.x - sc.x;
|
||||
let dy = cursor_screen.y - sc.y;
|
||||
let dl = (dx * dx + dy * dy).sqrt();
|
||||
let (nx, ny) = if dl > 1e-6 {
|
||||
(dx / dl, -dy / dl)
|
||||
} else {
|
||||
(1.0, 0.0)
|
||||
};
|
||||
Vec3::new(cv.x + r * nx, cv.y + r * ny, cv.z)
|
||||
};
|
||||
(w.as_dvec3(), edge_d * edge_d)
|
||||
}
|
||||
TangentGeom::Arc {
|
||||
center,
|
||||
axis_x,
|
||||
axis_y,
|
||||
radius,
|
||||
start_angle,
|
||||
end_angle,
|
||||
} => {
|
||||
let Some(from) = self.from_point else {
|
||||
continue;
|
||||
};
|
||||
let Some(world) = arc_tangent_points(
|
||||
from.as_dvec3(),
|
||||
*center,
|
||||
*axis_x,
|
||||
*axis_y,
|
||||
*radius,
|
||||
*start_angle,
|
||||
*end_angle,
|
||||
)
|
||||
.into_iter()
|
||||
.min_by(|a, b| {
|
||||
let sa = world_to_screen(*a, view_rot, eye, bounds);
|
||||
let sb = world_to_screen(*b, view_rot, eye, bounds);
|
||||
dist2(sa, cursor_screen).total_cmp(&dist2(sb, cursor_screen))
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
let edge_d2 = wire
|
||||
.points
|
||||
.windows(2)
|
||||
.enumerate()
|
||||
.filter_map(|(index, _)| {
|
||||
let a = wp_f64(wire, index);
|
||||
let b = wp_f64(wire, index + 1);
|
||||
(a.is_finite() && b.is_finite()).then(|| {
|
||||
dist2_to_segment(
|
||||
cursor_screen,
|
||||
world_to_screen(a, view_rot, eye, bounds),
|
||||
world_to_screen(b, view_rot, eye, bounds),
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let dx = cursor_screen.x - sc.x;
|
||||
let dy = cursor_screen.y - sc.y;
|
||||
let dl = (dx * dx + dy * dy).sqrt();
|
||||
let (nx, ny) = if dl > 1e-6 {
|
||||
(dx / dl, -dy / dl)
|
||||
} else {
|
||||
(1.0, 0.0)
|
||||
};
|
||||
Vec3::new(cv.x + r * nx, cv.y + r * ny, cv.z)
|
||||
});
|
||||
(w, edge_d * edge_d)
|
||||
.fold(f32::INFINITY, f32::min);
|
||||
(world, edge_d2)
|
||||
}
|
||||
};
|
||||
let (tier, sub) = (
|
||||
snap_tier(SnapType::Tangent),
|
||||
snap_priority(SnapType::Tangent),
|
||||
);
|
||||
let screen_pt = world_to_screen(world_pt.as_dvec3(), view_rot, eye, bounds);
|
||||
let screen_pt = world_to_screen(world_pt, view_rot, eye, bounds);
|
||||
if d2 < radius2
|
||||
&& in_bounds(screen_pt)
|
||||
&& snap_better(tier, d2, sub, (best_rank, best_d2, best_sub))
|
||||
@@ -1687,9 +1718,13 @@ impl Snapper {
|
||||
),
|
||||
radius: *radius as f64,
|
||||
},
|
||||
TangentGeom::Arc { center, radius, .. } => TangentObject::Circle {
|
||||
center: glam::DVec3::from_array(*center),
|
||||
radius: *radius,
|
||||
},
|
||||
};
|
||||
best = Some(SnapResult {
|
||||
world: world_pt.as_dvec3(),
|
||||
world: world_pt,
|
||||
screen: screen_pt,
|
||||
snap_type: SnapType::Tangent,
|
||||
tangent_obj: Some(tangent_obj),
|
||||
@@ -2249,30 +2284,45 @@ fn seg_intersect_2d(a0: Point, a1: Point, b0: Point, b1: Point) -> Option<(f32,
|
||||
Some((t, s))
|
||||
}
|
||||
|
||||
/// The two points on a circle (`center`, `radius`, in its XY plane) where a
|
||||
/// line drawn from `p` touches tangentially, or `None` when `p` is inside or on
|
||||
/// the circle (no external tangent). Each tangent point's radius is
|
||||
/// perpendicular to the line from `p`, so it sits at the half-angle
|
||||
/// `acos(radius / |center→p|)` on either side of the center→p direction. This
|
||||
/// is the real Tangent osnap; snapping to the circle point merely facing the
|
||||
/// cursor made Tangent behave like Nearest (#274).
|
||||
/// Returns the two external tangent points on an XY circle.
|
||||
fn circle_tangent_points(p: Vec3, center: Vec3, radius: f32) -> Option<(Vec3, Vec3)> {
|
||||
let vx = p.x - center.x;
|
||||
let vy = p.y - center.y;
|
||||
let d = (vx * vx + vy * vy).sqrt();
|
||||
if d <= radius + 1e-6 {
|
||||
let curve = cadkernel::geom2d::Curve::Circle(cadkernel::geom2d::Circle {
|
||||
centre: [center.x as f64, center.y as f64],
|
||||
radius: radius as f64,
|
||||
});
|
||||
let points = cadkernel::geom2d::tangent_from(&curve, [p.x as f64, p.y as f64]);
|
||||
let [first, second] = points.as_slice() else {
|
||||
return None;
|
||||
}
|
||||
let base = vy.atan2(vx);
|
||||
let off = (radius / d).acos();
|
||||
let at = |a: f32| {
|
||||
Vec3::new(
|
||||
center.x + radius * a.cos(),
|
||||
center.y + radius * a.sin(),
|
||||
center.z,
|
||||
)
|
||||
};
|
||||
Some((at(base + off), at(base - off)))
|
||||
Some((
|
||||
Vec3::new(first.point[0] as f32, first.point[1] as f32, center.z),
|
||||
Vec3::new(second.point[0] as f32, second.point[1] as f32, center.z),
|
||||
))
|
||||
}
|
||||
|
||||
fn arc_tangent_points(
|
||||
from: DVec3,
|
||||
center: [f64; 3],
|
||||
axis_x: [f64; 3],
|
||||
axis_y: [f64; 3],
|
||||
radius: f64,
|
||||
start_angle: f64,
|
||||
end_angle: f64,
|
||||
) -> Vec<DVec3> {
|
||||
let plane = cadkernel::space::Plane::from_axes(center, axis_x, axis_y);
|
||||
let Some(from) = plane.project(from.to_array()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let curve = cadkernel::geom2d::Curve::Arc(cadkernel::geom2d::Arc {
|
||||
centre: [0.0, 0.0],
|
||||
radius,
|
||||
start_angle,
|
||||
end_angle,
|
||||
});
|
||||
cadkernel::geom2d::tangent_from(&curve, from)
|
||||
.into_iter()
|
||||
.map(|point| DVec3::from_array(plane.point_at(point.point)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Snap to the extension of a ray beyond `origin` in `dir` direction.
|
||||
|
||||
Reference in new issue
Block a user