oxigdal_algorithms/vector/
delaunay.rs1use crate::error::{AlgorithmError, Result};
6use oxigdal_core::vector::{Coordinate, LineString, Point, Polygon};
7
8#[derive(Debug, Clone)]
10pub struct DelaunayOptions {
11 pub compute_quality: bool,
13 pub min_angle: f64,
15}
16
17impl Default for DelaunayOptions {
18 fn default() -> Self {
19 Self {
20 compute_quality: false,
21 min_angle: 20.0,
22 }
23 }
24}
25
26#[derive(Debug, Clone)]
28pub struct Triangle {
29 pub vertices: [usize; 3],
31 pub polygon: Polygon,
33 pub quality: Option<f64>,
35}
36
37#[derive(Debug, Clone)]
39pub struct DelaunayTriangulation {
40 pub points: Vec<Point>,
42 pub triangles: Vec<Triangle>,
44 pub num_triangles: usize,
46}
47
48pub fn delaunay_triangulation(
85 points: &[Point],
86 options: &DelaunayOptions,
87) -> Result<DelaunayTriangulation> {
88 if points.len() < 3 {
89 return Err(AlgorithmError::InvalidInput(
90 "Need at least 3 points for triangulation".to_string(),
91 ));
92 }
93
94 let delaunator_points: Vec<delaunator::Point> = points
96 .iter()
97 .map(|p| delaunator::Point {
98 x: p.coord.x,
99 y: p.coord.y,
100 })
101 .collect();
102
103 let delaunay = delaunator::triangulate(&delaunator_points);
105
106 let mut triangles = Vec::new();
108
109 for tri_idx in 0..(delaunay.triangles.len() / 3) {
110 let a = delaunay.triangles[tri_idx * 3];
111 let b = delaunay.triangles[tri_idx * 3 + 1];
112 let c = delaunay.triangles[tri_idx * 3 + 2];
113
114 let pa = &points[a];
115 let pb = &points[b];
116 let pc = &points[c];
117
118 let coords_tri = vec![
120 Coordinate::new_2d(pa.coord.x, pa.coord.y),
121 Coordinate::new_2d(pb.coord.x, pb.coord.y),
122 Coordinate::new_2d(pc.coord.x, pc.coord.y),
123 Coordinate::new_2d(pa.coord.x, pa.coord.y), ];
125
126 let exterior = LineString::new(coords_tri)
127 .map_err(|e| AlgorithmError::InvalidGeometry(format!("Invalid triangle: {}", e)))?;
128
129 let polygon = Polygon::new(exterior, vec![]).map_err(|e| {
130 AlgorithmError::InvalidGeometry(format!("Invalid triangle polygon: {}", e))
131 })?;
132
133 let quality = if options.compute_quality {
135 Some(compute_triangle_quality(pa, pb, pc))
136 } else {
137 None
138 };
139
140 triangles.push(Triangle {
141 vertices: [a, b, c],
142 polygon,
143 quality,
144 });
145 }
146
147 let num_triangles = triangles.len();
148
149 Ok(DelaunayTriangulation {
150 points: points.to_vec(),
151 triangles,
152 num_triangles,
153 })
154}
155
156fn compute_triangle_quality(pa: &Point, pb: &Point, pc: &Point) -> f64 {
158 let a = distance(pb, pc);
160 let b = distance(pc, pa);
161 let c = distance(pa, pb);
162
163 let s = (a + b + c) / 2.0;
165
166 let area = (s * (s - a) * (s - b) * (s - c)).sqrt();
168
169 let inradius = area / s;
171
172 let circumradius = (a * b * c) / (4.0 * area);
174
175 if circumradius > 0.0 {
177 2.0 * inradius / circumradius
178 } else {
179 0.0
180 }
181}
182
183fn distance(p1: &Point, p2: &Point) -> f64 {
185 let dx = p1.coord.x - p2.coord.x;
186 let dy = p1.coord.y - p2.coord.y;
187 (dx * dx + dy * dy).sqrt()
188}
189
190pub fn in_circumcircle(pa: &Point, pb: &Point, pc: &Point, pd: &Point) -> bool {
192 let ax = pa.coord.x - pd.coord.x;
193 let ay = pa.coord.y - pd.coord.y;
194 let bx = pb.coord.x - pd.coord.x;
195 let by = pb.coord.y - pd.coord.y;
196 let cx = pc.coord.x - pd.coord.x;
197 let cy = pc.coord.y - pd.coord.y;
198
199 let det = (ax * ax + ay * ay) * (bx * cy - cx * by) - (bx * bx + by * by) * (ax * cy - cx * ay)
200 + (cx * cx + cy * cy) * (ax * by - bx * ay);
201
202 det > 0.0
203}
204
205pub fn constrained_delaunay(
207 points: &[Point],
208 constraints: &[(usize, usize)],
209 options: &DelaunayOptions,
210) -> Result<DelaunayTriangulation> {
211 let mut triangulation = delaunay_triangulation(points, options)?;
213
214 triangulation
216 .triangles
217 .retain(|triangle| !violates_constraints(triangle, constraints, points));
218
219 triangulation.num_triangles = triangulation.triangles.len();
220
221 Ok(triangulation)
222}
223
224fn violates_constraints(
226 triangle: &Triangle,
227 constraints: &[(usize, usize)],
228 _points: &[Point],
229) -> bool {
230 for &(c1, c2) in constraints {
232 let [a, b, c] = triangle.vertices;
234
235 if (c1 == a && c2 == b)
236 || (c1 == b && c2 == a)
237 || (c1 == b && c2 == c)
238 || (c1 == c && c2 == b)
239 || (c1 == c && c2 == a)
240 || (c1 == a && c2 == c)
241 {
242 continue;
243 }
244
245 }
248
249 false
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_delaunay_simple() {
258 let points = vec![
259 Point::new(0.0, 0.0),
260 Point::new(1.0, 0.0),
261 Point::new(0.5, 1.0),
262 Point::new(0.5, 0.5),
263 ];
264
265 let options = DelaunayOptions::default();
266 let result = delaunay_triangulation(&points, &options);
267
268 assert!(result.is_ok());
269
270 let triangulation = result.expect("Triangulation failed");
271 assert!(triangulation.num_triangles >= 2);
272 }
273
274 #[test]
275 fn test_triangle_quality() {
276 let pa = Point::new(0.0, 0.0);
278 let pb = Point::new(1.0, 0.0);
279 let pc = Point::new(0.5, 0.866); let quality = compute_triangle_quality(&pa, &pb, &pc);
282 assert!(quality > 0.9); }
284
285 #[test]
286 fn test_in_circumcircle() {
287 let pa = Point::new(0.0, 0.0);
288 let pb = Point::new(1.0, 0.0);
289 let pc = Point::new(0.0, 1.0);
290 let pd = Point::new(0.25, 0.25); assert!(in_circumcircle(&pa, &pb, &pc, &pd));
293 }
294
295 #[test]
296 fn test_constrained_delaunay() {
297 let points = vec![
298 Point::new(0.0, 0.0),
299 Point::new(1.0, 0.0),
300 Point::new(0.5, 1.0),
301 Point::new(0.5, 0.5),
302 ];
303
304 let constraints = vec![(0, 2)]; let options = DelaunayOptions::default();
307 let result = constrained_delaunay(&points, &constraints, &options);
308
309 assert!(result.is_ok());
310 }
311}