spade/delaunay_triangulation.rs
1use super::delaunay_core::Dcel;
2use crate::{
3 handles::VertexHandle, ConstrainedDelaunayTriangulation, HasPosition, HintGenerator,
4 InsertionError, LastUsedVertexHintGenerator, NaturalNeighbor, Point2, Triangulation,
5 TriangulationExt,
6};
7use alloc::vec;
8use alloc::vec::Vec;
9
10use num_traits::Float;
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15/// A two-dimensional [Delaunay triangulation](https://en.wikipedia.org/wiki/Delaunay_triangulation).
16///
17/// A Delaunay triangulation a triangulation that fulfills the *Delaunay Property*: No
18/// vertex of the triangulation is contained in the
19/// [circumcircle](https://en.wikipedia.org/wiki/Circumscribed_circle) of any triangle.
20/// As a consequence, Delaunay triangulations are well suited to support interpolation
21/// algorithms and nearest neighbor searches. It is often constructed in order to retrieve its dual
22/// graph, the [Voronoi diagram](#voronoi-diagram).
23///
24#[doc = include_str!("../images/circumcircle.svg")]
25///
26/// *An example triangulation with a few circumcircles drawn. No circumcircle contains more than three vertices.*
27///
28/// Most methods on this type require the [Triangulation] trait. Refer to its documentation
29/// for more details on how to use `DelaunayTriangulation`.
30///
31/// # Basic Usage
32/// Vertices need to implement the [HasPosition] trait. Spade bundles
33/// the [Point2] struct for basic use cases.
34///
35/// ## Basic example
36/// ```
37/// use spade::{DelaunayTriangulation, Triangulation, Point2, InsertionError};
38///
39/// fn main() -> Result<(), InsertionError> {
40///
41/// let mut triangulation: DelaunayTriangulation<_> = DelaunayTriangulation::new();
42///
43/// // Insert three vertices that span one triangle (face)
44/// triangulation.insert(Point2::new(0.0, 1.0))?;
45/// triangulation.insert(Point2::new(1.0, 1.0))?;
46/// triangulation.insert(Point2::new(0.5, -1.0))?;
47///
48/// assert_eq!(triangulation.num_vertices(), 3);
49/// assert_eq!(triangulation.num_inner_faces(), 1);
50/// assert_eq!(triangulation.num_undirected_edges(), 3);
51/// Ok(())
52/// }
53/// ```
54/// ## Right handed and left-handed coordinate systems
55/// For simplicity, all method names and their documentation assume that the underlying coordinate system
56/// is right-handed (e.g. x-axis points to the right, y-axis points upwards). If a left-handed system
57/// (lhs) is used, any term related to orientation needs to be reversed:
58/// - "left" becomes "right" (example: the face of a directed edge is on the right side for a lhs)
59/// - "counter clock wise" becomes "clockwise" (example: the vertices of a face are returned in clock wise order for a lhs)
60///
61/// <table>
62/// <tr><th>left-handed system</th><th>right-handed system</th></tr>
63/// <tr><td>
64#[doc = concat!(include_str!("../images/lhs.svg"), "</td><td>",include_str!("../images/rhs.svg"), " </td></tr></table>")]
65/// # Extracting geometry information
66///
67/// Spade uses [handles](crate::handles) to extract the triangulation's geometry.
68/// Handles are usually retrieved by inserting a vertex or by iterating.
69///
70/// ## Example
71/// ```
72/// fn main() -> Result<(), spade::InsertionError> {
73/// use crate::spade::{DelaunayTriangulation, Triangulation, Point2};
74///
75/// let mut triangulation: DelaunayTriangulation<Point2<f64>> = DelaunayTriangulation::new();
76///
77/// triangulation.insert(Point2::new(0.0, 1.0))?;
78/// triangulation.insert(Point2::new(1.0, 1.0))?;
79/// triangulation.insert(Point2::new(0.5, -1.0))?;
80///
81/// for face in triangulation.inner_faces() {
82/// // face is a FaceHandle
83/// // edges is an array containing 3 directed edge handles
84/// let edges = face.adjacent_edges();
85/// for edge in &edges {
86/// let from = edge.from();
87/// let to = edge.to();
88/// // from and to are vertex handles
89/// println!("found an edge: {:?} -> {:?}", from, to);
90/// }
91///
92/// // vertices is an array containing 3 vertex handles
93/// let vertices = face.vertices();
94/// for vertex in &vertices {
95/// println!("Found vertex with position {:?}", vertex.position());
96/// }
97/// }
98/// # Ok(()) }
99/// ```
100///
101/// # Type parameters
102/// The triangulation's vertices, edges and faces can contain custom data.
103/// By default, the edge and face types are set to `()`. The vertex type must
104/// be specified.
105///
106/// * `V: HasPosition` The vertex type
107/// * `DE: Default` The directed edge type.
108/// * `UE: Default` The undirected edge type.
109/// * `F: Default` The face type.
110///
111/// Only vertices can be inserted directly. Faces and edges are create via `Default::default()`.
112/// Usually, edge and face data will need to be modified in a separate pass.
113///
114/// Setting any custom data works by calling [vertex_data_mut](Triangulation::vertex_data_mut),
115/// [directed_edge_data_mut](Triangulation::directed_edge_data_mut),
116/// [undirected_edge_data_mut](Triangulation::undirected_edge_data_mut) and
117/// [face_data_mut](Triangulation::face_data_mut).
118///
119/// ## Example
120/// ```
121/// fn main() -> Result<(), spade::InsertionError> {
122/// use crate::spade::{DelaunayTriangulation, Triangulation, Point2};
123///
124/// // A custom undirected edge type used to cache the length of an edge
125/// #[derive(Default)]
126/// struct EdgeWithLength { length: f64 }
127///
128/// // Creates a new triangulation with a custom undirected edge type
129/// let mut triangulation: DelaunayTriangulation<Point2<f64>, (), EdgeWithLength>
130/// = DelaunayTriangulation::new();
131///
132/// triangulation.insert(Point2::new(0.0, 1.0))?;
133/// triangulation.insert(Point2::new(1.0, 1.0))?;
134/// triangulation.insert(Point2::new(0.5, -1.0))?;
135///
136/// for edge in triangulation.fixed_undirected_edges() {
137/// let positions = triangulation.undirected_edge(edge).positions();
138/// let length = positions[0].distance_2(positions[1]).sqrt();
139/// // Write length into the edge data
140/// triangulation.undirected_edge_data_mut(edge).length = length;
141/// }
142///
143/// for edge in triangulation.undirected_edges() {
144/// let length = edge.data().length;
145/// assert!(length > 0.0);
146/// }
147/// # Ok(()) }
148/// ```
149///
150/// # Outer face
151/// Every triangulation contains an *outer face* that is adjacent to all edges of the
152/// triangulation's convex hull. The outer face is even present for a triangulation without
153/// vertices. It is either referenced by calling [Triangulation::outer_face()] or
154/// [handles::OUTER_FACE](crate::handles::OUTER_FACE)
155///
156#[doc = include_str!("../images/outer_faces.svg")]
157///
158/// # Voronoi Diagram
159///
160/// the dual graph of the Delaunay triangulation is the *Voronoi diagram*. The Voronoi diagram
161/// subdivides the plane into several *Voronoi cells* (called `VoronoiFace` by Spade for consistency)
162/// which contain all points in the plane that share a common nearest neighbor.
163///
164/// Keep in mind that "faces" and "vertices" are swapped - an (inner) Voronoi *vertex*
165/// corresponds to a single Delaunay *face*.
166/// The position of an inner voronoi vertex is the *circumcenter* of its dual Delaunay
167/// face.
168///
169#[doc = include_str!("../images/basic_voronoi.svg")]
170///
171/// *A Delaunay triangulation (black lines) and its dual graph, the Voronoi diagram (blue lines)*
172///
173/// ## Extracting the Voronoi Diagram
174/// Spade defines various functions to extract information about the Voronoi diagram:
175///
176/// **Types**
177/// * [DirectedVoronoiEdge](crate::handles::DirectedVoronoiEdge)
178/// * [UndirectedVoronoiEdge](crate::handles::UndirectedVoronoiEdge)
179/// * [VoronoiVertex](crate::handles::VoronoiVertex)
180/// * [VoronoiFace](crate::handles::VoronoiFace)
181///
182/// **Iterators**
183/// * [Triangulation::directed_voronoi_edges()]
184/// * [Triangulation::undirected_voronoi_edges()]
185///
186/// **Conversion**
187/// * [DirectedVoronoiEdge::as_undirected()](crate::handles::DirectedVoronoiEdge::as_undirected())
188/// * [UndirectedVoronoiEdge::as_directed()](crate::handles::UndirectedVoronoiEdge::as_directed())
189/// * [DirectedEdgeHandle::as_voronoi_edge()](crate::handles::DirectedEdgeHandle::as_voronoi_edge())
190/// * [DirectedVoronoiEdge::as_delaunay_edge()](crate::handles::DirectedVoronoiEdge::as_delaunay_edge())
191/// * [UndirectedEdgeHandle::as_voronoi_edge()](crate::handles::UndirectedEdgeHandle::as_voronoi_edge())
192/// * [UndirectedVoronoiEdge::as_delaunay_edge()](crate::handles::UndirectedVoronoiEdge::as_delaunay_edge())
193///
194/// ## Extracting the Voronoi Diagram (Example)
195/// Extracting the geometry of the voronoi diagram can be slightly tricky as some of the voronoi
196/// extend into infinity (see the dashed lines in the example above).
197///
198/// ```
199/// use spade::handles::{VoronoiVertex::Inner, VoronoiVertex::Outer};
200/// use spade::{DelaunayTriangulation, Point2, Triangulation};
201///
202/// // Prints out the location of all voronoi edges in a triangulation
203/// fn log_voronoi_diagram(triangulation: &DelaunayTriangulation<Point2<f64>>) {
204/// for edge in triangulation.undirected_voronoi_edges() {
205/// match edge.vertices() {
206/// [Inner(from), Inner(to)] => {
207/// // "from" and "to" are inner faces of the Delaunay triangulation
208/// println!(
209/// "Found voronoi edge between {:?} and {:?}",
210/// from.circumcenter(),
211/// to.circumcenter()
212/// );
213/// }
214/// [Inner(from), Outer(edge)] | [Outer(edge), Inner(from)] => {
215/// // Some lines don't have a finite end and extend into infinity.
216/// println!(
217/// "Found infinite voronoi edge going out of {:?} into the direction {:?}",
218/// from.circumcenter(),
219/// edge.direction_vector()
220/// );
221/// }
222/// [Outer(_), Outer(_)] => {
223/// // This case only happens if all vertices of the triangulation lie on the
224/// // same line and can probably be ignored.
225/// }
226/// }
227/// }
228/// }
229/// ```
230///
231/// # Performance tuning
232///
233/// Fine-tuning a Delaunay triangulation can be more tricky from time to time. However, some will *nearly always* be
234/// the right thing to do:
235///
236/// - Measure, don't guess. Execution times are hard to predict.
237/// - If you plan to perform several random access queries (e.g. looking up the point at an arbitrary position):
238/// Consider using `[HierarchyHintGenerator](crate::HierarchyHintGenerator)
239/// - For data sets with uniformly distributed vertices: Use [HierarchyHintGenerator](crate::HierarchyHintGenerator) if
240/// bulk loading is not applicable.
241/// - For data sets where vertices are inserted in close local proximity (each vertex is not too far away from the
242/// previously inserted vertex): Consider using [LastUsedVertexHintGenerator](LastUsedVertexHintGenerator).
243/// - Try to avoid large custom data types for edges, vertices and faces.
244/// - Using `f64` and `f32` as scalar type will usually end up roughly having the same run time performance.
245/// - Prefer using [bulk_load](Triangulation::bulk_load) over [insert](Triangulation::insert).
246/// - The run time of all vertex operations (insertion, removal and lookup) is roughly the same for larger triangulations.
247///
248/// ## Complexity classes
249///
250/// This table display the average and amortized cost for inserting a vertex into a triangulation with `n` vertices.
251///
252/// | | Uniformly distributed vertices | Insertion of vertices with local proximity |
253/// |-----------------------------|--------------------------------|--------------------------------------------|
254/// | LastUsedVertexHintGenerator | O(sqrt(n)) (worst case) | O(1) (best case), fastest |
255/// | HierarchyHintGenerator | O(log(n)) (Average case) | O(1) (best case) |
256///
257/// # See also
258/// Delaunay triangulations are closely related to [constrained Delaunay triangulations](crate::ConstrainedDelaunayTriangulation)
259#[doc(alias = "Voronoi")]
260#[doc(alias = "Voronoi diagram")]
261#[doc(alias = "Delaunay")]
262#[derive(Debug, Clone)]
263#[cfg_attr(
264 feature = "serde",
265 derive(Serialize, Deserialize),
266 serde(crate = "serde")
267)]
268pub struct DelaunayTriangulation<V, DE = (), UE = (), F = (), L = LastUsedVertexHintGenerator>
269where
270 V: HasPosition,
271 DE: Default,
272 UE: Default,
273 F: Default,
274 L: HintGenerator<<V as HasPosition>::Scalar>,
275{
276 pub(crate) dcel: Dcel<V, DE, UE, F>,
277 pub(crate) hint_generator: L,
278}
279
280impl<V, DE, UE, F, L> DelaunayTriangulation<V, DE, UE, F, L>
281where
282 V: HasPosition,
283 DE: Default,
284 UE: Default,
285 F: Default,
286 L: HintGenerator<<V as HasPosition>::Scalar>,
287{
288 /// Returns the nearest neighbor of a given input vertex.
289 ///
290 /// Returns `None` if the triangulation is empty.
291 ///
292 /// # Runtime
293 /// This method takes `O(sqrt(n))` on average where n is the number of vertices.
294 pub fn nearest_neighbor(
295 &self,
296 position: Point2<<V as HasPosition>::Scalar>,
297 ) -> Option<VertexHandle<'_, V, DE, UE, F>> {
298 if self.num_vertices() == 0 {
299 return None;
300 }
301
302 let hint = self.hint_generator().get_hint(position);
303 let hint = self.validate_vertex_handle(hint);
304
305 let vertex = self.walk_to_nearest_neighbor(hint, position);
306 self.hint_generator().notify_vertex_lookup(vertex.fix());
307 Some(vertex)
308 }
309
310 /// Creates a new delaunay triangulation with an efficient bulk loading strategy.
311 ///
312 /// In contrast to [Triangulation::bulk_load], this method will create a triangulation with
313 /// vertices returned *in the same order* as the input vertices.
314 ///
315 /// # Duplicate handling
316 ///
317 /// For every set of vertices with identical positions, only the vertex with the lowest index
318 /// is kept.
319 ///
320 /// For example, if the input vertices are `[v0, v1, v2, v1]` (where v1 is duplicated), the
321 /// resulting triangulation will be `[v0, v1, v2]`.
322 ///
323 /// Consider checking the triangulation's size after calling this method to ensure that no
324 /// duplicates were present. Removing duplicates requires additional work and slightly
325 /// increases the run time.
326 ///
327 /// # Run Time
328 ///
329 /// `O(n*log(n))` for `n` input vertices. Slightly (5% - 10%) slower than [Triangulation::bulk_load].
330 ///
331 /// # Example
332 /// ```
333 /// # use spade::InsertionError;
334 /// use spade::{DelaunayTriangulation, Point2, Triangulation};
335 ///
336 /// # fn main() -> Result<(), InsertionError> {
337 /// let vertices = vec![
338 /// Point2::new(0.5, 1.0),
339 /// Point2::new(-0.5, 2.0),
340 /// Point2::new(0.2, 3.0),
341 /// Point2::new(0.0, 4.0),
342 /// Point2::new(-0.2, 5.0)
343 /// ];
344 /// let triangulation = DelaunayTriangulation::<Point2<f64>>::bulk_load_stable(vertices.clone())?;
345 /// // This assert will not hold for regular bulk loading!
346 /// assert_eq!(triangulation.vertices().map(|v| *v.data()).collect::<Vec<_>>(), vertices);
347 ///
348 /// // This is how you would check for duplicates:
349 /// let duplicates_found = triangulation.num_vertices() < vertices.len();
350 /// assert!(!duplicates_found);
351 /// # Ok(()) }
352 /// ```
353 pub fn bulk_load_stable(elements: Vec<V>) -> Result<Self, InsertionError> {
354 let cdt = ConstrainedDelaunayTriangulation::bulk_load_cdt(elements, vec![])?;
355 let result = Self::from_cdt(cdt);
356
357 Ok(result)
358 }
359
360 fn from_cdt(
361 cdt: ConstrainedDelaunayTriangulation<V, DE, UE, F, L>,
362 ) -> DelaunayTriangulation<V, DE, UE, F, L> {
363 let (dcel, hint_generator, num_constraints) = cdt.into_parts();
364 let dcel = dcel.map_undirected_edges(|cdt_edge| cdt_edge.deconstruct().1);
365 assert_eq!(num_constraints, 0);
366 DelaunayTriangulation {
367 dcel,
368 hint_generator,
369 }
370 }
371}
372
373impl<V, DE, UE, F, L> Default for DelaunayTriangulation<V, DE, UE, F, L>
374where
375 V: HasPosition,
376 DE: Default,
377 UE: Default,
378 F: Default,
379 L: HintGenerator<<V as HasPosition>::Scalar>,
380{
381 fn default() -> Self {
382 Self {
383 dcel: Default::default(),
384 hint_generator: Default::default(),
385 }
386 }
387}
388
389impl<V, DE, UE, F, L> DelaunayTriangulation<V, DE, UE, F, L>
390where
391 V: HasPosition,
392 DE: Default,
393 UE: Default,
394 F: Default,
395 V::Scalar: Float,
396 L: HintGenerator<<V as HasPosition>::Scalar>,
397{
398 /// Allows using natural neighbor interpolation on this triangulation. Refer to the documentation
399 /// of [NaturalNeighbor] for more information.
400 pub fn natural_neighbor(&self) -> NaturalNeighbor<'_, Self> {
401 NaturalNeighbor::new(self)
402 }
403}
404
405impl<V, DE, UE, F, L> Triangulation for DelaunayTriangulation<V, DE, UE, F, L>
406where
407 V: HasPosition,
408 DE: Default,
409 UE: Default,
410 F: Default,
411 L: HintGenerator<<V as HasPosition>::Scalar>,
412{
413 type Vertex = V;
414 type DirectedEdge = DE;
415 type UndirectedEdge = UE;
416 type Face = F;
417 type HintGenerator = L;
418
419 fn s(&self) -> &Dcel<V, DE, UE, F> {
420 &self.dcel
421 }
422
423 fn s_mut(&mut self) -> &mut Dcel<V, DE, UE, F> {
424 &mut self.dcel
425 }
426
427 fn hint_generator(&self) -> &Self::HintGenerator {
428 &self.hint_generator
429 }
430
431 fn hint_generator_mut(&mut self) -> &mut Self::HintGenerator {
432 &mut self.hint_generator
433 }
434
435 fn from_parts(
436 dcel: Dcel<Self::Vertex, Self::DirectedEdge, Self::UndirectedEdge, Self::Face>,
437 hint_generator: Self::HintGenerator,
438 num_constraints: usize,
439 ) -> Self {
440 assert_eq!(num_constraints, 0);
441 Self {
442 dcel,
443 hint_generator,
444 }
445 }
446
447 fn into_parts(
448 self,
449 ) -> (
450 Dcel<Self::Vertex, Self::DirectedEdge, Self::UndirectedEdge, Self::Face>,
451 Self::HintGenerator,
452 usize,
453 ) {
454 (self.dcel, self.hint_generator, 0)
455 }
456}
457
458#[cfg(test)]
459mod test {
460 use crate::test_utilities::{random_points_with_seed, SEED};
461
462 use crate::{DelaunayTriangulation, InsertionError, Point2, Triangulation};
463
464 #[allow(unused)]
465 #[cfg(feature = "serde")]
466 // Just needs to compile
467 fn check_serde() {
468 use serde::{Deserialize, Serialize};
469
470 use crate::{HierarchyHintGenerator, LastUsedVertexHintGenerator, Point2};
471
472 fn requires_serde<'de, T: Serialize + Deserialize<'de>>() {}
473
474 type DT<L> = DelaunayTriangulation<Point2<f64>, (), (), (), L>;
475
476 requires_serde::<DT<LastUsedVertexHintGenerator>>();
477 requires_serde::<DT<HierarchyHintGenerator<f64>>>();
478 }
479
480 #[test]
481 fn test_nearest_neighbor() -> Result<(), InsertionError> {
482 const SIZE: usize = 54;
483 let points = random_points_with_seed(SIZE, SEED);
484
485 let d = DelaunayTriangulation::<_>::bulk_load(points.clone())?;
486
487 let sample_points = random_points_with_seed(SIZE * 3, SEED);
488 for p in sample_points {
489 let nn_delaunay = d.nearest_neighbor(p);
490 let nn_linear_search = points.iter().min_by(|l, r| {
491 let d1 = l.distance_2(p);
492 let d2 = r.distance_2(p);
493 d1.partial_cmp(&d2).unwrap()
494 });
495 assert_eq!(nn_delaunay.map(|p| p.position()), nn_linear_search.cloned());
496 }
497 Ok(())
498 }
499
500 #[test]
501 fn test_nearest_neighbor_small() -> Result<(), InsertionError> {
502 let mut d = DelaunayTriangulation::<_>::new();
503 assert_eq!(None, d.nearest_neighbor(Point2::new(0.0, 0.0)));
504
505 d.insert(Point2::new(0.0, 0.0))?;
506 assert!(d.nearest_neighbor(Point2::new(0.0, 1.0)).is_some());
507 Ok(())
508 }
509
510 #[test]
511 #[allow(clippy::redundant_clone)]
512 #[allow(unused_must_use)]
513 fn test_clone_is_implemented() {
514 // Just needs to compile
515 DelaunayTriangulation::<Point2<f64>>::new().clone();
516 }
517}