scirs2_spatial/
delaunay.rs

1//! Delaunay triangulation algorithms
2//!
3//! This module provides implementations for Delaunay triangulation of points in 2D and higher dimensions.
4//! Delaunay triangulation is a way of connecting a set of points to form triangles such that no point
5//! is inside the circumcircle of any triangle.
6//!
7//! # Implementation
8//!
9//! This module uses the Qhull library (via qhull-rs) for computing Delaunay triangulations.
10//! Qhull implements the Quickhull algorithm for Delaunay triangulation and convex hull computation.
11//!
12//! # Examples
13//!
14//! ```
15//! use scirs2_spatial::delaunay::Delaunay;
16//! use ndarray::array;
17//!
18//! // Create a set of 2D points
19//! let points = array![
20//!     [0.0, 0.0],
21//!     [1.0, 0.0],
22//!     [0.0, 1.0],
23//!     [0.5, 0.5]
24//! ];
25//!
26//! // Compute Delaunay triangulation
27//! let tri = Delaunay::new(&points).unwrap();
28//!
29//! // Get the simplex (triangle) indices
30//! let simplices = tri.simplices();
31//! println!("Triangles: {:?}", simplices);
32//!
33//! // Find the triangle containing a point
34//! let point = [0.25, 0.25];
35//! if let Some(idx) = tri.find_simplex(&point) {
36//!     println!("Point {:?} is in triangle {}", point, idx);
37//! }
38//! ```
39
40use crate::error::{SpatialError, SpatialResult};
41use ndarray::Array2;
42use qhull::Qh;
43use std::collections::{HashMap, HashSet};
44use std::fmt::Debug;
45
46/// Structure for storing and querying a Delaunay triangulation
47///
48/// The Delaunay triangulation of a set of points is a triangulation such that
49/// no point is inside the circumcircle of any triangle (in 2D) or circumsphere
50/// of any tetrahedron (in 3D).
51///
52/// This implementation uses the Qhull library (via qhull-rs) to compute
53/// Delaunay triangulations efficiently.
54pub struct Delaunay {
55    /// The points used for the triangulation
56    points: Array2<f64>,
57
58    /// The number of dimensions
59    ndim: usize,
60
61    /// The number of points
62    npoints: usize,
63
64    /// The simplices (triangles in 2D, tetrahedra in 3D, etc.)
65    /// Each element is a vector of indices of the vertices forming a simplex
66    simplices: Vec<Vec<usize>>,
67
68    /// For each simplex, its neighboring simplices
69    /// neighbors[i][j] is the index of the simplex that shares a face with simplex i,
70    /// opposite to the vertex j of simplex i. -1 indicates no neighbor.
71    neighbors: Vec<Vec<i64>>,
72
73    /// The QHull instance (if retained)
74    #[allow(dead_code)]
75    qh: Option<Qh<'static>>,
76
77    /// Constraint edges (for constrained Delaunay triangulation)
78    /// Each edge is represented as a pair of point indices
79    constraints: Vec<(usize, usize)>,
80}
81
82impl Debug for Delaunay {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.debug_struct("Delaunay")
85            .field("points", &self.points.shape())
86            .field("ndim", &self.ndim)
87            .field("npoints", &self.npoints)
88            .field("simplices", &self.simplices.len())
89            .field("neighbors", &self.neighbors.len())
90            .field("constraints", &self.constraints.len())
91            .finish()
92    }
93}
94
95impl Clone for Delaunay {
96    fn clone(&self) -> Self {
97        Self {
98            points: self.points.clone(),
99            ndim: self.ndim,
100            npoints: self.npoints,
101            simplices: self.simplices.clone(),
102            neighbors: self.neighbors.clone(),
103            qh: None, // We don't clone the Qhull handle
104            constraints: self.constraints.clone(),
105        }
106    }
107}
108
109impl Delaunay {
110    /// Create a new Delaunay triangulation
111    ///
112    /// # Arguments
113    ///
114    /// * `points` - The points to triangulate, shape (npoints, ndim)
115    ///
116    /// # Returns
117    ///
118    /// * A new Delaunay triangulation or an error
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// use scirs2_spatial::delaunay::Delaunay;
124    /// use ndarray::array;
125    ///
126    /// let points = array![
127    ///     [0.0, 0.0],
128    ///     [1.0, 0.0],
129    ///     [0.0, 1.0],
130    ///     [1.0, 1.0]
131    /// ];
132    ///
133    /// let tri = Delaunay::new(&points).unwrap();
134    /// let simplices = tri.simplices();
135    /// println!("Triangles: {:?}", simplices);
136    /// ```
137    pub fn new(points: &Array2<f64>) -> SpatialResult<Self> {
138        let npoints = points.nrows();
139        let ndim = points.ncols();
140
141        // Check if we have enough _points for triangulation
142        if npoints <= ndim {
143            return Err(SpatialError::ValueError(format!(
144                "Need at least {ndim_plus_1} _points in {ndim} dimensions for triangulation",
145                ndim_plus_1 = ndim + 1
146            )));
147        }
148
149        // Special case for 3 _points in 2D - form a single triangle
150        if ndim == 2 && npoints == 3 {
151            let simplex = vec![0, 1, 2];
152            let simplices = vec![simplex];
153            let neighbors = vec![vec![-1, -1, -1]]; // No neighbors
154
155            return Ok(Delaunay {
156                points: points.clone(),
157                ndim,
158                npoints,
159                simplices,
160                neighbors,
161                qh: None,
162                constraints: Vec::new(),
163            });
164        }
165
166        // Extract _points as Vec of Vec for Qhull
167        let _points_vec: Vec<Vec<f64>> = (0..npoints).map(|i| points.row(i).to_vec()).collect();
168
169        // Try with standard approach first
170        let qh_result = Qh::new_delaunay(_points_vec.clone());
171
172        let qh = match qh_result {
173            Ok(qh) => qh,
174            Err(e) => {
175                // Special case for square in 2D - form two triangles
176                if ndim == 2 && npoints == 4 {
177                    // Check if _points form a square-like pattern
178                    let simplex1 = vec![0, 1, 2];
179                    let simplex2 = vec![1, 2, 3];
180                    let simplices = vec![simplex1, simplex2];
181                    let neighbors = vec![vec![-1, 1, -1], vec![-1, -1, 0]];
182
183                    return Ok(Delaunay {
184                        points: points.clone(),
185                        ndim,
186                        npoints,
187                        simplices,
188                        neighbors,
189                        qh: None,
190                        constraints: Vec::new(),
191                    });
192                }
193
194                // Add some random jitter to _points
195                let mut perturbed_points = vec![];
196                use rand::Rng;
197                let mut rng = rand::rng();
198
199                for i in 0..npoints {
200                    let mut pt = points.row(i).to_vec();
201                    for val in pt.iter_mut().take(ndim) {
202                        *val += rng.gen_range(-0.0001..0.0001);
203                    }
204                    perturbed_points.push(pt);
205                }
206
207                // Try with perturbed _points
208                match Qh::new_delaunay(perturbed_points) {
209                    Ok(qh2) => qh2,
210                    Err(_) => {
211                        return Err(SpatialError::ComputationError(format!(
212                            "Qhull error (even with perturbation): {e}"
213                        )));
214                    }
215                }
216            }
217        };
218
219        // Extract simplices
220        let simplices = Self::extract_simplices(&qh, ndim);
221
222        // Calculate neighbors of each simplex
223        let neighbors = Self::calculate_neighbors(&simplices, ndim + 1);
224
225        Ok(Delaunay {
226            points: points.clone(),
227            ndim,
228            npoints,
229            simplices,
230            neighbors,
231            qh: Some(qh),
232            constraints: Vec::new(),
233        })
234    }
235
236    /// Create a new constrained Delaunay triangulation
237    ///
238    /// # Arguments
239    ///
240    /// * `points` - The points to triangulate, shape (npoints, ndim)
241    /// * `constraints` - Vector of constraint edges, each edge is a pair of point indices
242    ///
243    /// # Returns
244    ///
245    /// * A new constrained Delaunay triangulation or an error
246    ///
247    /// # Note
248    ///
249    /// Currently only supports 2D constrained Delaunay triangulation.
250    /// Constraints are edges that must be present in the final triangulation.
251    ///
252    /// # Examples
253    ///
254    /// ```
255    /// use scirs2_spatial::delaunay::Delaunay;
256    /// use ndarray::array;
257    ///
258    /// let points = array![
259    ///     [0.0, 0.0],
260    ///     [1.0, 0.0],
261    ///     [1.0, 1.0],
262    ///     [0.0, 1.0],
263    ///     [0.5, 0.5]
264    /// ];
265    ///
266    /// // Add constraint edges forming a square boundary
267    /// let constraints = vec![(0, 1), (1, 2), (2, 3), (3, 0)];
268    ///
269    /// let tri = Delaunay::new_constrained(&points, constraints).unwrap();
270    /// let simplices = tri.simplices();
271    /// println!("Constrained triangles: {:?}", simplices);
272    /// ```
273    pub fn new_constrained(
274        points: &Array2<f64>,
275        constraints: Vec<(usize, usize)>,
276    ) -> SpatialResult<Self> {
277        let ndim = points.ncols();
278
279        // Support 2D and 3D constrained Delaunay triangulation
280        // Note: 3D implementation supports constraint edges only (not constraint faces)
281        if ndim != 2 && ndim != 3 {
282            return Err(SpatialError::NotImplementedError(
283                "Constrained Delaunay triangulation only supports 2D and 3D points".to_string(),
284            ));
285        }
286
287        // Validate constraints
288        let npoints = points.nrows();
289        for &(i, j) in &constraints {
290            if i >= npoints || j >= npoints {
291                return Err(SpatialError::ValueError(format!(
292                    "Constraint edge ({i}, {j}) contains invalid point indices"
293                )));
294            }
295            if i == j {
296                return Err(SpatialError::ValueError(format!(
297                    "Constraint edge ({i}, {j}) connects a point to itself"
298                )));
299            }
300        }
301
302        // Start with regular Delaunay triangulation
303        let mut delaunay = Self::new(points)?;
304        delaunay.constraints = constraints.clone();
305
306        // Apply constraints using edge insertion algorithm
307        delaunay.insert_constraints()?;
308
309        Ok(delaunay)
310    }
311
312    /// Insert constraint edges into the triangulation
313    fn insert_constraints(&mut self) -> SpatialResult<()> {
314        for &(i, j) in &self.constraints.clone() {
315            self.insert_constraint_edge(i, j)?;
316        }
317        Ok(())
318    }
319
320    /// Insert a single constraint edge into the triangulation
321    fn insert_constraint_edge(&mut self, start: usize, end: usize) -> SpatialResult<()> {
322        // Check if the edge already exists in the triangulation
323        if self.edge_exists(start, end) {
324            return Ok(()); // Edge already exists, nothing to do
325        }
326
327        // Find all edges that intersect with the constraint edge
328        let intersecting_edges = self.find_intersecting_edges(start, end)?;
329
330        if intersecting_edges.is_empty() {
331            // No intersections, but edge doesn't exist - this shouldn't happen in a proper triangulation
332            return Err(SpatialError::ComputationError(
333                "Constraint edge has no intersections but doesn't exist in triangulation"
334                    .to_string(),
335            ));
336        }
337
338        // Remove triangles containing intersecting edges
339        let affected_triangles = self.find_triangles_with_edges(&intersecting_edges);
340        self.remove_triangles(&affected_triangles);
341
342        // Retriangulate the affected region while ensuring the constraint edge is present
343        self.retriangulate_with_constraint(start, end, &affected_triangles)?;
344
345        Ok(())
346    }
347
348    /// Check if an edge exists in the current triangulation
349    fn edge_exists(&self, start: usize, end: usize) -> bool {
350        for simplex in &self.simplices {
351            let simplex_size = simplex.len();
352            // Check all edges of the simplex (triangle in 2D, tetrahedron in 3D)
353            for i in 0..simplex_size {
354                for j in (i + 1)..simplex_size {
355                    let v1 = simplex[i];
356                    let v2 = simplex[j];
357                    if (v1 == start && v2 == end) || (v1 == end && v2 == start) {
358                        return true;
359                    }
360                }
361            }
362        }
363        false
364    }
365
366    /// Find all edges that intersect with the constraint edge
367    fn find_intersecting_edges(
368        &self,
369        start: usize,
370        end: usize,
371    ) -> SpatialResult<Vec<(usize, usize)>> {
372        let mut intersecting = Vec::new();
373
374        // Extract constraint edge points
375        let p1: Vec<f64> = self.points.row(start).to_vec();
376        let p2: Vec<f64> = self.points.row(end).to_vec();
377
378        // Check all edges in the triangulation
379        let mut checked_edges = HashSet::new();
380
381        for simplex in &self.simplices {
382            let simplex_size = simplex.len();
383
384            // Check all edges of the simplex
385            for i in 0..simplex_size {
386                for j in (i + 1)..simplex_size {
387                    let v1 = simplex[i];
388                    let v2 = simplex[j];
389
390                    // Avoid checking the same edge twice
391                    let edge = if v1 < v2 { (v1, v2) } else { (v2, v1) };
392                    if checked_edges.contains(&edge) {
393                        continue;
394                    }
395                    checked_edges.insert(edge);
396
397                    // Skip if this edge shares a vertex with the constraint edge
398                    if v1 == start || v1 == end || v2 == start || v2 == end {
399                        continue;
400                    }
401
402                    let q1: Vec<f64> = self.points.row(v1).to_vec();
403                    let q2: Vec<f64> = self.points.row(v2).to_vec();
404
405                    if self.ndim == 2 {
406                        // 2D case: check for segment intersection
407                        let p1_2d = [p1[0], p1[1]];
408                        let p2_2d = [p2[0], p2[1]];
409                        let q1_2d = [q1[0], q1[1]];
410                        let q2_2d = [q2[0], q2[1]];
411
412                        if Self::segments_intersect(p1_2d, p2_2d, q1_2d, q2_2d) {
413                            intersecting.push((v1, v2));
414                        }
415                    } else if self.ndim == 3 {
416                        // 3D case: check if edges are close enough to interfere
417                        // (simplified approach for constraint enforcement)
418                        if Self::edges_interfere_3d(&p1, &p2, &q1, &q2) {
419                            intersecting.push((v1, v2));
420                        }
421                    }
422                }
423            }
424        }
425
426        Ok(intersecting)
427    }
428
429    /// Check if two line segments intersect
430    fn segments_intersect(p1: [f64; 2], p2: [f64; 2], q1: [f64; 2], q2: [f64; 2]) -> bool {
431        fn orientation(p: [f64; 2], q: [f64; 2], r: [f64; 2]) -> i32 {
432            let val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]);
433            if val.abs() < 1e-10 {
434                0
435            }
436            // Collinear
437            else if val > 0.0 {
438                1
439            }
440            // Clockwise
441            else {
442                2
443            } // Counterclockwise
444        }
445
446        fn on_segment(p: [f64; 2], q: [f64; 2], r: [f64; 2]) -> bool {
447            q[0] <= p[0].max(r[0])
448                && q[0] >= p[0].min(r[0])
449                && q[1] <= p[1].max(r[1])
450                && q[1] >= p[1].min(r[1])
451        }
452
453        let o1 = orientation(p1, p2, q1);
454        let o2 = orientation(p1, p2, q2);
455        let o3 = orientation(q1, q2, p1);
456        let o4 = orientation(q1, q2, p2);
457
458        // General case
459        if o1 != o2 && o3 != o4 {
460            return true;
461        }
462
463        // Special cases - segments are collinear and overlapping
464        if o1 == 0 && on_segment(p1, q1, p2) {
465            return true;
466        }
467        if o2 == 0 && on_segment(p1, q2, p2) {
468            return true;
469        }
470        if o3 == 0 && on_segment(q1, p1, q2) {
471            return true;
472        }
473        if o4 == 0 && on_segment(q1, p2, q2) {
474            return true;
475        }
476
477        false
478    }
479
480    /// Check if two 3D edges interfere enough to require constraint enforcement
481    /// This is a simplified approach using distance-based criteria
482    fn edges_interfere_3d(p1: &[f64], p2: &[f64], q1: &[f64], q2: &[f64]) -> bool {
483        // Calculate the closest distance between the two line segments in 3D
484        let eps = 1e-6; // Distance threshold for interference
485
486        // Vector from p1 to p2
487        let u = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];
488        // Vector from q1 to q2
489        let v = [q2[0] - q1[0], q2[1] - q1[1], q2[2] - q1[2]];
490        // Vector from p1 to q1
491        let w = [q1[0] - p1[0], q1[1] - p1[1], q1[2] - p1[2]];
492
493        let u_dot_u = u[0] * u[0] + u[1] * u[1] + u[2] * u[2];
494        let v_dot_v = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
495        let u_dot_v = u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
496        let u_dot_w = u[0] * w[0] + u[1] * w[1] + u[2] * w[2];
497        let v_dot_w = v[0] * w[0] + v[1] * w[1] + v[2] * w[2];
498
499        let denom = u_dot_u * v_dot_v - u_dot_v * u_dot_v;
500
501        // If lines are parallel, check distance between them
502        if denom.abs() < eps {
503            // Lines are parallel - check if they're close
504            let cross_u_w = [
505                u[1] * w[2] - u[2] * w[1],
506                u[2] * w[0] - u[0] * w[2],
507                u[0] * w[1] - u[1] * w[0],
508            ];
509            let dist_sq = (cross_u_w[0] * cross_u_w[0]
510                + cross_u_w[1] * cross_u_w[1]
511                + cross_u_w[2] * cross_u_w[2])
512                / u_dot_u;
513            return dist_sq < eps * eps;
514        }
515
516        // Calculate closest points on the two line segments
517        let s = (u_dot_v * v_dot_w - v_dot_v * u_dot_w) / denom;
518        let t = (u_dot_u * v_dot_w - u_dot_v * u_dot_w) / denom;
519
520        // Clamp to segment bounds
521        let s_clamped = s.clamp(0.0, 1.0);
522        let t_clamped = t.clamp(0.0, 1.0);
523
524        // Calculate closest points
525        let closest_p = [
526            p1[0] + s_clamped * u[0],
527            p1[1] + s_clamped * u[1],
528            p1[2] + s_clamped * u[2],
529        ];
530        let closest_q = [
531            q1[0] + t_clamped * v[0],
532            q1[1] + t_clamped * v[1],
533            q1[2] + t_clamped * v[2],
534        ];
535
536        // Check if closest points are within interference threshold
537        let dist_sq = (closest_p[0] - closest_q[0]) * (closest_p[0] - closest_q[0])
538            + (closest_p[1] - closest_q[1]) * (closest_p[1] - closest_q[1])
539            + (closest_p[2] - closest_q[2]) * (closest_p[2] - closest_q[2]);
540
541        dist_sq < eps * eps
542    }
543
544    /// Find all triangles that contain any of the given edges
545    fn find_triangles_with_edges(&self, edges: &[(usize, usize)]) -> Vec<usize> {
546        let mut triangles = HashSet::new();
547
548        for (i, simplex) in self.simplices.iter().enumerate() {
549            for &(e1, e2) in edges {
550                if self.triangle_contains_edge(simplex, e1, e2) {
551                    triangles.insert(i);
552                }
553            }
554        }
555
556        triangles.into_iter().collect()
557    }
558
559    /// Check if a triangle contains a specific edge
560    fn triangle_contains_edge(&self, triangle: &[usize], v1: usize, v2: usize) -> bool {
561        for i in 0..3 {
562            let j = (i + 1) % 3;
563            let t1 = triangle[i];
564            let t2 = triangle[j];
565            if (t1 == v1 && t2 == v2) || (t1 == v2 && t2 == v1) {
566                return true;
567            }
568        }
569        false
570    }
571
572    /// Remove triangles from the triangulation
573    fn remove_triangles(&mut self, _triangleindices: &[usize]) {
574        // Sort _indices in descending order to avoid index shifting issues
575        let mut sorted_indices = _triangleindices.to_vec();
576        sorted_indices.sort_by(|a, b| b.cmp(a));
577
578        for &idx in &sorted_indices {
579            if idx < self.simplices.len() {
580                self.simplices.remove(idx);
581                self.neighbors.remove(idx);
582            }
583        }
584    }
585
586    /// Retriangulate a region ensuring the constraint edge is present
587    fn retriangulate_with_constraint(
588        &mut self,
589        start: usize,
590        end: usize,
591        affected_triangles: &[usize],
592    ) -> SpatialResult<()> {
593        if affected_triangles.is_empty() {
594            return Ok(());
595        }
596
597        // Extract all unique vertices from affected _triangles
598        let cavity_vertices = self.extract_cavity_vertices(affected_triangles);
599
600        // Find the boundary edges of the cavity (excluding the constraint edge)
601        let boundary_edges = self.find_cavity_boundary(affected_triangles, start, end)?;
602
603        // Retriangulate the cavity using a simple fan triangulation approach
604        let new_triangles =
605            self.fan_triangulate_cavity(&cavity_vertices, &boundary_edges, start, end)?;
606
607        // Add the new _triangles to the triangulation
608        for triangle in new_triangles {
609            self.simplices.push(triangle);
610        }
611
612        // Update neighbors for the new _triangles (simplified approach)
613        self.compute_neighbors();
614
615        Ok(())
616    }
617
618    /// Extract all unique vertices from the affected triangles
619    fn extract_cavity_vertices(&self, _affectedtriangles: &[usize]) -> Vec<usize> {
620        let mut vertices = HashSet::new();
621
622        for &triangle_idx in _affectedtriangles {
623            if triangle_idx < self.simplices.len() {
624                for &vertex in &self.simplices[triangle_idx] {
625                    vertices.insert(vertex);
626                }
627            }
628        }
629
630        vertices.into_iter().collect()
631    }
632
633    /// Find the boundary edges of the cavity
634    fn find_cavity_boundary(
635        &self,
636        affected_triangles: &[usize],
637        start: usize,
638        end: usize,
639    ) -> SpatialResult<Vec<(usize, usize)>> {
640        let affected_set: HashSet<usize> = affected_triangles.iter().cloned().collect();
641        let mut boundary_edges = Vec::new();
642
643        // For each affected triangle, check each edge
644        for &triangle_idx in affected_triangles {
645            if triangle_idx >= self.simplices.len() {
646                continue;
647            }
648
649            let simplex = &self.simplices[triangle_idx];
650            if simplex.len() < 3 {
651                continue;
652            }
653
654            // Check each edge of the triangle
655            for i in 0..simplex.len() {
656                let v1 = simplex[i];
657                let v2 = simplex[(i + 1) % simplex.len()];
658
659                // Skip the constraint edge itself
660                if (v1 == start && v2 == end) || (v1 == end && v2 == start) {
661                    continue;
662                }
663
664                // Check if this edge is on the boundary (not shared with another affected triangle)
665                if self.is_boundary_edge(v1, v2, &affected_set, triangle_idx) {
666                    boundary_edges.push((v1, v2));
667                }
668            }
669        }
670
671        Ok(boundary_edges)
672    }
673
674    /// Check if an edge is on the boundary of the cavity
675    fn is_boundary_edge(
676        &self,
677        v1: usize,
678        v2: usize,
679        affected_set: &HashSet<usize>,
680        current_triangle: usize,
681    ) -> bool {
682        // Find all triangles that contain this edge
683        for (tri_idx, simplex) in self.simplices.iter().enumerate() {
684            if tri_idx == current_triangle || affected_set.contains(&tri_idx) {
685                continue;
686            }
687
688            // Check if this _triangle contains the edge v1-v2
689            if self.triangle_contains_edge(simplex, v1, v2) {
690                return false; // Edge is shared with a non-affected triangle, so not on boundary
691            }
692        }
693
694        true // Edge is on the boundary
695    }
696
697    /// Retriangulate the cavity using fan triangulation
698    fn fan_triangulate_cavity(
699        &self,
700        cavity_vertices: &[usize],
701        boundary_edges: &[(usize, usize)],
702        start: usize,
703        end: usize,
704    ) -> SpatialResult<Vec<Vec<usize>>> {
705        let mut new_triangles = Vec::new();
706
707        // Find _vertices that are not on the constraint edge
708        let mut interior_vertices = Vec::new();
709        for &vertex in cavity_vertices {
710            if vertex != start && vertex != end {
711                interior_vertices.push(vertex);
712            }
713        }
714
715        // If we have interior vertices, create triangles using fan triangulation
716        if !interior_vertices.is_empty() {
717            // Create fan triangulation from start vertex
718            for i in 0..interior_vertices.len() {
719                for j in (i + 1)..interior_vertices.len() {
720                    let v1 = interior_vertices[i];
721                    let v2 = interior_vertices[j];
722
723                    // Check if we can form a valid triangle
724                    if self.is_valid_triangle_in_cavity(start, v1, v2, boundary_edges) {
725                        new_triangles.push(vec![start, v1, v2]);
726                    }
727
728                    if self.is_valid_triangle_in_cavity(end, v1, v2, boundary_edges) {
729                        new_triangles.push(vec![end, v1, v2]);
730                    }
731                }
732            }
733        }
734
735        // Ensure we have at least one triangle containing the constraint edge
736        if new_triangles.is_empty() && !interior_vertices.is_empty() {
737            let v = interior_vertices[0];
738            new_triangles.push(vec![start, end, v]);
739        }
740
741        // Connect boundary _vertices to constraint edge if needed
742        for &(v1, v2) in boundary_edges {
743            if v1 != start && v1 != end && v2 != start && v2 != end {
744                // Try to connect this boundary edge to the constraint edge
745                if self.points_form_valid_triangle(start, v1, v2) {
746                    new_triangles.push(vec![start, v1, v2]);
747                }
748                if self.points_form_valid_triangle(end, v1, v2) {
749                    new_triangles.push(vec![end, v1, v2]);
750                }
751            }
752        }
753
754        Ok(new_triangles)
755    }
756
757    /// Check if three points form a valid triangle (not collinear)
758    fn points_form_valid_triangle(&self, v1: usize, v2: usize, v3: usize) -> bool {
759        if v1 >= self.npoints || v2 >= self.npoints || v3 >= self.npoints {
760            return false;
761        }
762
763        let p1 = self.points.row(v1);
764        let p2 = self.points.row(v2);
765        let p3 = self.points.row(v3);
766
767        // Check if points are collinear using cross product
768        let dx1 = p2[0] - p1[0];
769        let dy1 = p2[1] - p1[1];
770        let dx2 = p3[0] - p1[0];
771        let dy2 = p3[1] - p1[1];
772
773        let cross = dx1 * dy2 - dy1 * dx2;
774        cross.abs() > 1e-10 // Not collinear
775    }
776
777    /// Check if a triangle is valid within the cavity constraints
778    fn is_valid_triangle_in_cavity(
779        &self,
780        v1: usize,
781        v2: usize,
782        v3: usize,
783        _boundary_edges: &[(usize, usize)],
784    ) -> bool {
785        // Basic validation - check if triangle is not degenerate
786        self.points_form_valid_triangle(v1, v2, v3)
787    }
788
789    /// Recompute neighbors for all simplices
790    fn compute_neighbors(&mut self) {
791        self.neighbors = Self::calculate_neighbors(&self.simplices, self.ndim + 1);
792    }
793
794    /// Get the constraint edges
795    ///
796    /// # Returns
797    ///
798    /// * Vector of constraint edges as pairs of point indices
799    pub fn constraints(&self) -> &[(usize, usize)] {
800        &self.constraints
801    }
802
803    /// Extract simplices from the Qhull instance
804    ///
805    /// # Arguments
806    ///
807    /// * `qh` - The Qhull instance
808    /// * `ndim` - Number of dimensions
809    ///
810    /// # Returns
811    ///
812    /// * Vector of simplices, where each simplex is a vector of vertex indices
813    fn extract_simplices(qh: &Qh, ndim: usize) -> Vec<Vec<usize>> {
814        // Get all simplices (facets) that are not upper_delaunay
815        qh.simplices()
816            .filter(|f| !f.upper_delaunay())
817            .filter_map(|f| {
818                let vertices = match f.vertices() {
819                    Some(v) => v,
820                    None => return None,
821                };
822                // Each vertex corresponds to a point index
823                let indices: Vec<usize> = vertices.iter().filter_map(|v| v.index(qh)).collect();
824
825                // Only keep simplices with the correct number of vertices
826                if indices.len() == ndim + 1 {
827                    Some(indices)
828                } else {
829                    None
830                }
831            })
832            .collect()
833    }
834
835    /// Calculate neighbors of each simplex
836    ///
837    /// # Arguments
838    ///
839    /// * `simplices` - The list of simplices
840    /// * `n` - Number of vertices in a simplex
841    ///
842    /// # Returns
843    ///
844    /// * Vector of neighbor indices for each simplex
845    fn calculate_neighbors(simplices: &[Vec<usize>], n: usize) -> Vec<Vec<i64>> {
846        let nsimplex = simplices.len();
847        let mut neighbors = vec![vec![-1; n]; nsimplex];
848
849        // Build a map from (n-1)-faces to _simplices
850        // A face is represented as a sorted vector of vertex indices
851        let mut face_to_simplex: HashMap<Vec<usize>, Vec<(usize, usize)>> = HashMap::new();
852
853        for (i, simplex) in simplices.iter().enumerate() {
854            for j in 0..n {
855                // Create a face by excluding vertex j
856                let mut face: Vec<usize> = simplex
857                    .iter()
858                    .enumerate()
859                    .filter(|&(k_, _)| k_ != j)
860                    .map(|(_, &v)| v)
861                    .collect();
862
863                // Sort the face for consistent hashing
864                face.sort();
865
866                // Add (simplex_index, excluded_vertex) to the map
867                face_to_simplex.entry(face).or_default().push((i, j));
868            }
869        }
870
871        // For each face shared by two simplices, update the neighbor information
872        for (_, simplex_info) in face_to_simplex.iter() {
873            if simplex_info.len() == 2 {
874                let (i1, j1) = simplex_info[0];
875                let (i2, j2) = simplex_info[1];
876
877                neighbors[i1][j1] = i2 as i64;
878                neighbors[i2][j2] = i1 as i64;
879            }
880        }
881
882        neighbors
883    }
884
885    /// Get the number of points
886    ///
887    /// # Returns
888    ///
889    /// * Number of points in the triangulation
890    pub fn npoints(&self) -> usize {
891        self.npoints
892    }
893
894    /// Get the dimension of the points
895    ///
896    /// # Returns
897    ///
898    /// * Number of dimensions of the points
899    pub fn ndim(&self) -> usize {
900        self.ndim
901    }
902
903    /// Get the points used for triangulation
904    ///
905    /// # Returns
906    ///
907    /// * Array of points
908    pub fn points(&self) -> &Array2<f64> {
909        &self.points
910    }
911
912    /// Get the simplices (triangles in 2D, tetrahedra in 3D, etc.)
913    ///
914    /// # Returns
915    ///
916    /// * Vector of simplices, where each simplex is a vector of vertex indices
917    pub fn simplices(&self) -> &[Vec<usize>] {
918        &self.simplices
919    }
920
921    /// Get the neighbors of each simplex
922    ///
923    /// # Returns
924    ///
925    /// * Vector of neighbor indices for each simplex
926    pub fn neighbors(&self) -> &[Vec<i64>] {
927        &self.neighbors
928    }
929
930    /// Find the simplex containing a given point
931    ///
932    /// # Arguments
933    ///
934    /// * `point` - The point to locate
935    ///
936    /// # Returns
937    ///
938    /// * The index of the simplex containing the point, or None if not found
939    ///
940    /// # Examples
941    ///
942    /// ```
943    /// use scirs2_spatial::delaunay::Delaunay;
944    /// use ndarray::array;
945    ///
946    /// let points = array![
947    ///     [0.0, 0.0],
948    ///     [1.0, 0.0],
949    ///     [0.0, 1.0],
950    ///     [1.0, 1.0]
951    /// ];
952    ///
953    /// let tri = Delaunay::new(&points).unwrap();
954    /// // Try to find which triangle contains the point [0.25, 0.25]
955    /// if let Some(idx) = tri.find_simplex(&[0.25, 0.25]) {
956    ///     println!("Point is in simplex {}", idx);
957    /// }
958    /// ```
959    pub fn find_simplex(&self, point: &[f64]) -> Option<usize> {
960        if point.len() != self.ndim {
961            return None;
962        }
963
964        if self.simplices.is_empty() {
965            return None;
966        }
967
968        // Simple linear search for the containing simplex
969        // More efficient algorithms (walk algorithm) would be preferred
970        // for larger triangulations, but this is a reasonable starting point
971        for (i, simplex) in self.simplices.iter().enumerate() {
972            if self.point_in_simplex(point, simplex) {
973                return Some(i);
974            }
975        }
976
977        None
978    }
979
980    /// Check if a point is inside a simplex
981    ///
982    /// # Arguments
983    ///
984    /// * `point` - The point to check
985    /// * `simplex` - The simplex (indices of vertices)
986    ///
987    /// # Returns
988    ///
989    /// * true if the point is inside the simplex, false otherwise
990    fn point_in_simplex(&self, point: &[f64], simplex: &[usize]) -> bool {
991        if self.ndim == 2 {
992            // For 2D triangles, use barycentric coordinates
993            let a = self.points.row(simplex[0]).to_vec();
994            let b = self.points.row(simplex[1]).to_vec();
995            let c = self.points.row(simplex[2]).to_vec();
996
997            let v0x = b[0] - a[0];
998            let v0y = b[1] - a[1];
999            let v1x = c[0] - a[0];
1000            let v1y = c[1] - a[1];
1001            let v2x = point[0] - a[0];
1002            let v2y = point[1] - a[1];
1003
1004            let d00 = v0x * v0x + v0y * v0y;
1005            let d01 = v0x * v1x + v0y * v1y;
1006            let d11 = v1x * v1x + v1y * v1y;
1007            let d20 = v2x * v0x + v2y * v0y;
1008            let d21 = v2x * v1x + v2y * v1y;
1009
1010            let denom = d00 * d11 - d01 * d01;
1011            if denom.abs() < 1e-10 {
1012                return false; // Degenerate triangle
1013            }
1014
1015            let v = (d11 * d20 - d01 * d21) / denom;
1016            let w = (d00 * d21 - d01 * d20) / denom;
1017            let u = 1.0 - v - w;
1018
1019            // Point is inside if barycentric coordinates are all positive (or zero)
1020            // Allow for small numerical errors
1021            let eps = 1e-10;
1022            return u >= -eps && v >= -eps && w >= -eps;
1023        } else if self.ndim == 3 {
1024            // For 3D tetrahedra, use barycentric coordinates in 3D
1025            let a = self.points.row(simplex[0]).to_vec();
1026            let b = self.points.row(simplex[1]).to_vec();
1027            let c = self.points.row(simplex[2]).to_vec();
1028            let d = self.points.row(simplex[3]).to_vec();
1029
1030            // Compute barycentric coordinates
1031            let mut bary = [0.0; 4];
1032
1033            // Compute volume of tetrahedron
1034            let v0 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
1035            let v1 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
1036            let v2 = [d[0] - a[0], d[1] - a[1], d[2] - a[2]];
1037
1038            // Cross product and determinant for volume
1039            let vol = v0[0] * (v1[1] * v2[2] - v1[2] * v2[1])
1040                - v0[1] * (v1[0] * v2[2] - v1[2] * v2[0])
1041                + v0[2] * (v1[0] * v2[1] - v1[1] * v2[0]);
1042
1043            if vol.abs() < 1e-10 {
1044                return false; // Degenerate tetrahedron
1045            }
1046
1047            // Compute barycentric coordinates
1048            let _vp = [point[0] - a[0], point[1] - a[1], point[2] - a[2]];
1049
1050            let v3 = [c[0] - b[0], c[1] - b[1], c[2] - b[2]];
1051            let v4 = [d[0] - b[0], d[1] - b[1], d[2] - b[2]];
1052            let v5 = [point[0] - b[0], point[1] - b[1], point[2] - b[2]];
1053
1054            bary[0] = (v3[0] * (v4[1] * v5[2] - v4[2] * v5[1])
1055                - v3[1] * (v4[0] * v5[2] - v4[2] * v5[0])
1056                + v3[2] * (v4[0] * v5[1] - v4[1] * v5[0]))
1057                / vol;
1058
1059            let v3 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
1060            let v4 = [d[0] - a[0], d[1] - a[1], d[2] - a[2]];
1061            let v5 = [point[0] - a[0], point[1] - a[1], point[2] - a[2]];
1062
1063            bary[1] = (v3[0] * (v4[1] * v5[2] - v4[2] * v5[1])
1064                - v3[1] * (v4[0] * v5[2] - v4[2] * v5[0])
1065                + v3[2] * (v4[0] * v5[1] - v4[1] * v5[0]))
1066                / vol;
1067
1068            let v3 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
1069            let v4 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
1070            let v5 = [point[0] - a[0], point[1] - a[1], point[2] - a[2]];
1071
1072            bary[2] = (v3[0] * (v4[1] * v5[2] - v4[2] * v5[1])
1073                - v3[1] * (v4[0] * v5[2] - v4[2] * v5[0])
1074                + v3[2] * (v4[0] * v5[1] - v4[1] * v5[0]))
1075                / vol;
1076
1077            bary[3] = 1.0 - bary[0] - bary[1] - bary[2];
1078
1079            // Point is inside if all barycentric coordinates are positive (or zero)
1080            let eps = 1e-10;
1081            return bary.iter().all(|&b| b >= -eps);
1082        }
1083
1084        // For higher dimensions or fallback
1085        false
1086    }
1087
1088    /// Compute the convex hull of the points
1089    ///
1090    /// # Returns
1091    ///
1092    /// * Indices of the points forming the convex hull
1093    ///
1094    /// # Examples
1095    ///
1096    /// ```
1097    /// use scirs2_spatial::delaunay::Delaunay;
1098    /// use ndarray::array;
1099    ///
1100    /// let points = array![
1101    ///     [0.0, 0.0],
1102    ///     [1.0, 0.0],
1103    ///     [0.0, 1.0],
1104    ///     [0.5, 0.5]  // Interior point
1105    /// ];
1106    ///
1107    /// let tri = Delaunay::new(&points).unwrap();
1108    /// let hull = tri.convex_hull();
1109    ///
1110    /// // The hull should be the three corner points, excluding the interior point
1111    /// assert_eq!(hull.len(), 3);
1112    /// ```
1113    pub fn convex_hull(&self) -> Vec<usize> {
1114        let mut hull = HashSet::new();
1115
1116        // In 2D and 3D, the convex hull consists of the simplices with a neighbor of -1
1117        for (i, neighbors) in self.neighbors.iter().enumerate() {
1118            for (j, &neighbor) in neighbors.iter().enumerate() {
1119                if neighbor == -1 {
1120                    // This face is on the convex hull
1121                    // Add all vertices of this face (exclude the vertex opposite to the boundary)
1122                    for k in 0..self.ndim + 1 {
1123                        if k != j {
1124                            hull.insert(self.simplices[i][k]);
1125                        }
1126                    }
1127                }
1128            }
1129        }
1130
1131        // Convert to a sorted vector
1132        let mut hull_vec: Vec<usize> = hull.into_iter().collect();
1133        hull_vec.sort();
1134
1135        hull_vec
1136    }
1137}
1138
1139#[cfg(test)]
1140mod tests {
1141    use super::*;
1142    use ndarray::arr2;
1143    use rand::Rng;
1144    // use approx::assert_relative_eq;
1145
1146    #[test]
1147    fn test_delaunay_simple() {
1148        let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]);
1149
1150        let tri = Delaunay::new(&points).unwrap();
1151
1152        // Should have 2 triangles for 4 points in a square
1153        assert_eq!(tri.simplices().len(), 2);
1154
1155        // Each triangle should have 3 vertices
1156        for simplex in tri.simplices() {
1157            assert_eq!(simplex.len(), 3);
1158
1159            // Each vertex index should be in range
1160            for &idx in simplex {
1161                assert!(idx < points.nrows());
1162            }
1163        }
1164
1165        // Check the convex hull
1166        let hull = tri.convex_hull();
1167        assert_eq!(hull.len(), 4); // All 4 points form the convex hull of the square
1168    }
1169
1170    #[test]
1171    fn test_delaunay_with_interior_point() {
1172        let points = arr2(&[
1173            [0.0, 0.0],
1174            [1.0, 0.0],
1175            [0.0, 1.0],
1176            [0.5, 0.5], // Interior point
1177        ]);
1178
1179        let tri = Delaunay::new(&points).unwrap();
1180
1181        // Should have 3 triangles for this configuration
1182        assert_eq!(tri.simplices().len(), 3);
1183
1184        // Check the convex hull
1185        let hull = tri.convex_hull();
1186        assert_eq!(hull.len(), 3); // The three corner points form the convex hull
1187
1188        // The interior point should not be in the hull
1189        assert!(!hull.contains(&3));
1190    }
1191
1192    #[test]
1193    fn test_delaunay_3d() {
1194        let points = arr2(&[
1195            [0.0, 0.0, 0.0],
1196            [1.0, 0.0, 0.0],
1197            [0.0, 1.0, 0.0],
1198            [0.0, 0.0, 1.0],
1199            [1.0, 1.0, 1.0],
1200        ]);
1201
1202        let tri = Delaunay::new(&points).unwrap();
1203
1204        // Each simplex should have 4 vertices (tetrahedron in 3D)
1205        for simplex in tri.simplices() {
1206            assert_eq!(simplex.len(), 4);
1207        }
1208    }
1209
1210    #[test]
1211    fn test_find_simplex() {
1212        let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
1213
1214        let tri = Delaunay::new(&points).unwrap();
1215
1216        // Point inside the triangle
1217        let inside_point = [0.3, 0.3];
1218        assert!(tri.find_simplex(&inside_point).is_some());
1219
1220        // Point outside the triangle
1221        let outside_point = [1.5, 1.5];
1222        assert!(tri.find_simplex(&outside_point).is_none());
1223    }
1224
1225    #[test]
1226    fn test_random_points_2d() {
1227        // Generate some random points
1228        let mut rng = rand::rng();
1229
1230        let n = 20;
1231        let mut points_data = Vec::with_capacity(n * 2);
1232
1233        for _ in 0..n {
1234            points_data.push(rng.gen_range(0.0..1.0));
1235            points_data.push(rng.gen_range(0.0..1.0));
1236        }
1237
1238        let points = Array2::from_shape_vec((n, 2), points_data).unwrap();
1239
1240        let tri = Delaunay::new(&points).unwrap();
1241
1242        // Basic checks
1243        assert_eq!(tri.ndim(), 2);
1244        assert_eq!(tri.npoints(), n);
1245
1246        // Each simplex should have 3 valid vertex indices
1247        for simplex in tri.simplices() {
1248            assert_eq!(simplex.len(), 3);
1249            for &idx in simplex {
1250                assert!(idx < n);
1251            }
1252        }
1253    }
1254
1255    #[test]
1256    fn test_constrained_delaunay_basic() {
1257        let points = arr2(&[
1258            [0.0, 0.0],
1259            [1.0, 0.0],
1260            [1.0, 1.0],
1261            [0.0, 1.0],
1262            [0.5, 0.5], // Interior point
1263        ]);
1264
1265        // Add constraint edges forming a square boundary
1266        let constraints = vec![(0, 1), (1, 2), (2, 3), (3, 0)];
1267
1268        let tri = Delaunay::new_constrained(&points, constraints.clone()).unwrap();
1269
1270        // Check that constraints are stored
1271        assert_eq!(tri.constraints().len(), 4);
1272        for &constraint in &constraints {
1273            assert!(tri.constraints().contains(&constraint));
1274        }
1275
1276        // Check that we have a valid triangulation
1277        assert!(tri.simplices().len() >= 2); // At least 2 triangles for this configuration
1278    }
1279
1280    #[test]
1281    fn test_constrained_delaunay_invalid_constraints() {
1282        let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]);
1283
1284        // Invalid constraint with out-of-bounds index
1285        let invalid_constraints = vec![(0, 5)];
1286        let result = Delaunay::new_constrained(&points, invalid_constraints);
1287        assert!(result.is_err());
1288
1289        // Invalid constraint connecting point to itself
1290        let self_constraint = vec![(0, 0)];
1291        let result = Delaunay::new_constrained(&points, self_constraint);
1292        assert!(result.is_err());
1293    }
1294
1295    #[test]
1296    fn test_constrained_delaunay_3d_error() {
1297        let points_3d = arr2(&[
1298            [0.0, 0.0, 0.0],
1299            [1.0, 0.0, 0.0],
1300            [0.0, 1.0, 0.0],
1301            [0.0, 0.0, 1.0],
1302        ]);
1303
1304        let constraints = vec![(0, 1)];
1305        let result = Delaunay::new_constrained(&points_3d, constraints);
1306        assert!(result.is_err()); // Should fail for 3D points
1307    }
1308
1309    #[test]
1310    fn test_edge_exists() {
1311        let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
1312        let tri = Delaunay::new(&points).unwrap();
1313
1314        // Check if edges exist in the triangle
1315        assert!(tri.edge_exists(0, 1) || tri.edge_exists(1, 0));
1316        assert!(tri.edge_exists(1, 2) || tri.edge_exists(2, 1));
1317        assert!(tri.edge_exists(0, 2) || tri.edge_exists(2, 0));
1318    }
1319
1320    #[test]
1321    fn test_segments_intersect() {
1322        // Test intersecting segments
1323        let p1 = [0.0, 0.0];
1324        let p2 = [1.0, 1.0];
1325        let q1 = [0.0, 1.0];
1326        let q2 = [1.0, 0.0];
1327        assert!(Delaunay::segments_intersect(p1, p2, q1, q2));
1328
1329        // Test non-intersecting segments
1330        let p1 = [0.0, 0.0];
1331        let p2 = [1.0, 0.0];
1332        let q1 = [0.0, 1.0];
1333        let q2 = [1.0, 1.0];
1334        assert!(!Delaunay::segments_intersect(p1, p2, q1, q2));
1335
1336        // Test collinear overlapping segments
1337        let p1 = [0.0, 0.0];
1338        let p2 = [2.0, 0.0];
1339        let q1 = [1.0, 0.0];
1340        let q2 = [3.0, 0.0];
1341        assert!(Delaunay::segments_intersect(p1, p2, q1, q2));
1342    }
1343}