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