Skip to main content

oxiphysics_geometry/computational_geometry/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5// TODO(P4): public API churn — Point2::add/sub should become ops::Add/Sub impls
6use super::functions::*;
7use super::functions::{FaceId, HalfEdgeId, Point3, VertexId};
8
9/// A Voronoi cell: the site point and its circumcenter-based Voronoi vertices.
10#[derive(Debug, Clone)]
11pub struct VoronoiCell2D {
12    /// The generating site index.
13    pub site: usize,
14    /// Circumcenters of Delaunay triangles that form the Voronoi polygon vertices.
15    pub circumcenters: Vec<Point2>,
16}
17/// A face in a line arrangement (a convex polygon cell bounded by arrangement edges).
18#[derive(Debug, Clone)]
19pub struct ArrangementFace {
20    /// Indices of bounding arrangement vertices (in order).
21    pub vertex_indices: Vec<usize>,
22}
23/// An arrangement of lines in the plane.
24///
25/// Stores the vertices (pairwise intersections) and adjacency information.
26#[derive(Debug, Clone, Default)]
27pub struct LineArrangement {
28    /// The input lines.
29    pub lines: Vec<Line2D>,
30    /// All vertices of the arrangement (pairwise intersections).
31    pub vertices: Vec<ArrangementVertex>,
32}
33impl LineArrangement {
34    /// Construct the arrangement from a set of lines.
35    ///
36    /// Computes all O(n²) pairwise intersections, merging near-coincident ones.
37    pub fn build(lines: &[Line2D]) -> Self {
38        let n = lines.len();
39        let mut vertices: Vec<ArrangementVertex> = Vec::new();
40        let merge_eps = 1e-9;
41        for i in 0..n {
42            for j in (i + 1)..n {
43                if let Some(pt) = line_intersect_2d(&lines[i], &lines[j]) {
44                    let existing = vertices
45                        .iter_mut()
46                        .find(|v| v.point.dist_sq(pt) < merge_eps);
47                    if let Some(v) = existing {
48                        if !v.line_indices.contains(&i) {
49                            v.line_indices.push(i);
50                        }
51                        if !v.line_indices.contains(&j) {
52                            v.line_indices.push(j);
53                        }
54                    } else {
55                        vertices.push(ArrangementVertex {
56                            point: pt,
57                            line_indices: vec![i, j],
58                        });
59                    }
60                }
61            }
62        }
63        Self {
64            lines: lines.to_vec(),
65            vertices,
66        }
67    }
68    /// Number of vertices (intersection points).
69    pub fn num_vertices(&self) -> usize {
70        self.vertices.len()
71    }
72    /// By Euler's formula V - E + F = 2, compute the number of faces
73    /// (including the unbounded outer face) from V and E.
74    ///
75    /// For n lines in general position: V = n(n-1)/2, E = n(n+1), F = n(n-1)/2 + n + 1.
76    pub fn euler_face_count(&self) -> usize {
77        let n = self.lines.len();
78        n * (n - 1) / 2 + n + 1
79    }
80    /// Sort the vertices along a given line by arc-length parameter.
81    pub fn vertices_on_line(&self, line_idx: usize) -> Vec<usize> {
82        let mut idxs: Vec<usize> = self
83            .vertices
84            .iter()
85            .enumerate()
86            .filter(|(_, v)| v.line_indices.contains(&line_idx))
87            .map(|(i, _)| i)
88            .collect();
89        let l = &self.lines[line_idx];
90        let dir = Point2::new(-l.b, l.a);
91        idxs.sort_by(|&a, &b| {
92            let ta = dir.dot(self.vertices[a].point);
93            let tb = dir.dot(self.vertices[b].point);
94            ta.partial_cmp(&tb).unwrap_or(std::cmp::Ordering::Equal)
95        });
96        idxs
97    }
98}
99/// A face in the half-edge mesh, storing one of its bounding half-edges.
100#[derive(Debug, Clone)]
101pub struct HEFace {
102    /// One half-edge on the boundary of this face.
103    pub half_edge: HalfEdgeId,
104}
105/// A single directed half-edge.
106#[derive(Debug, Clone)]
107pub struct HalfEdge {
108    /// Origin vertex of this half-edge.
109    pub origin: VertexId,
110    /// The twin (opposite) half-edge.
111    pub twin: HalfEdgeId,
112    /// The next half-edge around the face (counter-clockwise).
113    pub next: HalfEdgeId,
114    /// The previous half-edge around the face.
115    pub prev: HalfEdgeId,
116    /// The face to the left of this half-edge.
117    pub face: Option<FaceId>,
118}
119/// A visibility graph for path planning among 2D polygon obstacles.
120///
121/// Nodes are the vertices of all obstacles plus optional source/target points.
122/// Edges connect pairs of nodes that can see each other (the open segment
123/// between them does not cross any obstacle edge).
124#[derive(Debug, Clone, Default)]
125pub struct VisibilityGraph {
126    /// All nodes (vertices + queries).
127    pub nodes: Vec<Point2>,
128    /// Adjacency list: `edges[i]` = list of node indices visible from node `i`.
129    pub edges: Vec<Vec<usize>>,
130}
131impl VisibilityGraph {
132    /// Build the visibility graph from a collection of obstacles.
133    pub fn build(obstacles: &[Obstacle2D]) -> Self {
134        let mut nodes: Vec<Point2> = Vec::new();
135        for obs in obstacles {
136            for &v in &obs.vertices {
137                nodes.push(v);
138            }
139        }
140        let all_edges: Vec<(Point2, Point2)> = obstacles.iter().flat_map(|o| o.edges()).collect();
141        let n = nodes.len();
142        let mut edges = vec![Vec::new(); n];
143        for i in 0..n {
144            for j in 0..n {
145                if i == j {
146                    continue;
147                }
148                if segment_visible(nodes[i], nodes[j], &all_edges) {
149                    edges[i].push(j);
150                }
151            }
152        }
153        Self { nodes, edges }
154    }
155    /// Add a query point (e.g., start or goal) and compute its visibility to existing nodes.
156    pub fn add_query_point(&mut self, pt: Point2, obstacles: &[Obstacle2D]) {
157        let all_edges: Vec<(Point2, Point2)> = obstacles.iter().flat_map(|o| o.edges()).collect();
158        let n = self.nodes.len();
159        let new_id = n;
160        self.nodes.push(pt);
161        self.edges.push(Vec::new());
162        for i in 0..n {
163            if segment_visible(pt, self.nodes[i], &all_edges) {
164                self.edges[new_id].push(i);
165                self.edges[i].push(new_id);
166            }
167        }
168    }
169    /// Number of nodes in the graph.
170    pub fn num_nodes(&self) -> usize {
171        self.nodes.len()
172    }
173    /// Shortest path (by Euclidean distance) from `src` to `dst` using Dijkstra.
174    pub fn shortest_path(&self, src: usize, dst: usize) -> Option<Vec<usize>> {
175        use std::cmp::Reverse;
176        use std::collections::BinaryHeap;
177        let n = self.nodes.len();
178        let mut dist = vec![f64::INFINITY; n];
179        let mut prev = vec![usize::MAX; n];
180        dist[src] = 0.0;
181        let mut heap: BinaryHeap<Reverse<(u64, usize)>> = BinaryHeap::new();
182        heap.push(Reverse((0, src)));
183        while let Some(Reverse((d_raw, u))) = heap.pop() {
184            let d = d_raw as f64 / 1e9;
185            if d > dist[u] + 1e-12 {
186                continue;
187            }
188            if u == dst {
189                break;
190            }
191            for &v in &self.edges[u] {
192                let nd = dist[u] + self.nodes[u].dist(self.nodes[v]);
193                if nd < dist[v] - 1e-12 {
194                    dist[v] = nd;
195                    prev[v] = u;
196                    heap.push(Reverse(((nd * 1e9) as u64, v)));
197                }
198            }
199        }
200        if dist[dst].is_infinite() {
201            return None;
202        }
203        let mut path = Vec::new();
204        let mut cur = dst;
205        while cur != usize::MAX {
206            path.push(cur);
207            cur = prev[cur];
208        }
209        path.reverse();
210        Some(path)
211    }
212}
213/// A vertex in the half-edge mesh, storing a representative outgoing half-edge.
214#[derive(Debug, Clone)]
215pub struct HEVertex {
216    /// Position of this vertex.
217    pub position: Point3,
218    /// An outgoing half-edge from this vertex.
219    pub half_edge: Option<HalfEdgeId>,
220}
221/// A line in the plane represented in the form ax + by = c.
222#[derive(Debug, Clone, Copy)]
223pub struct Line2D {
224    /// Coefficient a.
225    pub a: f64,
226    /// Coefficient b.
227    pub b: f64,
228    /// Right-hand side c.
229    pub c: f64,
230}
231impl Line2D {
232    /// Construct a line through two points.
233    pub fn through(p: Point2, q: Point2) -> Self {
234        let a = q.y - p.y;
235        let b = p.x - q.x;
236        let c = a * p.x + b * p.y;
237        Self { a, b, c }
238    }
239    /// Signed distance of point p from this line (positive on the a·x+b·y>c side).
240    pub fn signed_dist(&self, p: Point2) -> f64 {
241        let norm = (self.a * self.a + self.b * self.b).sqrt();
242        if norm < 1e-15 {
243            return 0.0;
244        }
245        (self.a * p.x + self.b * p.y - self.c) / norm
246    }
247    /// Evaluate which side of the line the point is on.
248    /// Returns positive if on the left, negative if on the right, zero if on the line.
249    pub fn side(&self, p: Point2) -> f64 {
250        self.a * p.x + self.b * p.y - self.c
251    }
252}
253/// A trapezoid in the trapezoidal decomposition of a planar subdivision.
254///
255/// Bounded by left and right endpoints and top/bottom line segments.
256#[derive(Debug, Clone)]
257pub struct Trapezoid {
258    /// Left x-boundary.
259    pub x_left: f64,
260    /// Right x-boundary.
261    pub x_right: f64,
262    /// Bottom edge index (index into segment list, or `usize::MAX` for floor).
263    pub bottom_seg: usize,
264    /// Top edge index (index into segment list, or `usize::MAX` for ceiling).
265    pub top_seg: usize,
266    /// Face label (e.g., triangle index).
267    pub face: Option<usize>,
268}
269/// A triangle in a 2D Delaunay triangulation (vertex indices into a point list).
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub struct DelaunayTri {
272    /// First vertex index.
273    pub a: usize,
274    /// Second vertex index.
275    pub b: usize,
276    /// Third vertex index.
277    pub c: usize,
278}
279impl DelaunayTri {
280    /// Create a new Delaunay triangle from vertex indices.
281    pub fn new(a: usize, b: usize, c: usize) -> Self {
282        Self { a, b, c }
283    }
284}
285/// A line segment in 2D for slab-based point location.
286#[derive(Debug, Clone, Copy)]
287pub struct Segment2D {
288    /// Left endpoint.
289    pub left: Point2,
290    /// Right endpoint.
291    pub right: Point2,
292}
293impl Segment2D {
294    /// Create a new segment, normalising so `left.x <= right.x`.
295    pub fn new(a: Point2, b: Point2) -> Self {
296        if a.x <= b.x {
297            Self { left: a, right: b }
298        } else {
299            Self { left: b, right: a }
300        }
301    }
302    /// Y-value on the segment at x (by linear interpolation).
303    pub fn y_at(&self, x: f64) -> Option<f64> {
304        let dx = self.right.x - self.left.x;
305        if dx.abs() < 1e-15 {
306            return None;
307        }
308        let t = (x - self.left.x) / dx;
309        if !(0.0..=1.0).contains(&t) {
310            return None;
311        }
312        Some(self.left.y + t * (self.right.y - self.left.y))
313    }
314}
315/// Slab-based point location structure.
316///
317/// Partitions the plane into vertical slabs at the x-coordinates of all
318/// segment endpoints, then stores the ordered set of segments in each slab.
319/// Query time: O(log n) slabs + O(log k) binary search within a slab.
320#[derive(Debug, Clone, Default)]
321pub struct SlabPointLocator {
322    /// Sorted distinct x-coordinates of all endpoints.
323    pub slab_xs: Vec<f64>,
324    /// For each slab i (between slab_xs\[i\] and slab_xs\[i+1\]),
325    /// the list of segment indices that cross that slab, sorted top-to-bottom
326    /// at the slab midpoint.
327    pub slab_segments: Vec<Vec<usize>>,
328    /// The segments.
329    pub segments: Vec<Segment2D>,
330    /// Face labels per trapezoid cell: slab_faces\[slab\]\[k\] is the face below
331    /// segments\[slab_segments\[slab\\]\[k\]].
332    pub slab_faces: Vec<Vec<Option<usize>>>,
333}
334impl SlabPointLocator {
335    /// Build the slab structure from a list of segments and optional face labels.
336    ///
337    /// `face_labels[i]` is the face above `segments[i]` (optional).
338    pub fn build(segments: &[Segment2D], _face_labels: &[Option<usize>]) -> Self {
339        let mut xs: Vec<f64> = Vec::new();
340        for s in segments {
341            xs.push(s.left.x);
342            xs.push(s.right.x);
343        }
344        xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
345        xs.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
346        let n_slabs = if xs.len() >= 2 { xs.len() - 1 } else { 0 };
347        let mut slab_segments = vec![Vec::new(); n_slabs];
348        let mut slab_faces = vec![Vec::new(); n_slabs];
349        for slab_i in 0..n_slabs {
350            let x_mid = (xs[slab_i] + xs[slab_i + 1]) / 2.0;
351            let mut active: Vec<(f64, usize)> = segments
352                .iter()
353                .enumerate()
354                .filter_map(|(idx, s)| s.y_at(x_mid).map(|y| (y, idx)))
355                .collect();
356            active.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
357            slab_segments[slab_i] = active.iter().map(|(_, idx)| *idx).collect();
358            slab_faces[slab_i] = active.iter().map(|_| None).collect();
359        }
360        Self {
361            slab_xs: xs,
362            slab_segments,
363            segments: segments.to_vec(),
364            slab_faces,
365        }
366    }
367    /// Locate a query point: returns the index of the first segment strictly
368    /// above the point in the slab, or `None` if the point is above all segments.
369    pub fn locate(&self, p: Point2) -> Option<usize> {
370        let slab_i = self
371            .slab_xs
372            .partition_point(|&x| x <= p.x)
373            .saturating_sub(1);
374        if slab_i >= self.slab_segments.len() {
375            return None;
376        }
377        for &seg_idx in &self.slab_segments[slab_i] {
378            if let Some(y) = self.segments[seg_idx].y_at(p.x)
379                && y >= p.y
380            {
381                return Some(seg_idx);
382            }
383        }
384        None
385    }
386}
387/// A face of the 3D convex hull: three vertex indices with outward normal.
388#[derive(Debug, Clone)]
389pub struct ConvexFace3D {
390    /// Indices of the three vertices (counter-clockwise when viewed from outside).
391    pub verts: [usize; 3],
392    /// Outward-facing unit normal.
393    pub normal: Point3,
394}
395/// A vertex in a line arrangement: the intersection of two or more lines.
396#[derive(Debug, Clone)]
397pub struct ArrangementVertex {
398    /// The intersection point.
399    pub point: Point2,
400    /// Indices of lines passing through this vertex.
401    pub line_indices: Vec<usize>,
402}
403/// A 2D polygon obstacle (simple, non-self-intersecting).
404#[derive(Debug, Clone)]
405pub struct Obstacle2D {
406    /// Vertices of the obstacle polygon (in order).
407    pub vertices: Vec<Point2>,
408}
409impl Obstacle2D {
410    /// Construct an obstacle from a vertex list.
411    pub fn new(vertices: Vec<Point2>) -> Self {
412        Self { vertices }
413    }
414    /// Collect all edges as (Point2, Point2) pairs.
415    pub fn edges(&self) -> Vec<(Point2, Point2)> {
416        let n = self.vertices.len();
417        (0..n)
418            .map(|i| (self.vertices[i], self.vertices[(i + 1) % n]))
419            .collect()
420    }
421}
422/// A point in 2D space.
423#[derive(Debug, Clone, Copy, PartialEq)]
424pub struct Point2 {
425    /// X coordinate.
426    pub x: f64,
427    /// Y coordinate.
428    pub y: f64,
429}
430impl Point2 {
431    /// Construct a new 2D point.
432    pub fn new(x: f64, y: f64) -> Self {
433        Self { x, y }
434    }
435    /// Scale by a scalar.
436    pub fn scale(self, t: f64) -> Self {
437        Self::new(self.x * t, self.y * t)
438    }
439    /// 2D cross product (scalar z-component of the 3D cross product).
440    pub fn cross(self, other: Self) -> f64 {
441        self.x * other.y - self.y * other.x
442    }
443    /// Dot product.
444    pub fn dot(self, other: Self) -> f64 {
445        self.x * other.x + self.y * other.y
446    }
447    /// Euclidean distance squared to another point.
448    pub fn dist_sq(self, other: Self) -> f64 {
449        let d = self - other;
450        d.dot(d)
451    }
452    /// Euclidean distance to another point.
453    pub fn dist(self, other: Self) -> f64 {
454        self.dist_sq(other).sqrt()
455    }
456    /// 2D cross product of vectors (p1-p0) and (p2-p0).
457    pub fn cross2(p0: Self, p1: Self, p2: Self) -> f64 {
458        (p1 - p0).cross(p2 - p0)
459    }
460}
461impl std::ops::Sub for Point2 {
462    type Output = Self;
463    fn sub(self, other: Self) -> Self {
464        Self::new(self.x - other.x, self.y - other.y)
465    }
466}
467impl std::ops::Add for Point2 {
468    type Output = Self;
469    fn add(self, other: Self) -> Self {
470        Self::new(self.x + other.x, self.y + other.y)
471    }
472}
473/// A manifold polygon mesh represented with the half-edge data structure.
474///
475/// Supports O(1) adjacency queries: vertex–face, face–face, and edge–edge.
476#[derive(Debug, Clone, Default)]
477pub struct HalfEdgeMesh {
478    /// All half-edges.
479    pub half_edges: Vec<HalfEdge>,
480    /// All vertices.
481    pub vertices: Vec<HEVertex>,
482    /// All faces.
483    pub faces: Vec<HEFace>,
484}
485impl HalfEdgeMesh {
486    /// Create an empty half-edge mesh.
487    pub fn new() -> Self {
488        Self::default()
489    }
490    /// Add a vertex at the given position and return its [`VertexId`].
491    pub fn add_vertex(&mut self, pos: Point3) -> VertexId {
492        let id = self.vertices.len();
493        self.vertices.push(HEVertex {
494            position: pos,
495            half_edge: None,
496        });
497        id
498    }
499    /// Add a triangular face from three vertex indices (counter-clockwise winding).
500    ///
501    /// Allocates three half-edges and one face record.  Twin links are resolved
502    /// lazily by `build_twin_links`.
503    pub fn add_triangle(&mut self, v0: VertexId, v1: VertexId, v2: VertexId) -> FaceId {
504        let face_id = self.faces.len();
505        let he0 = self.half_edges.len();
506        let he1 = he0 + 1;
507        let he2 = he0 + 2;
508        self.half_edges.push(HalfEdge {
509            origin: v0,
510            twin: usize::MAX,
511            next: he1,
512            prev: he2,
513            face: Some(face_id),
514        });
515        self.half_edges.push(HalfEdge {
516            origin: v1,
517            twin: usize::MAX,
518            next: he2,
519            prev: he0,
520            face: Some(face_id),
521        });
522        self.half_edges.push(HalfEdge {
523            origin: v2,
524            twin: usize::MAX,
525            next: he0,
526            prev: he1,
527            face: Some(face_id),
528        });
529        self.faces.push(HEFace { half_edge: he0 });
530        if self.vertices[v0].half_edge.is_none() {
531            self.vertices[v0].half_edge = Some(he0);
532        }
533        if self.vertices[v1].half_edge.is_none() {
534            self.vertices[v1].half_edge = Some(he1);
535        }
536        if self.vertices[v2].half_edge.is_none() {
537            self.vertices[v2].half_edge = Some(he2);
538        }
539        face_id
540    }
541    /// Walk around a face and collect the vertex indices.
542    pub fn face_vertices(&self, fid: FaceId) -> Vec<VertexId> {
543        let start = self.faces[fid].half_edge;
544        let mut verts = Vec::new();
545        let mut cur = start;
546        loop {
547            verts.push(self.half_edges[cur].origin);
548            cur = self.half_edges[cur].next;
549            if cur == start {
550                break;
551            }
552        }
553        verts
554    }
555    /// Collect all faces that share vertex `vid`.
556    pub fn vertex_faces(&self, vid: VertexId) -> Vec<FaceId> {
557        let start_he = match self.vertices[vid].half_edge {
558            Some(h) => h,
559            None => return vec![],
560        };
561        let mut faces = Vec::new();
562        let mut cur = start_he;
563        loop {
564            if let Some(f) = self.half_edges[cur].face {
565                faces.push(f);
566            }
567            let twin = self.half_edges[cur].twin;
568            if twin == usize::MAX {
569                break;
570            }
571            cur = self.half_edges[twin].next;
572            if cur == start_he {
573                break;
574            }
575        }
576        faces
577    }
578    /// Resolve twin half-edge links by matching directed edges (v_a, v_b) with (v_b, v_a).
579    pub fn build_twin_links(&mut self) {
580        use std::collections::HashMap;
581        let n = self.half_edges.len();
582        let mut edge_map: HashMap<(usize, usize), usize> = HashMap::new();
583        for i in 0..n {
584            let origin = self.half_edges[i].origin;
585            let dest = self.half_edges[self.half_edges[i].next].origin;
586            edge_map.insert((origin, dest), i);
587        }
588        for i in 0..n {
589            let origin = self.half_edges[i].origin;
590            let dest = self.half_edges[self.half_edges[i].next].origin;
591            if let Some(&twin) = edge_map.get(&(dest, origin)) {
592                self.half_edges[i].twin = twin;
593            }
594        }
595    }
596    /// Number of faces.
597    pub fn num_faces(&self) -> usize {
598        self.faces.len()
599    }
600    /// Number of vertices.
601    pub fn num_vertices(&self) -> usize {
602        self.vertices.len()
603    }
604    /// Compute the face normal (assumes planar convex polygon).
605    pub fn face_normal(&self, fid: FaceId) -> Point3 {
606        let verts = self.face_vertices(fid);
607        if verts.len() < 3 {
608            return [0.0; 3];
609        }
610        let p0 = self.vertices[verts[0]].position;
611        let p1 = self.vertices[verts[1]].position;
612        let p2 = self.vertices[verts[2]].position;
613        let e1 = sub3(p1, p0);
614        let e2 = sub3(p2, p0);
615        normalize3(cross3(e1, e2))
616    }
617    /// Compute the centroid of a face.
618    pub fn face_centroid(&self, fid: FaceId) -> Point3 {
619        let verts = self.face_vertices(fid);
620        let n = verts.len() as f64;
621        let mut acc = [0.0f64; 3];
622        for vid in &verts {
623            let p = self.vertices[*vid].position;
624            acc[0] += p[0];
625            acc[1] += p[1];
626            acc[2] += p[2];
627        }
628        [acc[0] / n, acc[1] / n, acc[2] / n]
629    }
630}
631/// Result of an incremental 3D convex hull computation.
632#[derive(Debug, Clone)]
633pub struct ConvexHull3D {
634    /// Vertices on the hull (subset of input points).
635    pub vertices: Vec<Point3>,
636    /// Triangular faces.
637    pub faces: Vec<ConvexFace3D>,
638}
639impl ConvexHull3D {
640    /// Compute the volume of the convex hull using the divergence theorem.
641    pub fn volume(&self) -> f64 {
642        let mut vol = 0.0f64;
643        for face in &self.faces {
644            let a = self.vertices[face.verts[0]];
645            let b = self.vertices[face.verts[1]];
646            let c = self.vertices[face.verts[2]];
647            vol += dot3(a, cross3(b, c));
648        }
649        (vol / 6.0).abs()
650    }
651    /// Compute the surface area of the convex hull.
652    pub fn surface_area(&self) -> f64 {
653        let mut area = 0.0f64;
654        for face in &self.faces {
655            let a = self.vertices[face.verts[0]];
656            let b = self.vertices[face.verts[1]];
657            let c = self.vertices[face.verts[2]];
658            let ab = sub3(b, a);
659            let ac = sub3(c, a);
660            area += 0.5 * mag3(cross3(ab, ac));
661        }
662        area
663    }
664}
665/// Result of the art gallery approximation.
666#[derive(Debug, Clone)]
667pub struct ArtGalleryResult {
668    /// Indices of the chosen guard vertices.
669    pub guards: Vec<usize>,
670    /// Coverage: for each vertex, `covered[i] = true` if vertex `i` is visible
671    /// from at least one guard.
672    pub covered: Vec<bool>,
673}