refactor(trim): find an extension's boundary with the kernel
EXTEND asked where a line reaches by matching over every Geo variant and running its own line-line, line-circle and line-ellipse test — twice over, once for a LINE and once for a polyline's terminal segment, about two hundred and sixty lines between them. A boundary spline was walked as sixty-four chords there rather than intersected, so an extend landed on the chord nearest the spline instead of on the spline. Both are one call now: shoot the segment's own infinite line and take the nearest crossing on the side being extended. That is `cut_params`, which every other cut in the file already goes through. With those two gone, the sampled copy carried beside each boundary spline had no remaining reader, so a spline boundary is now only ever the exact NURBS. The line-ellipse adapter lost its last caller with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ca85856f52
commit
f3c2e061e0
2 changed files with 50 additions and 324 deletions
|
|
@ -15,7 +15,7 @@ use acadrust::kernel::geom2d::{self, Ellipse};
|
||||||
|
|
||||||
/// Re-exported unchanged: these already speak in plain `f64`, so there is no
|
/// Re-exported unchanged: these already speak in plain `f64`, so there is no
|
||||||
/// call-shape difference for this module to absorb.
|
/// call-shape difference for this module to absorb.
|
||||||
pub use acadrust::kernel::geom2d::{angle_within_arc, arc_parameter, lerp, normalize_angle};
|
pub use acadrust::kernel::geom2d::{arc_parameter, lerp, normalize_angle};
|
||||||
|
|
||||||
/// Preview geometry keeps the density the commands have always used.
|
/// Preview geometry keeps the density the commands have always used.
|
||||||
const SEGMENTS_PER_RADIAN: f64 = geom2d::DEFAULT_SEGMENTS_PER_RADIAN;
|
const SEGMENTS_PER_RADIAN: f64 = geom2d::DEFAULT_SEGMENTS_PER_RADIAN;
|
||||||
|
|
@ -51,35 +51,6 @@ pub fn line_circle(px: f64, py: f64, dx: f64, dy: f64, cx: f64, cy: f64, r: f64)
|
||||||
geom2d::line_circle([px, py], [dx, dy], [cx, cy], r)
|
geom2d::line_circle([px, py], [dx, dy], [cx, cy], r)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `(s_on_line, t_on_ellipse)` pairs where a line meets an ellipse.
|
|
||||||
///
|
|
||||||
/// `nx, ny` is the unit major axis.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn line_ellipse(
|
|
||||||
px: f64,
|
|
||||||
py: f64,
|
|
||||||
dx: f64,
|
|
||||||
dy: f64,
|
|
||||||
cx: f64,
|
|
||||||
cy: f64,
|
|
||||||
a: f64,
|
|
||||||
b: f64,
|
|
||||||
nx: f64,
|
|
||||||
ny: f64,
|
|
||||||
) -> Vec<(f64, f64)> {
|
|
||||||
geom2d::line_ellipse(
|
|
||||||
[px, py],
|
|
||||||
[dx, dy],
|
|
||||||
&Ellipse {
|
|
||||||
centre: [cx, cy],
|
|
||||||
major_radius: a,
|
|
||||||
minor_radius: b,
|
|
||||||
major_axis: [nx, ny],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The two endpoints of a straight segment, as render vertices.
|
|
||||||
pub fn line_points(start: [f64; 3], end: [f64; 3]) -> Vec<[f32; 3]> {
|
pub fn line_points(start: [f64; 3], end: [f64; 3]) -> Vec<[f32; 3]> {
|
||||||
narrow(vec![start, end])
|
narrow(vec![start, end])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,8 @@ use std::f64::consts::TAU;
|
||||||
// its call shapes to the loose scalars and f32 render vertices used here.
|
// its call shapes to the loose scalars and f32 render vertices used here.
|
||||||
use super::geom;
|
use super::geom;
|
||||||
use super::geom::{
|
use super::geom::{
|
||||||
angle_within_arc as in_arc, arc_parameter as arc_t, arc_points as arc_pts,
|
arc_parameter as arc_t, arc_points as arc_pts, ellipse_points as ellipse_pts,
|
||||||
ellipse_points as ellipse_pts, lerp as lerp2,
|
lerp as lerp2, normalize_angle as norm,
|
||||||
line_circle as lc, line_ellipse as le, line_line as ll, normalize_angle as norm,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::modules::draw::fence::{crossing_box_preview, FencePick};
|
use crate::modules::draw::fence::{crossing_box_preview, FencePick};
|
||||||
|
|
@ -131,14 +130,9 @@ enum Geo {
|
||||||
t1: f64, // end parameter (may be > 2π if wrapped)
|
t1: f64, // end parameter (may be > 2π if wrapped)
|
||||||
},
|
},
|
||||||
/// A NURBS boundary, carried exactly.
|
/// A NURBS boundary, carried exactly.
|
||||||
///
|
|
||||||
/// `segs` is a sampling of the same curve, kept only for the two
|
|
||||||
/// hand-rolled ray walks in the polyline-extend paths. Every crossing
|
|
||||||
/// that decides a trim uses `curve`.
|
|
||||||
Spline {
|
Spline {
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
curve: Box<NurbsCurve>,
|
curve: Box<NurbsCurve>,
|
||||||
segs: Vec<([f64; 2], [f64; 2])>,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,17 +244,10 @@ fn geo_from_entity(h: Handle, e: &EntityType) -> Option<Geo> {
|
||||||
t1,
|
t1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Curve::Nurbs(curve) => {
|
Curve::Nurbs(curve) => Some(Geo::Spline {
|
||||||
// Carried exactly. The sampled polyline beside it is only for the
|
handle: h,
|
||||||
// two hand-rolled ray walks in the polyline-extend paths; every
|
curve: Box::new(curve),
|
||||||
// crossing that decides a trim goes through the curve itself.
|
}),
|
||||||
let sampled = curve.tessellate(16);
|
|
||||||
(sampled.len() >= 2).then(|| Geo::Spline {
|
|
||||||
handle: h,
|
|
||||||
curve: Box::new(curve),
|
|
||||||
segs: sampled.windows(2).map(|w| (w[0], w[1])).collect(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// A polyline reaches here already taken apart into its segments.
|
// A polyline reaches here already taken apart into its segments.
|
||||||
Curve::Polyline(_) => None,
|
Curve::Polyline(_) => None,
|
||||||
}
|
}
|
||||||
|
|
@ -987,6 +974,47 @@ fn trim_lwpolyline(poly: &LwPolyline, cx: f64, cy: f64, geos: &[Geo]) -> Option<
|
||||||
|
|
||||||
/// Extend the first or last segment of an LwPolyline to the nearest boundary.
|
/// Extend the first or last segment of an LwPolyline to the nearest boundary.
|
||||||
/// Click point (DXF XY) determines which end to extend.
|
/// Click point (DXF XY) determines which end to extend.
|
||||||
|
/// The nearest boundary crossing past one end of a segment.
|
||||||
|
///
|
||||||
|
/// `t` runs on the infinite line through `from` → `to`: zero at `from`, one
|
||||||
|
/// at `to`. Extending the far end wants the smallest `t` above one; extending
|
||||||
|
/// the near end wants the largest `t` below zero. `None` when nothing lies
|
||||||
|
/// that way.
|
||||||
|
///
|
||||||
|
/// The crossings come from `cut_params`, which is the same kernel call every
|
||||||
|
/// other cut in this file uses. What was here instead was a match over every
|
||||||
|
/// `Geo` variant with its own line-line, line-circle and line-ellipse test —
|
||||||
|
/// twice, once for a line and once for a polyline's terminal segment — and a
|
||||||
|
/// boundary spline was walked as sixty-four chords rather than intersected.
|
||||||
|
fn extension_hit(
|
||||||
|
from: [f64; 2],
|
||||||
|
to: [f64; 2],
|
||||||
|
handle: Handle,
|
||||||
|
geos: &[Geo],
|
||||||
|
beyond_end: bool,
|
||||||
|
) -> Option<f64> {
|
||||||
|
let direction = [to[0] - from[0], to[1] - from[1]];
|
||||||
|
if direction[0].hypot(direction[1]) < 1e-9 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// An infinite line rather than the segment: the crossing being looked for
|
||||||
|
// is by definition off the end of the drawn part.
|
||||||
|
let ray = Curve::XLine(KernelXLine {
|
||||||
|
base: from,
|
||||||
|
direction,
|
||||||
|
});
|
||||||
|
let hits = cut_params(&ray, handle, geos);
|
||||||
|
if beyond_end {
|
||||||
|
hits.into_iter()
|
||||||
|
.filter(|t| *t > 1.0 + 1e-6)
|
||||||
|
.min_by(f64::total_cmp)
|
||||||
|
} else {
|
||||||
|
hits.into_iter()
|
||||||
|
.filter(|t| *t < -1e-6)
|
||||||
|
.max_by(f64::total_cmp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn extend_lwpoly(
|
fn extend_lwpoly(
|
||||||
poly: &LwPolyline,
|
poly: &LwPolyline,
|
||||||
click_x: f64,
|
click_x: f64,
|
||||||
|
|
@ -1030,124 +1058,7 @@ fn extend_lwpoly(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// t_click on the segment: 0 = ax/ay, 1 = bx/by. We're extending beyond t=1.
|
let best_t = extension_hit([ax, ay], [bx, by], poly.common.handle, geos, true)?;
|
||||||
let target = poly.common.handle;
|
|
||||||
let mut best_t = f64::INFINITY;
|
|
||||||
|
|
||||||
for geo in geos {
|
|
||||||
match geo {
|
|
||||||
Geo::Line { handle, p1, p2 } => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let (ex, ey) = (p2[0] - p1[0], p2[1] - p1[1]);
|
|
||||||
if let Some((t, u)) = ll(ax, ay, dx, dy, p1[0], p1[1], ex, ey) {
|
|
||||||
if (-1e-9..=1.0 + 1e-9).contains(&u) && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Arc {
|
|
||||||
handle,
|
|
||||||
cx,
|
|
||||||
cy,
|
|
||||||
r,
|
|
||||||
a0,
|
|
||||||
a1,
|
|
||||||
} => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for t in lc(ax, ay, dx, dy, *cx, *cy, *r) {
|
|
||||||
let ix = ax + t * dx;
|
|
||||||
let iy = ay + t * dy;
|
|
||||||
if in_arc((iy - cy).atan2(ix - cx), *a0, *a1) && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Circle { handle, cx, cy, r } => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for t in lc(ax, ay, dx, dy, *cx, *cy, *r) {
|
|
||||||
if t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Ray {
|
|
||||||
handle,
|
|
||||||
bx: rbx,
|
|
||||||
by: rby,
|
|
||||||
dx: rdx,
|
|
||||||
dy: rdy,
|
|
||||||
} => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some((t, u)) = ll(ax, ay, dx, dy, *rbx, *rby, *rdx, *rdy) {
|
|
||||||
if u >= -1e-9 && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::InfLine {
|
|
||||||
handle,
|
|
||||||
bx: ibx,
|
|
||||||
by: iby,
|
|
||||||
dx: idx,
|
|
||||||
dy: idy,
|
|
||||||
} => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some((t, _)) = ll(ax, ay, dx, dy, *ibx, *iby, *idx, *idy) {
|
|
||||||
if t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Ellipse {
|
|
||||||
handle,
|
|
||||||
cx,
|
|
||||||
cy,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
nx,
|
|
||||||
ny,
|
|
||||||
t0,
|
|
||||||
t1,
|
|
||||||
} => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (t, t_ell) in le(ax, ay, dx, dy, *cx, *cy, *a, *b, *nx, *ny) {
|
|
||||||
if in_arc(t_ell, *t0, *t1) && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Spline { handle, segs, .. } => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (p1, p2) in segs {
|
|
||||||
let ex = p2[0] - p1[0];
|
|
||||||
let ey = p2[1] - p1[1];
|
|
||||||
if let Some((t, u)) = ll(ax, ay, dx, dy, p1[0], p1[1], ex, ey) {
|
|
||||||
if (-1e-9..=1.0 + 1e-9).contains(&u) && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !best_t.is_finite() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_x = ax + best_t * dx;
|
let new_x = ax + best_t * dx;
|
||||||
let new_y = ay + best_t * dy;
|
let new_y = ay + best_t * dy;
|
||||||
|
|
@ -1173,165 +1084,9 @@ fn extend_line(orig: &LineEnt, t_click: f64, geos: &[Geo]) -> Option<EntityType>
|
||||||
let bx = orig.end.x;
|
let bx = orig.end.x;
|
||||||
let by = orig.end.y;
|
let by = orig.end.y;
|
||||||
let (dx, dy) = (bx - ax, by - ay);
|
let (dx, dy) = (bx - ax, by - ay);
|
||||||
let target = orig.common.handle;
|
|
||||||
let extend_end = t_click >= 0.5;
|
let extend_end = t_click >= 0.5;
|
||||||
|
let best_t = extension_hit([ax, ay], [bx, by], orig.common.handle, geos, extend_end)?;
|
||||||
|
|
||||||
let mut best_t = if extend_end {
|
|
||||||
f64::INFINITY
|
|
||||||
} else {
|
|
||||||
f64::NEG_INFINITY
|
|
||||||
};
|
|
||||||
|
|
||||||
for geo in geos {
|
|
||||||
match geo {
|
|
||||||
Geo::Line { handle, p1, p2 } => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let (ex, ey) = (p2[0] - p1[0], p2[1] - p1[1]);
|
|
||||||
if let Some((t, u)) = ll(ax, ay, dx, dy, p1[0], p1[1], ex, ey) {
|
|
||||||
if !(-1e-9..=1.0 + 1e-9).contains(&u) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if extend_end && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
if !extend_end && t < -1e-6 && t > best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Arc {
|
|
||||||
handle,
|
|
||||||
cx,
|
|
||||||
cy,
|
|
||||||
r,
|
|
||||||
a0,
|
|
||||||
a1,
|
|
||||||
} => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for t in lc(ax, ay, dx, dy, *cx, *cy, *r) {
|
|
||||||
let ix = ax + t * dx;
|
|
||||||
let iy = ay + t * dy;
|
|
||||||
if !in_arc((iy - cy).atan2(ix - cx), *a0, *a1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if extend_end && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
if !extend_end && t < -1e-6 && t > best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Circle { handle, cx, cy, r } => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for t in lc(ax, ay, dx, dy, *cx, *cy, *r) {
|
|
||||||
if extend_end && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
if !extend_end && t < -1e-6 && t > best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Ray {
|
|
||||||
handle,
|
|
||||||
bx: rbx,
|
|
||||||
by: rby,
|
|
||||||
dx: rdx,
|
|
||||||
dy: rdy,
|
|
||||||
} => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some((t, u)) = ll(ax, ay, dx, dy, *rbx, *rby, *rdx, *rdy) {
|
|
||||||
if u >= -1e-9 {
|
|
||||||
// only forward along the Ray
|
|
||||||
if extend_end && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
if !extend_end && t < -1e-6 && t > best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::InfLine {
|
|
||||||
handle,
|
|
||||||
bx: ibx,
|
|
||||||
by: iby,
|
|
||||||
dx: idx,
|
|
||||||
dy: idy,
|
|
||||||
} => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some((t, _u)) = ll(ax, ay, dx, dy, *ibx, *iby, *idx, *idy) {
|
|
||||||
if extend_end && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
if !extend_end && t < -1e-6 && t > best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Ellipse {
|
|
||||||
handle,
|
|
||||||
cx: ecx,
|
|
||||||
cy: ecy,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
nx,
|
|
||||||
ny,
|
|
||||||
t0: et0,
|
|
||||||
t1: et1,
|
|
||||||
} => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (t, t_ell) in le(ax, ay, dx, dy, *ecx, *ecy, *a, *b, *nx, *ny) {
|
|
||||||
if !in_arc(t_ell, *et0, *et1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if extend_end && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
if !extend_end && t < -1e-6 && t > best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geo::Spline { handle, segs, .. } => {
|
|
||||||
if *handle == target {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (p1, p2) in segs {
|
|
||||||
let ex = p2[0] - p1[0];
|
|
||||||
let ey = p2[1] - p1[1];
|
|
||||||
if let Some((t, u)) = ll(ax, ay, dx, dy, p1[0], p1[1], ex, ey) {
|
|
||||||
if !(-1e-9..=1.0 + 1e-9).contains(&u) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if extend_end && t > 1.0 + 1e-6 && t < best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
if !extend_end && t < -1e-6 && t > best_t {
|
|
||||||
best_t = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !best_t.is_finite() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let mut line = orig.clone();
|
let mut line = orig.clone();
|
||||||
line.common.handle = Handle::NULL;
|
line.common.handle = Handle::NULL;
|
||||||
let new_x = ax + best_t * dx;
|
let new_x = ax + best_t * dx;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue