add_segment_with_marker using closest_node
This commit is contained in:
parent
cce57cce6b
commit
896ad29936
|
|
@ -141,20 +141,53 @@ impl FemmDoc {
|
|||
|
||||
/// auto-tolerance for intersection-node coalescing, derived from the node bounding box.
|
||||
fn bbox_tolerance(&self) -> f64 {
|
||||
if self.nodes.len() < 2 {
|
||||
return 1.0e-8;
|
||||
nodes_bbox_tolerance(&self.nodes)
|
||||
}
|
||||
|
||||
/// rebuilds every list through the PSLG-aware add primitives, catching crossings missed by incremental edits.
|
||||
pub fn enforce_pslg(&mut self) {
|
||||
let old_nodes = std::mem::take(&mut self.nodes);
|
||||
let old_segments = std::mem::take(&mut self.segments);
|
||||
let old_arcs = std::mem::take(&mut self.arcs);
|
||||
let old_block_labels = std::mem::take(&mut self.block_labels);
|
||||
|
||||
let tol = nodes_bbox_tolerance(&old_nodes);
|
||||
|
||||
// dedupes by position with metadata preserved.
|
||||
for n in &old_nodes {
|
||||
let mut duplicate = false;
|
||||
for existing in &self.nodes {
|
||||
if (existing.x - n.x).hypot(existing.y - n.y) < tol {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !duplicate {
|
||||
self.nodes.push(n.clone());
|
||||
}
|
||||
}
|
||||
let mut xmin = f64::INFINITY;
|
||||
let mut xmax = f64::NEG_INFINITY;
|
||||
let mut ymin = f64::INFINITY;
|
||||
let mut ymax = f64::NEG_INFINITY;
|
||||
for n in &self.nodes {
|
||||
xmin = xmin.min(n.x); xmax = xmax.max(n.x);
|
||||
ymin = ymin.min(n.y); ymax = ymax.max(n.y);
|
||||
|
||||
for s in old_segments {
|
||||
let p0 = (old_nodes[s.n0 as usize].x, old_nodes[s.n0 as usize].y);
|
||||
let p1 = (old_nodes[s.n1 as usize].x, old_nodes[s.n1 as usize].y);
|
||||
let (Some(n0), Some(n1)) = (
|
||||
self.closest_node(p0.0, p0.1),
|
||||
self.closest_node(p1.0, p1.1),
|
||||
) else { continue };
|
||||
self.add_segment_with_marker(n0 as i32, n1 as i32, &s.boundary_marker);
|
||||
}
|
||||
let dx = xmax - xmin;
|
||||
let dy = ymax - ymin;
|
||||
(dx * dx + dy * dy).sqrt() * BBOX_TOLERANCE_FRAC
|
||||
|
||||
for a in old_arcs {
|
||||
let p0 = (old_nodes[a.n0 as usize].x, old_nodes[a.n0 as usize].y);
|
||||
let p1 = (old_nodes[a.n1 as usize].x, old_nodes[a.n1 as usize].y);
|
||||
let (Some(n0), Some(n1)) = (
|
||||
self.closest_node(p0.0, p0.1),
|
||||
self.closest_node(p1.0, p1.1),
|
||||
) else { continue };
|
||||
self.add_arc_segment(n0 as i32, n1 as i32, a.arc_length);
|
||||
}
|
||||
|
||||
self.block_labels = old_block_labels;
|
||||
}
|
||||
|
||||
/// adds an arc segment between two node indices, sweeping `arc_length_deg` degrees.
|
||||
|
|
@ -285,6 +318,24 @@ impl FemmDoc {
|
|||
}
|
||||
}
|
||||
|
||||
/// auto-tolerance derived from the bounding box of a node slice.
|
||||
fn nodes_bbox_tolerance(nodes: &[Node]) -> f64 {
|
||||
if nodes.len() < 2 {
|
||||
return 1.0e-8;
|
||||
}
|
||||
let mut xmin = f64::INFINITY;
|
||||
let mut xmax = f64::NEG_INFINITY;
|
||||
let mut ymin = f64::INFINITY;
|
||||
let mut ymax = f64::NEG_INFINITY;
|
||||
for n in nodes {
|
||||
xmin = xmin.min(n.x); xmax = xmax.max(n.x);
|
||||
ymin = ymin.min(n.y); ymax = ymax.max(n.y);
|
||||
}
|
||||
let dx = xmax - xmin;
|
||||
let dy = ymax - ymin;
|
||||
(dx * dx + dy * dy).sqrt() * BBOX_TOLERANCE_FRAC
|
||||
}
|
||||
|
||||
/// Euclidean distance from (px, py) to the segment between (ax, ay) and (bx, by).
|
||||
fn point_to_segment_distance(px: f64, py: f64, ax: f64, ay: f64, bx: f64, by: f64) -> f64 {
|
||||
let dx = bx - ax;
|
||||
|
|
@ -337,6 +388,37 @@ mod tests {
|
|||
assert_eq!(touches_mid, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_pslg_splits_first_segment_at_late_intersection() {
|
||||
// horizontal added first stays whole; vertical second, its split inserts node 4 at origin.
|
||||
// enforce_pslg now re-adds both segments fresh; with node 4 already in place, both
|
||||
// get split through it, ending at 4 segments touching the origin node.
|
||||
let mut d = doc_with_corners();
|
||||
assert!(d.add_segment(0, 1));
|
||||
assert!(d.add_segment(2, 3));
|
||||
assert_eq!(d.segments.len(), 3, "before enforce: one whole, one split");
|
||||
|
||||
d.enforce_pslg();
|
||||
|
||||
assert_eq!(d.nodes.len(), 5, "no node added or merged by enforce");
|
||||
assert_eq!(d.segments.len(), 4, "both originals now split at the origin");
|
||||
let touches_origin = d.segments.iter().filter(|s| s.n0 == 4 || s.n1 == 4).count();
|
||||
assert_eq!(touches_origin, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_pslg_preserves_segment_boundary_marker() {
|
||||
let mut d = FemmDoc::default();
|
||||
d.add_node(0.0, 0.0, 0.0);
|
||||
d.add_node(1.0, 0.0, 0.0);
|
||||
assert!(d.add_segment_with_marker(0, 1, "outer"));
|
||||
|
||||
d.enforce_pslg();
|
||||
|
||||
assert_eq!(d.segments.len(), 1);
|
||||
assert_eq!(d.segments[0].boundary_marker, "outer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crossing_second_segment_splits_at_intersection() {
|
||||
// horizontal (-1,0)->(1,0) added first stays whole.
|
||||
|
|
|
|||
Loading…
Reference in New Issue