forked from 1b-agent/opencadstudio-upstream
fix: preserve exact mixed polyline intersections
This commit is contained in:
1 parent
fa6bf5e7e4
commit
c5eec2a507
2 files changed
+166
-70
No files matched your search
@@ -3110,6 +3110,75 @@ mod tests {
|
|||||||
assert_eq!(line_wire.world_width, 10.0);
|
assert_eq!(line_wire.world_width, 10.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_mixed_polyline_keeps_exact_line_intersections() {
|
||||||
|
let tangents = vec![
|
||||||
|
TangentGeom::Line {
|
||||||
|
p1: [-10.0, 0.0, 0.0],
|
||||||
|
p2: [0.0, 0.0, 0.0],
|
||||||
|
},
|
||||||
|
TangentGeom::Arc {
|
||||||
|
center: [0.0, 4.0, 0.0],
|
||||||
|
axis_x: [1.0, 0.0, 0.0],
|
||||||
|
axis_y: [0.0, 1.0, 0.0],
|
||||||
|
radius: 4.0,
|
||||||
|
start_angle: -std::f64::consts::FRAC_PI_2,
|
||||||
|
end_angle: std::f64::consts::FRAC_PI_2,
|
||||||
|
},
|
||||||
|
TangentGeom::Line {
|
||||||
|
p1: [0.0, 8.0, 0.0],
|
||||||
|
p2: [10.0, 8.0, 0.0],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let vertices = vec![
|
||||||
|
[-10.0, 0.0, 0.0],
|
||||||
|
[0.0, 0.0, 0.0],
|
||||||
|
[0.0, 8.0, 0.0],
|
||||||
|
[10.0, 8.0, 0.0],
|
||||||
|
];
|
||||||
|
let wires = split_mixed_polyline(
|
||||||
|
&tangents,
|
||||||
|
&vertices,
|
||||||
|
"mixed",
|
||||||
|
[1.0; 4],
|
||||||
|
false,
|
||||||
|
0.0,
|
||||||
|
[0.0; 8],
|
||||||
|
1.0,
|
||||||
|
Vec::new(),
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
&[(0.0, 0.0); 3],
|
||||||
|
0.0,
|
||||||
|
Vec::new(),
|
||||||
|
Vec::new(),
|
||||||
|
);
|
||||||
|
let line_wire = wires
|
||||||
|
.iter()
|
||||||
|
.find(|wire| {
|
||||||
|
wire.tangent_geoms
|
||||||
|
.iter()
|
||||||
|
.all(|geom| matches!(geom, TangentGeom::Line { .. }))
|
||||||
|
})
|
||||||
|
.expect("straight-segment wire");
|
||||||
|
let circle = WireModel {
|
||||||
|
tangent_geoms: vec![TangentGeom::PlanarCircle {
|
||||||
|
center: [5.0, 4.0, 0.0],
|
||||||
|
axis_x: [1.0, 0.0, 0.0],
|
||||||
|
axis_y: [0.0, 1.0, 0.0],
|
||||||
|
radius: 5.0,
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut points = crate::snap::exact_curve_intersections(line_wire, &circle)
|
||||||
|
.expect("analytical intersections");
|
||||||
|
points.sort_by(|left, right| left.x.total_cmp(&right.x));
|
||||||
|
assert_eq!(points.len(), 2, "unexpected intersections: {points:?}");
|
||||||
|
assert!((points[0] - glam::DVec3::new(2.0, 8.0, 0.0)).length() < 1.0e-9);
|
||||||
|
assert!((points[1] - glam::DVec3::new(8.0, 8.0, 0.0)).length() < 1.0e-9);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split_mixed_polyline_zero_width_segments_after_tapered_segment() {
|
fn test_split_mixed_polyline_zero_width_segments_after_tapered_segment() {
|
||||||
let tg_line = TangentGeom::Line {
|
let tg_line = TangentGeom::Line {
|
||||||
|
|||||||
+97
-70
@@ -2797,27 +2797,56 @@ fn wire_plane(wire: &WireModel) -> Option<WirePlane> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn curves_in_frame(wire: &WireModel, frame: &WirePlane, tol: f64) -> Vec<Curve> {
|
fn tangent_line_endpoints(
|
||||||
|
wire: &WireModel,
|
||||||
|
p1: [f32; 3],
|
||||||
|
p2: [f32; 3],
|
||||||
|
) -> (DVec3, DVec3) {
|
||||||
|
if wire.points.len() == 2 && wire.tangent_geoms.len() == 1 {
|
||||||
|
return (wp_f64(wire, 0), wp_f64(wire, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
let matches = |vertex: [f64; 3], point: [f32; 3]| {
|
||||||
|
(vertex[0] as f32 - point[0]).abs() < 1e-3
|
||||||
|
&& (vertex[1] as f32 - point[1]).abs() < 1e-3
|
||||||
|
&& (vertex[2] as f32 - point[2]).abs() < 1e-3
|
||||||
|
};
|
||||||
|
let count = wire.key_vertices.len();
|
||||||
|
if count >= 2 {
|
||||||
|
for start in 0..count {
|
||||||
|
let end = (start + 1) % count;
|
||||||
|
if matches(wire.key_vertices[start], p1) && matches(wire.key_vertices[end], p2) {
|
||||||
|
return (
|
||||||
|
DVec3::from_array(wire.key_vertices[start]),
|
||||||
|
DVec3::from_array(wire.key_vertices[end]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
Vec3::from_array(p1).as_dvec3(),
|
||||||
|
Vec3::from_array(p2).as_dvec3(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn curves_in_frame(wire: &WireModel, frame: &WirePlane, tol: f64) -> Option<Vec<Curve>> {
|
||||||
use cadkernel::geom2d::{Arc as KArc, Circle as KCircle, Ellipse as KEllipse, EllipseArc as KEllipseArc, Line as KLine};
|
use cadkernel::geom2d::{Arc as KArc, Circle as KCircle, Ellipse as KEllipse, EllipseArc as KEllipseArc, Line as KLine};
|
||||||
|
|
||||||
let mut curves = Vec::new();
|
let mut curves = Vec::new();
|
||||||
|
|
||||||
if wire.tangent_geoms.is_empty() {
|
if wire.tangent_geoms.is_empty() {
|
||||||
if wire.points.len() == 2 {
|
if wire.points.len() != 2 {
|
||||||
let (p0, p1) = (wp_f64(wire, 0), wp_f64(wire, 1));
|
return None;
|
||||||
if frame.contains(p0, tol) && frame.contains(p1, tol) {
|
|
||||||
curves.push(Curve::Line(KLine { start: frame.to_2d(p0), end: frame.to_2d(p1) }));
|
|
||||||
}
|
|
||||||
} else if wire.points.len() > 2 {
|
|
||||||
for i in 0..wire.points.len().saturating_sub(1) {
|
|
||||||
let p0 = wp_f64(wire, i);
|
|
||||||
let p1 = wp_f64(wire, i + 1);
|
|
||||||
if !p0.is_nan() && !p1.is_nan() && frame.contains(p0, tol) && frame.contains(p1, tol) {
|
|
||||||
curves.push(Curve::Line(KLine { start: frame.to_2d(p0), end: frame.to_2d(p1) }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return curves;
|
let (p0, p1) = (wp_f64(wire, 0), wp_f64(wire, 1));
|
||||||
|
if !frame.contains(p0, tol) || !frame.contains(p1, tol) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(vec![Curve::Line(KLine {
|
||||||
|
start: frame.to_2d(p0),
|
||||||
|
end: frame.to_2d(p1),
|
||||||
|
})]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let angle_in_frame = |world_point: DVec3, centre: DVec3| {
|
let angle_in_frame = |world_point: DVec3, centre: DVec3| {
|
||||||
@@ -2825,93 +2854,91 @@ fn curves_in_frame(wire: &WireModel, frame: &WirePlane, tol: f64) -> Vec<Curve>
|
|||||||
(p2[1] - c2[1]).atan2(p2[0] - c2[0])
|
(p2[1] - c2[1]).atan2(p2[0] - c2[0])
|
||||||
};
|
};
|
||||||
|
|
||||||
for (geom_idx, geom) in wire.tangent_geoms.iter().enumerate() {
|
for geom in &wire.tangent_geoms {
|
||||||
match geom {
|
match geom {
|
||||||
TangentGeom::Line { p1, p2 } => {
|
TangentGeom::Line { p1, p2 } => {
|
||||||
let (p1, p2) = if wire.key_vertices.len() > geom_idx + 1 {
|
let (p1, p2) = tangent_line_endpoints(wire, *p1, *p2);
|
||||||
(
|
if !frame.contains(p1, tol) || !frame.contains(p2, tol) {
|
||||||
DVec3::from_array(wire.key_vertices[geom_idx]),
|
return None;
|
||||||
DVec3::from_array(wire.key_vertices[geom_idx + 1]),
|
|
||||||
)
|
|
||||||
} else if wire.points.len() == 2 && wire.tangent_geoms.len() == 1 {
|
|
||||||
(wp_f64(wire, 0), wp_f64(wire, 1))
|
|
||||||
} else {
|
|
||||||
(Vec3::from_array(*p1).as_dvec3(), Vec3::from_array(*p2).as_dvec3())
|
|
||||||
};
|
|
||||||
if frame.contains(p1, tol) && frame.contains(p2, tol) {
|
|
||||||
curves.push(Curve::Line(KLine { start: frame.to_2d(p1), end: frame.to_2d(p2) }));
|
|
||||||
}
|
}
|
||||||
|
curves.push(Curve::Line(KLine { start: frame.to_2d(p1), end: frame.to_2d(p2) }));
|
||||||
}
|
}
|
||||||
TangentGeom::Circle { center, radius } => {
|
TangentGeom::Circle { center, radius } => {
|
||||||
let c = DVec3::new(center[0] as f64, center[1] as f64, center[2] as f64);
|
let c = DVec3::new(center[0] as f64, center[1] as f64, center[2] as f64);
|
||||||
if DVec3::Z.cross(frame.normal).length() <= tol && frame.contains(c, tol) {
|
if DVec3::Z.cross(frame.normal).length() > tol || !frame.contains(c, tol) {
|
||||||
curves.push(Curve::Circle(KCircle { centre: frame.to_2d(c), radius: *radius as f64 }));
|
return None;
|
||||||
}
|
}
|
||||||
|
curves.push(Curve::Circle(KCircle { centre: frame.to_2d(c), radius: *radius as f64 }));
|
||||||
}
|
}
|
||||||
TangentGeom::PlanarCircle { center, axis_x, axis_y, radius } => {
|
TangentGeom::PlanarCircle { center, axis_x, axis_y, radius } => {
|
||||||
let c = DVec3::new(center[0], center[1], center[2]);
|
let c = DVec3::new(center[0], center[1], center[2]);
|
||||||
let n = DVec3::new(axis_x[0], axis_x[1], axis_x[2]).cross(DVec3::new(axis_y[0], axis_y[1], axis_y[2])).normalize();
|
let n = DVec3::new(axis_x[0], axis_x[1], axis_x[2]).cross(DVec3::new(axis_y[0], axis_y[1], axis_y[2])).normalize();
|
||||||
if n.cross(frame.normal).length() <= tol && frame.contains(c, tol) {
|
if n.cross(frame.normal).length() > tol || !frame.contains(c, tol) {
|
||||||
curves.push(Curve::Circle(KCircle { centre: frame.to_2d(c), radius: *radius }));
|
return None;
|
||||||
}
|
}
|
||||||
|
curves.push(Curve::Circle(KCircle { centre: frame.to_2d(c), radius: *radius }));
|
||||||
}
|
}
|
||||||
TangentGeom::Arc { center, axis_x, axis_y, radius, start_angle, end_angle } => {
|
TangentGeom::Arc { center, axis_x, axis_y, radius, start_angle, end_angle } => {
|
||||||
let c = DVec3::new(center[0], center[1], center[2]);
|
let c = DVec3::new(center[0], center[1], center[2]);
|
||||||
let (ax, ay) = (DVec3::new(axis_x[0], axis_x[1], axis_x[2]), DVec3::new(axis_y[0], axis_y[1], axis_y[2]));
|
let (ax, ay) = (DVec3::new(axis_x[0], axis_x[1], axis_x[2]), DVec3::new(axis_y[0], axis_y[1], axis_y[2]));
|
||||||
let n = ax.cross(ay).normalize();
|
let n = ax.cross(ay).normalize();
|
||||||
if n.cross(frame.normal).length() <= tol && frame.contains(c, tol) {
|
if n.cross(frame.normal).length() > tol || !frame.contains(c, tol) {
|
||||||
let arc = KArc { centre: frame.to_2d(c), radius: *radius, start_angle: *start_angle, end_angle: *end_angle };
|
return None;
|
||||||
let sweep = arc.sweep();
|
}
|
||||||
if (sweep - std::f64::consts::TAU).abs() <= 1e-12 {
|
let arc = KArc { centre: frame.to_2d(c), radius: *radius, start_angle: *start_angle, end_angle: *end_angle };
|
||||||
curves.push(Curve::Circle(KCircle { centre: arc.centre, radius: *radius }));
|
let sweep = arc.sweep();
|
||||||
} else {
|
if (sweep - std::f64::consts::TAU).abs() <= 1e-12 {
|
||||||
let boundary = if n.dot(frame.normal) < 0.0 { *end_angle } else { *start_angle };
|
curves.push(Curve::Circle(KCircle { centre: arc.centre, radius: *radius }));
|
||||||
let start_angle = angle_in_frame(c + *radius * (boundary.cos() * ax + boundary.sin() * ay), c);
|
} else {
|
||||||
curves.push(Curve::Arc(KArc { start_angle, end_angle: start_angle + sweep, ..arc }));
|
let boundary = if n.dot(frame.normal) < 0.0 { *end_angle } else { *start_angle };
|
||||||
}
|
let start_angle = angle_in_frame(c + *radius * (boundary.cos() * ax + boundary.sin() * ay), c);
|
||||||
|
curves.push(Curve::Arc(KArc { start_angle, end_angle: start_angle + sweep, ..arc }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TangentGeom::PlanarEllipse { center, major_axis, normal, minor_axis_ratio, start_param, end_param } => {
|
TangentGeom::PlanarEllipse { center, major_axis, normal, minor_axis_ratio, start_param, end_param } => {
|
||||||
let c = DVec3::new(center[0], center[1], center[2]);
|
let c = DVec3::new(center[0], center[1], center[2]);
|
||||||
let ell_normal = DVec3::new(normal[0], normal[1], normal[2]).normalize();
|
let ell_normal = DVec3::new(normal[0], normal[1], normal[2]).normalize();
|
||||||
if ell_normal.cross(frame.normal).length() <= tol && frame.contains(c, tol) {
|
if ell_normal.cross(frame.normal).length() > tol || !frame.contains(c, tol) {
|
||||||
let major = DVec3::new(major_axis[0], major_axis[1], major_axis[2]);
|
return None;
|
||||||
let major_radius = major.length();
|
|
||||||
if major_radius > 1e-12 {
|
|
||||||
let minor_radius = major_radius * *minor_axis_ratio;
|
|
||||||
let dir2d = frame.dir_to_2d(major / major_radius);
|
|
||||||
let len = (dir2d[0] * dir2d[0] + dir2d[1] * dir2d[1]).sqrt();
|
|
||||||
if len > 1e-12 {
|
|
||||||
let major_axis_2d = [dir2d[0] / len, dir2d[1] / len];
|
|
||||||
let (start_parameter, end_parameter) = if ell_normal.dot(frame.normal) < 0.0 {
|
|
||||||
(-*end_param, -*start_param)
|
|
||||||
} else {
|
|
||||||
(*start_param, *end_param)
|
|
||||||
};
|
|
||||||
curves.push(Curve::Ellipse(KEllipseArc {
|
|
||||||
ellipse: KEllipse { centre: frame.to_2d(c), major_radius, minor_radius, major_axis: major_axis_2d },
|
|
||||||
start_parameter,
|
|
||||||
end_parameter,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let major = DVec3::new(major_axis[0], major_axis[1], major_axis[2]);
|
||||||
|
let major_radius = major.length();
|
||||||
|
if major_radius <= 1e-12 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let minor_radius = major_radius * *minor_axis_ratio;
|
||||||
|
let dir2d = frame.dir_to_2d(major / major_radius);
|
||||||
|
let len = (dir2d[0] * dir2d[0] + dir2d[1] * dir2d[1]).sqrt();
|
||||||
|
if len <= 1e-12 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let major_axis_2d = [dir2d[0] / len, dir2d[1] / len];
|
||||||
|
let (start_parameter, end_parameter) = if ell_normal.dot(frame.normal) < 0.0 {
|
||||||
|
(-*end_param, -*start_param)
|
||||||
|
} else {
|
||||||
|
(*start_param, *end_param)
|
||||||
|
};
|
||||||
|
curves.push(Curve::Ellipse(KEllipseArc {
|
||||||
|
ellipse: KEllipse { centre: frame.to_2d(c), major_radius, minor_radius, major_axis: major_axis_2d },
|
||||||
|
start_parameter,
|
||||||
|
end_parameter,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
curves
|
Some(curves)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exact_curve_intersections(wire_a: &WireModel, wire_b: &WireModel) -> Option<Vec<DVec3>> {
|
pub(crate) fn exact_curve_intersections(
|
||||||
|
wire_a: &WireModel,
|
||||||
|
wire_b: &WireModel,
|
||||||
|
) -> Option<Vec<DVec3>> {
|
||||||
let frame = wire_plane(wire_a).or_else(|| wire_plane(wire_b))?;
|
let frame = wire_plane(wire_a).or_else(|| wire_plane(wire_b))?;
|
||||||
|
|
||||||
const PLANE_TOL: f64 = 1e-7;
|
const PLANE_TOL: f64 = 1e-7;
|
||||||
let curves_a = curves_in_frame(wire_a, &frame, PLANE_TOL);
|
let curves_a = curves_in_frame(wire_a, &frame, PLANE_TOL)?;
|
||||||
let curves_b = curves_in_frame(wire_b, &frame, PLANE_TOL);
|
let curves_b = curves_in_frame(wire_b, &frame, PLANE_TOL)?;
|
||||||
if curves_a.is_empty() || curves_b.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Two lines have nothing this path can improve on (the segment sweep is
|
// Two lines have nothing this path can improve on (the segment sweep is
|
||||||
// already exact for a straight pair); avoid the extra work.
|
// already exact for a straight pair); avoid the extra work.
|
||||||
|
|||||||
Reference in new issue
Block a user