Skip to content

Commit bc04be2

Browse files
authored
Fix/visibility (#30)
* test: add pan-handle test * refactor: add triangulation trait * feat: add triangulation to dev cli * refactor: replace visibility algorithm * test: update visibility tests * test: enable rdp tests * refactor: add type conversions from spade to geo * refactor: separate extension traits from types * fix: failing split across index 0 * fix: underflow for null polygon * chore: clean-up * chore: add license * chore: formatting
1 parent 890bea9 commit bc04be2

File tree

11 files changed

+637
-230
lines changed

11 files changed

+637
-230
lines changed

src/algorithms/simplify_charshape.rs

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818

1919
// Copyright 2025- Niall Oswald and Kenneth Martin and Jo Wayne Tan
2020

21-
use geo::{Coord, CoordsIter, GeoFloat, LineString, Polygon};
21+
use crate::extensions::triangulate::Triangulate;
22+
use geo::{Coord, GeoFloat, LineString, Polygon};
2223
use hashbrown::HashSet;
2324
use spade::handles::{DirectedEdgeHandle, VertexHandle};
24-
use spade::{CdtEdge, ConstrainedDelaunayTriangulation, Point2, SpadeNum, Triangulation};
25+
use spade::{CdtEdge, Point2, SpadeNum, Triangulation};
2526
use std::cmp::Ordering;
2627
use std::collections::BinaryHeap;
2728
use std::hash::Hash;
@@ -98,28 +99,7 @@ where
9899

99100
let eps_2 = eps * eps;
100101

101-
// Construct Delaunay triangulation
102-
let num_vertices = orig.exterior().0.len() - 1;
103-
104-
let vertices = orig
105-
.exterior_coords_iter()
106-
.take(num_vertices) // duplicate points are removed
107-
.map(|c| Point2::new(c.x, c.y))
108-
.collect::<Vec<_>>();
109-
110-
let edges = (0..num_vertices)
111-
.map(|i| {
112-
if i == 0 {
113-
[vertices.len() - 1, i]
114-
} else {
115-
[i - 1, i]
116-
}
117-
})
118-
.collect::<Vec<_>>();
119-
120-
let tri =
121-
ConstrainedDelaunayTriangulation::<Point2<T>>::bulk_load_cdt(vertices, edges).unwrap();
122-
102+
let tri = orig.triangulate();
123103
let boundary_edges = tri.convex_hull().map(|edge| edge.rev()).collect::<Vec<_>>();
124104
let mut boundary_nodes: HashSet<_> =
125105
HashSet::from_iter(boundary_edges.iter().map(|&edge| BoundaryNode(edge.from())));
@@ -169,7 +149,6 @@ fn recompute_boundary<'a, T>(
169149
) where
170150
T: GeoFloat + SpadeNum,
171151
{
172-
//
173152
let choices = [edge.prev(), edge.next()];
174153
for new_edge in choices {
175154
let e = CharScore {

src/algorithms/simplify_rdp.rs

Lines changed: 108 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -18,96 +18,139 @@
1818

1919
// Copyright 2025- Niall Oswald and Kenneth Martin and Jo Wayne Tan
2020

21-
use crate::algorithms::visibility::visiblity_polygon;
22-
use crate::extensions::segments::{FromSegments, HullSegments};
23-
use geo::{Coord, Distance, Euclidean, GeoFloat, Line, LineString, Polygon};
24-
use rayon::prelude::*;
21+
use crate::algorithms::visibility::visibility_intersection;
22+
use crate::extensions::conversions::IntoCoord;
23+
use crate::extensions::segments::FromSegments;
24+
use crate::extensions::triangulate::Triangulate;
25+
use geo::{Distance, Euclidean, GeoFloat, Line, LineString, Polygon};
26+
use spade::handles::{FixedVertexHandle, VertexHandle};
27+
use spade::{CdtEdge, ConstrainedDelaunayTriangulation, Point2, SpadeNum, Triangulation};
28+
29+
struct CircularIterator<'a, T: SpadeNum> {
30+
current: VertexHandle<'a, Point2<T>, (), CdtEdge<()>>,
31+
until: VertexHandle<'a, Point2<T>, (), CdtEdge<()>>,
32+
cdt: &'a ConstrainedDelaunayTriangulation<Point2<T>>,
33+
}
34+
35+
impl<'a, T: SpadeNum> CircularIterator<'a, T> {
36+
fn new(
37+
from: VertexHandle<'a, Point2<T>, (), CdtEdge<()>>,
38+
until: VertexHandle<'a, Point2<T>, (), CdtEdge<()>>,
39+
cdt: &'a ConstrainedDelaunayTriangulation<Point2<T>>,
40+
) -> Self {
41+
CircularIterator {
42+
current: from,
43+
until,
44+
cdt,
45+
}
46+
}
47+
}
48+
49+
impl<'a, T: SpadeNum> Iterator for CircularIterator<'a, T> {
50+
type Item = VertexHandle<'a, Point2<T>, (), CdtEdge<()>>;
51+
52+
fn next(&mut self) -> Option<Self::Item> {
53+
if self.current == self.until {
54+
return None;
55+
}
56+
57+
let vertex = self.current;
58+
59+
self.current = {
60+
let handle =
61+
FixedVertexHandle::from_index((self.current.index() + 1) % self.cdt.num_vertices());
62+
self.cdt.get_vertex(handle).unwrap()
63+
};
2564

26-
fn rdp_preserve<T>(ls: &[Coord<T>], eps: T) -> Vec<Coord<T>>
65+
Some(vertex)
66+
}
67+
}
68+
69+
fn rdp_preserve<T>(
70+
from: VertexHandle<'_, Point2<T>, (), CdtEdge<()>>,
71+
to: VertexHandle<'_, Point2<T>, (), CdtEdge<()>>,
72+
cdt: &ConstrainedDelaunayTriangulation<Point2<T>>,
73+
eps: T,
74+
) -> Vec<Point2<T>>
2775
where
28-
T: GeoFloat + Send + Sync,
76+
T: SpadeNum + GeoFloat + Send + Sync,
2977
{
30-
let (first, last) = match ls {
31-
[] => return vec![],
32-
&[only] => return vec![only],
33-
&[first, last] => return vec![first, last],
34-
&[first, .., last] => (first, last),
35-
};
36-
37-
let visible = visiblity_polygon(ls);
38-
let chord = Line::new(first, last);
39-
40-
let split_index = visible
41-
.into_iter()
42-
.skip(1)
43-
.fold(
44-
(0usize, T::zero()),
45-
|(farthest_index, farthest_distance), (index, coord)| {
46-
let distance = Euclidean.distance(coord, &chord);
47-
if distance < farthest_distance {
48-
(farthest_index, farthest_distance)
49-
} else {
50-
(index, distance)
51-
}
52-
},
53-
)
54-
.0;
55-
56-
if split_index == 0 || split_index == ls.len() - 1 {
57-
println!("Failed to reduce. Skipping.");
58-
return vec![first, last];
78+
if cdt.exists_constraint(from.fix(), to.fix()) {
79+
return vec![from.position(), to.position()];
5980
}
6081

61-
let farthest_distance = ls.iter().map(|&v| Euclidean.distance(v, &chord)).fold(
62-
T::zero(),
63-
|farthest_distance, distance| {
82+
let chord = {
83+
let [from, to] = [from, to].map(|v| v.position().into_coord());
84+
Line::new(from, to)
85+
};
86+
87+
let farthest_distance =
88+
CircularIterator::new(from, to, cdt).fold(T::zero(), |farthest_distance, v| {
89+
let distance = Euclidean.distance(v.position().into_coord(), &chord);
6490
if distance > farthest_distance {
6591
distance
6692
} else {
6793
farthest_distance
6894
}
95+
});
96+
97+
if farthest_distance <= eps {
98+
return vec![from.position(), to.position()];
99+
}
100+
101+
let (split_vertex, _) = visibility_intersection(from, to, cdt).into_iter().fold(
102+
(from, -T::one()), // Placeholder, should always be overwritten
103+
|(farthest_vertex, farthest_distance), v| {
104+
let distance = Euclidean.distance(v.position().into_coord(), &chord);
105+
if distance > farthest_distance {
106+
(v, distance)
107+
} else {
108+
(farthest_vertex, farthest_distance)
109+
}
69110
},
70111
);
71112

72-
if farthest_distance > eps {
73-
let (mut left, right) = rayon::join(
74-
|| rdp_preserve(&ls[..=split_index], eps),
75-
|| rdp_preserve(&ls[split_index..], eps),
76-
);
113+
// This should never occur
114+
if split_vertex == from || split_vertex == to {
115+
panic!("Attempted to split at endpoint");
116+
}
77117

78-
left.pop();
79-
left.extend_from_slice(&right);
118+
let (mut left, right) = rayon::join(
119+
|| rdp_preserve(from, split_vertex, cdt, eps),
120+
|| rdp_preserve(split_vertex, to, cdt, eps),
121+
);
80122

81-
return left;
82-
}
123+
left.pop();
124+
left.extend_from_slice(&right);
83125

84-
vec![first, last]
126+
left
85127
}
86128

87129
pub trait SimplifyRDP<T, Epsilon = T> {
88130
fn simplify_rdp(&self, eps: Epsilon) -> Self;
89131
}
90132

91-
impl<T> SimplifyRDP<T> for LineString<T>
92-
where
93-
T: GeoFloat + Send + Sync,
94-
{
95-
fn simplify_rdp(&self, eps: T) -> Self {
96-
LineString::new(rdp_preserve(&self.0, eps))
97-
}
98-
}
99-
100133
impl<T> SimplifyRDP<T> for Polygon<T>
101134
where
102-
T: GeoFloat + Send + Sync,
135+
T: SpadeNum + GeoFloat + Send + Sync,
103136
{
104137
fn simplify_rdp(&self, eps: T) -> Self {
105-
let reduced_segments = self
106-
.hull_segments()
107-
.into_par_iter() // parallelize with rayon
108-
.map(|ls| ls.simplify_rdp(eps))
109-
.collect::<Vec<_>>();
110-
111-
Polygon::from_segments(reduced_segments)
138+
if self.exterior().0.len() < 3 {
139+
return self.clone();
140+
}
141+
142+
let cdt = self.triangulate();
143+
144+
let segments = cdt
145+
.convex_hull()
146+
.map(|edge| {
147+
rdp_preserve(edge.from(), edge.to(), &cdt, eps)
148+
.into_iter()
149+
.map(|point| point.into_coord())
150+
.collect::<LineString<_>>()
151+
})
152+
.collect();
153+
154+
Polygon::from_segments(segments)
112155
}
113156
}

src/algorithms/simplify_vw.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
// Copyright 2025- Niall Oswald and Kenneth Martin and Jo Wayne Tan
2020

21-
use crate::extensions::ord_triangles::{OrdTriangle, OrdTriangles};
21+
use crate::types::ord_triangle::{OrdTriangle, OrdTriangles};
2222

2323
use geo::algorithm::{Area, Intersects};
2424
use geo::geometry::{Coord, Line, LineString, Point, Polygon};

0 commit comments

Comments
 (0)