Skip to main content

terrain_forge/analysis/
delaunay.rs

1//! Delaunay triangulation for natural room connections
2
3use crate::{Cell, Grid};
4use std::collections::HashSet;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct Point {
8    pub x: f32,
9    pub y: f32,
10}
11
12impl Point {
13    pub fn new(x: f32, y: f32) -> Self {
14        Self { x, y }
15    }
16
17    pub fn distance_to(&self, other: &Point) -> f32 {
18        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
19    }
20}
21
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct Triangle {
24    pub a: usize,
25    pub b: usize,
26    pub c: usize,
27}
28
29impl Triangle {
30    pub fn new(a: usize, b: usize, c: usize) -> Self {
31        Self { a, b, c }
32    }
33
34    pub fn contains_vertex(&self, vertex: usize) -> bool {
35        self.a == vertex || self.b == vertex || self.c == vertex
36    }
37
38    pub fn circumcenter(&self, points: &[Point]) -> Point {
39        let pa = points[self.a];
40        let pb = points[self.b];
41        let pc = points[self.c];
42
43        let d = 2.0 * (pa.x * (pb.y - pc.y) + pb.x * (pc.y - pa.y) + pc.x * (pa.y - pb.y));
44
45        if d.abs() < f32::EPSILON {
46            // Degenerate triangle, return centroid
47            return Point::new((pa.x + pb.x + pc.x) / 3.0, (pa.y + pb.y + pc.y) / 3.0);
48        }
49
50        let ux = (pa.x * pa.x + pa.y * pa.y) * (pb.y - pc.y)
51            + (pb.x * pb.x + pb.y * pb.y) * (pc.y - pa.y)
52            + (pc.x * pc.x + pc.y * pc.y) * (pa.y - pb.y);
53
54        let uy = (pa.x * pa.x + pa.y * pa.y) * (pc.x - pb.x)
55            + (pb.x * pb.x + pb.y * pb.y) * (pa.x - pc.x)
56            + (pc.x * pc.x + pc.y * pc.y) * (pb.x - pa.x);
57
58        Point::new(ux / d, uy / d)
59    }
60
61    pub fn circumradius(&self, points: &[Point]) -> f32 {
62        let center = self.circumcenter(points);
63        center.distance_to(&points[self.a])
64    }
65
66    pub fn in_circumcircle(&self, points: &[Point], point: &Point) -> bool {
67        let center = self.circumcenter(points);
68        let radius = self.circumradius(points);
69        center.distance_to(point) < radius + f32::EPSILON
70    }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74pub struct Edge {
75    pub a: usize,
76    pub b: usize,
77}
78
79impl Edge {
80    pub fn new(a: usize, b: usize) -> Self {
81        if a < b {
82            Self { a, b }
83        } else {
84            Self { a: b, b: a }
85        }
86    }
87
88    pub fn length(&self, points: &[Point]) -> f32 {
89        points[self.a].distance_to(&points[self.b])
90    }
91}
92
93pub struct DelaunayTriangulation {
94    pub points: Vec<Point>,
95    pub triangles: Vec<Triangle>,
96    pub edges: Vec<Edge>,
97}
98
99impl DelaunayTriangulation {
100    pub fn new(points: Vec<Point>) -> Self {
101        let mut triangulation = Self {
102            points,
103            triangles: Vec::new(),
104            edges: Vec::new(),
105        };
106
107        if triangulation.points.len() >= 3 {
108            triangulation.triangulate();
109        }
110
111        triangulation
112    }
113
114    fn triangulate(&mut self) {
115        if self.points.len() < 3 {
116            return;
117        }
118
119        // Create super triangle that contains all points
120        let (min_x, max_x, min_y, max_y) = self.bounding_box();
121        let dx = max_x - min_x;
122        let dy = max_y - min_y;
123        let delta_max = dx.max(dy);
124        let mid_x = (min_x + max_x) / 2.0;
125        let mid_y = (min_y + max_y) / 2.0;
126
127        let super_a = self.points.len();
128        let super_b = self.points.len() + 1;
129        let super_c = self.points.len() + 2;
130
131        self.points
132            .push(Point::new(mid_x - 20.0 * delta_max, mid_y - delta_max));
133        self.points
134            .push(Point::new(mid_x, mid_y + 20.0 * delta_max));
135        self.points
136            .push(Point::new(mid_x + 20.0 * delta_max, mid_y - delta_max));
137
138        self.triangles
139            .push(Triangle::new(super_a, super_b, super_c));
140
141        // Add points one by one
142        for i in 0..super_a {
143            let point = self.points[i];
144            let mut bad_triangles = Vec::new();
145            let mut polygon = Vec::new();
146
147            // Find bad triangles (those whose circumcircle contains the point)
148            for (j, triangle) in self.triangles.iter().enumerate() {
149                if triangle.in_circumcircle(&self.points, &point) {
150                    bad_triangles.push(j);
151                }
152            }
153
154            // Find the boundary of the polygonal hole
155            for &bad_idx in &bad_triangles {
156                let bad_tri = self.triangles[bad_idx];
157                let edges = [
158                    Edge::new(bad_tri.a, bad_tri.b),
159                    Edge::new(bad_tri.b, bad_tri.c),
160                    Edge::new(bad_tri.c, bad_tri.a),
161                ];
162
163                for edge in edges {
164                    let mut is_shared = false;
165                    for &other_idx in &bad_triangles {
166                        if other_idx != bad_idx {
167                            let other_tri = self.triangles[other_idx];
168                            let other_edges = [
169                                Edge::new(other_tri.a, other_tri.b),
170                                Edge::new(other_tri.b, other_tri.c),
171                                Edge::new(other_tri.c, other_tri.a),
172                            ];
173                            if other_edges.contains(&edge) {
174                                is_shared = true;
175                                break;
176                            }
177                        }
178                    }
179                    if !is_shared {
180                        polygon.push(edge);
181                    }
182                }
183            }
184
185            // Remove bad triangles
186            bad_triangles.sort_by(|a, b| b.cmp(a));
187            for &idx in &bad_triangles {
188                self.triangles.remove(idx);
189            }
190
191            // Create new triangles from the polygon
192            for edge in polygon {
193                self.triangles.push(Triangle::new(edge.a, edge.b, i));
194            }
195        }
196
197        // Remove triangles that contain super triangle vertices
198        self.triangles.retain(|tri| {
199            !tri.contains_vertex(super_a)
200                && !tri.contains_vertex(super_b)
201                && !tri.contains_vertex(super_c)
202        });
203
204        // Remove super triangle points
205        self.points.truncate(super_a);
206
207        // Extract edges
208        let mut edge_set = HashSet::new();
209        for triangle in &self.triangles {
210            edge_set.insert(Edge::new(triangle.a, triangle.b));
211            edge_set.insert(Edge::new(triangle.b, triangle.c));
212            edge_set.insert(Edge::new(triangle.c, triangle.a));
213        }
214        self.edges = edge_set.into_iter().collect();
215    }
216
217    fn bounding_box(&self) -> (f32, f32, f32, f32) {
218        if self.points.is_empty() {
219            return (0.0, 0.0, 0.0, 0.0);
220        }
221
222        let mut min_x = self.points[0].x;
223        let mut max_x = self.points[0].x;
224        let mut min_y = self.points[0].y;
225        let mut max_y = self.points[0].y;
226
227        for point in &self.points {
228            min_x = min_x.min(point.x);
229            max_x = max_x.max(point.x);
230            min_y = min_y.min(point.y);
231            max_y = max_y.max(point.y);
232        }
233
234        (min_x, max_x, min_y, max_y)
235    }
236
237    pub fn minimum_spanning_tree(&self) -> Vec<Edge> {
238        if self.edges.is_empty() {
239            return Vec::new();
240        }
241
242        let mut mst_edges = Vec::new();
243        let mut edges = self.edges.clone();
244        edges.sort_by(|a, b| {
245            a.length(&self.points)
246                .partial_cmp(&b.length(&self.points))
247                .unwrap()
248        });
249
250        let mut parent: Vec<usize> = (0..self.points.len()).collect();
251
252        fn find(parent: &mut [usize], x: usize) -> usize {
253            if parent[x] != x {
254                parent[x] = find(parent, parent[x]);
255            }
256            parent[x]
257        }
258
259        fn union(parent: &mut [usize], x: usize, y: usize) {
260            let px = find(parent, x);
261            let py = find(parent, y);
262            if px != py {
263                parent[px] = py;
264            }
265        }
266
267        for edge in edges {
268            let root_a = find(&mut parent, edge.a);
269            let root_b = find(&mut parent, edge.b);
270
271            if root_a != root_b {
272                mst_edges.push(edge);
273                union(&mut parent, edge.a, edge.b);
274
275                if mst_edges.len() == self.points.len() - 1 {
276                    break;
277                }
278            }
279        }
280
281        mst_edges
282    }
283}
284
285/// Connect rooms using Delaunay triangulation
286pub fn connect_rooms<C: Cell>(grid: &mut Grid<C>, room_centers: &[Point]) -> Vec<Edge> {
287    if room_centers.len() < 2 {
288        return Vec::new();
289    }
290
291    let triangulation = DelaunayTriangulation::new(room_centers.to_vec());
292    let mst = triangulation.minimum_spanning_tree();
293
294    // Draw connections on grid (simplified - just mark as passable)
295    for edge in &mst {
296        let start = triangulation.points[edge.a];
297        let end = triangulation.points[edge.b];
298        draw_line(grid, start, end);
299    }
300
301    mst
302}
303
304fn draw_line<C: Cell>(grid: &mut Grid<C>, start: Point, end: Point) {
305    let dx = (end.x - start.x).abs();
306    let dy = (end.y - start.y).abs();
307    let steps = (dx.max(dy) as usize).max(1);
308
309    for i in 0..=steps {
310        let t = i as f32 / steps as f32;
311        let x = (start.x + t * (end.x - start.x)) as i32;
312        let y = (start.y + t * (end.y - start.y)) as i32;
313
314        if let Some(cell) = grid.get_mut(x, y) {
315            if !cell.is_passable() {
316                // This is a simplified approach - in practice you'd want to set to Floor
317                // but we can't do that generically with the Cell trait
318            }
319        }
320    }
321}