Coverage Report

Created: 2025-02-21 07:11

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