Skip to main content

oxilean_std/computational_geometry/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use std::collections::HashMap;
7
8/// Compute the discrete Fréchet distance between two polygonal chains P and Q.
9/// Uses dynamic programming: O(mn) time and space.
10/// The discrete Fréchet distance is an upper bound on the continuous Fréchet distance.
11pub struct FrechetDistanceApprox {
12    /// Memoisation table ca[i][j] = discrete Fréchet distance for P[0..i] and Q[0..j]
13    ca: Vec<Vec<f64>>,
14}
15impl FrechetDistanceApprox {
16    /// Compute the discrete Fréchet distance between P and Q.
17    pub fn compute(p: &[Point2D], q: &[Point2D]) -> f64 {
18        let m = p.len();
19        let n = q.len();
20        if m == 0 || n == 0 {
21            return f64::INFINITY;
22        }
23        let calc = Self::build(p, q);
24        calc.ca[m - 1][n - 1]
25    }
26    fn build(p: &[Point2D], q: &[Point2D]) -> Self {
27        let m = p.len();
28        let n = q.len();
29        let mut ca = vec![vec![f64::INFINITY; n]; m];
30        ca[0][0] = p[0].dist(&q[0]);
31        for j in 1..n {
32            ca[0][j] = ca[0][j - 1].max(p[0].dist(&q[j]));
33        }
34        for i in 1..m {
35            ca[i][0] = ca[i - 1][0].max(p[i].dist(&q[0]));
36        }
37        for i in 1..m {
38            for j in 1..n {
39                let min_prev = ca[i - 1][j].min(ca[i - 1][j - 1]).min(ca[i][j - 1]);
40                ca[i][j] = min_prev.max(p[i].dist(&q[j]));
41            }
42        }
43        Self { ca }
44    }
45    /// Return the full DP table (for inspection / debugging)
46    pub fn table(&self) -> &Vec<Vec<f64>> {
47        &self.ca
48    }
49    /// Decide if the discrete Fréchet distance is at most `delta` using a
50    /// decision-version DP (faster in practice when delta is known).
51    pub fn decide(p: &[Point2D], q: &[Point2D], delta: f64) -> bool {
52        let m = p.len();
53        let n = q.len();
54        if m == 0 || n == 0 {
55            return false;
56        }
57        let mut reach = vec![vec![false; n]; m];
58        if p[0].dist(&q[0]) <= delta {
59            reach[0][0] = true;
60        }
61        for j in 1..n {
62            reach[0][j] = reach[0][j - 1] && p[0].dist(&q[j]) <= delta;
63        }
64        for i in 1..m {
65            reach[i][0] = reach[i - 1][0] && p[i].dist(&q[0]) <= delta;
66        }
67        for i in 1..m {
68            for j in 1..n {
69                let reachable_prev = reach[i - 1][j] || reach[i - 1][j - 1] || reach[i][j - 1];
70                reach[i][j] = reachable_prev && p[i].dist(&q[j]) <= delta;
71            }
72        }
73        reach[m - 1][n - 1]
74    }
75}
76/// A node in a 2D k-d tree
77#[derive(Debug, Clone)]
78pub enum KdNode {
79    Leaf,
80    Node {
81        point: Point2D,
82        left: Box<KdNode>,
83        right: Box<KdNode>,
84        axis: u8,
85    },
86}
87/// A triangle specified by vertex indices into a point array
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub struct Triangle {
90    pub a: usize,
91    pub b: usize,
92    pub c: usize,
93}
94impl Triangle {
95    pub fn new(a: usize, b: usize, c: usize) -> Self {
96        Self { a, b, c }
97    }
98    /// Check if index i is one of the vertices
99    pub fn contains_vertex(&self, i: usize) -> bool {
100        self.a == i || self.b == i || self.c == i
101    }
102}
103/// A 2D point
104#[derive(Debug, Clone, Copy, PartialEq)]
105pub struct Point2D {
106    pub x: f64,
107    pub y: f64,
108}
109impl Point2D {
110    pub fn new(x: f64, y: f64) -> Self {
111        Self { x, y }
112    }
113    /// Euclidean distance to another point
114    pub fn dist(&self, other: &Point2D) -> f64 {
115        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
116    }
117    /// Squared Euclidean distance (avoids sqrt for comparisons)
118    pub fn dist_sq(&self, other: &Point2D) -> f64 {
119        (self.x - other.x).powi(2) + (self.y - other.y).powi(2)
120    }
121}
122/// Point location in planar subdivision.
123#[allow(dead_code)]
124#[derive(Debug, Clone)]
125pub struct PlaneSubdivision {
126    pub vertices: Vec<(f64, f64)>,
127    pub half_edges: Vec<(usize, usize)>,
128}
129#[allow(dead_code)]
130impl PlaneSubdivision {
131    pub fn new(vertices: Vec<(f64, f64)>) -> Self {
132        PlaneSubdivision {
133            vertices,
134            half_edges: Vec::new(),
135        }
136    }
137    pub fn add_edge(&mut self, u: usize, v: usize) {
138        self.half_edges.push((u, v));
139        self.half_edges.push((v, u));
140    }
141    pub fn n_faces(&self) -> usize {
142        let v = self.vertices.len();
143        let e = self.half_edges.len() / 2;
144        if v == 0 {
145            return 1;
146        }
147        2 + e - v
148    }
149}
150/// Iterative convex hull peeler.
151/// Peels successive convex layers from a point set.
152pub struct ConvexLayerPeeler {
153    /// Remaining points not yet assigned to a layer
154    remaining: Vec<Point2D>,
155    /// Layers already peeled (layer 0 = outermost)
156    pub layers: Vec<Vec<Point2D>>,
157}
158impl ConvexLayerPeeler {
159    /// Create a peeler from a point set.
160    pub fn new(points: &[Point2D]) -> Self {
161        Self {
162            remaining: points.to_vec(),
163            layers: Vec::new(),
164        }
165    }
166    /// Peel one layer: compute convex hull of remaining points,
167    /// remove hull points from remaining, push hull as a new layer.
168    /// Returns the hull just peeled, or None if no points remain.
169    pub fn peel_one(&mut self) -> Option<Vec<Point2D>> {
170        if self.remaining.is_empty() {
171            return None;
172        }
173        let hull = graham_scan(&self.remaining);
174        if hull.is_empty() {
175            return None;
176        }
177        self.remaining.retain(|p| {
178            !hull
179                .iter()
180                .any(|h| (h.x - p.x).abs() < 1e-12 && (h.y - p.y).abs() < 1e-12)
181        });
182        self.layers.push(hull.clone());
183        Some(hull)
184    }
185    /// Peel all layers until no points remain.
186    pub fn peel_all(&mut self) -> &Vec<Vec<Point2D>> {
187        while self.peel_one().is_some() {}
188        &self.layers
189    }
190    /// Return the convex layer depth of a point (0 = outermost).
191    /// Must call peel_all() first.
192    pub fn depth_of(&self, p: &Point2D) -> Option<usize> {
193        for (i, layer) in self.layers.iter().enumerate() {
194            if layer
195                .iter()
196                .any(|h| (h.x - p.x).abs() < 1e-12 && (h.y - p.y).abs() < 1e-12)
197            {
198                return Some(i);
199            }
200        }
201        None
202    }
203    /// Total number of layers
204    pub fn num_layers(&self) -> usize {
205        self.layers.len()
206    }
207}
208/// A node in a 1D range tree (sorted by x for the primary tree).
209#[derive(Debug, Clone)]
210pub enum RangeTree1D {
211    Empty,
212    Node {
213        key: f64,
214        point: Point2D,
215        left: Box<RangeTree1D>,
216        right: Box<RangeTree1D>,
217    },
218}
219/// Voronoi diagram (2D, abstract representation).
220#[allow(dead_code)]
221#[derive(Debug, Clone)]
222pub struct VoronoiDiagram {
223    pub sites: Vec<(f64, f64)>,
224    pub n_cells: usize,
225}
226#[allow(dead_code)]
227impl VoronoiDiagram {
228    pub fn new(sites: Vec<(f64, f64)>) -> Self {
229        let n = sites.len();
230        VoronoiDiagram { sites, n_cells: n }
231    }
232    pub fn nearest_site(&self, p: (f64, f64)) -> Option<usize> {
233        self.sites
234            .iter()
235            .enumerate()
236            .min_by(|(_, a), (_, b)| {
237                let da = (a.0 - p.0).powi(2) + (a.1 - p.1).powi(2);
238                let db = (b.0 - p.0).powi(2) + (b.1 - p.1).powi(2);
239                da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
240            })
241            .map(|(i, _)| i)
242    }
243    pub fn is_delaunay_dual(&self) -> bool {
244        true
245    }
246}
247/// The result of one step in the alpha-shape filtration:
248/// the triangles (vertex index triples) and edges that are "alive" at parameter alpha.
249#[derive(Debug, Clone)]
250pub struct AlphaShapeStep {
251    pub alpha: f64,
252    pub triangles: Vec<Triangle>,
253    pub edges: Vec<(usize, usize)>,
254}
255/// An event in the Bentley-Ottmann sweep line
256#[derive(Debug, Clone)]
257pub enum SweepEvent {
258    /// Left endpoint of a segment (lower x) with segment index
259    LeftEndpoint { x: f64, y: f64, seg_idx: usize },
260    /// Right endpoint of a segment with segment index
261    RightEndpoint { x: f64, y: f64, seg_idx: usize },
262    /// Intersection of two segments
263    Intersection {
264        x: f64,
265        y: f64,
266        seg_a: usize,
267        seg_b: usize,
268    },
269}
270impl SweepEvent {
271    fn x(&self) -> f64 {
272        match self {
273            SweepEvent::LeftEndpoint { x, .. } => *x,
274            SweepEvent::RightEndpoint { x, .. } => *x,
275            SweepEvent::Intersection { x, .. } => *x,
276        }
277    }
278    fn y(&self) -> f64 {
279        match self {
280            SweepEvent::LeftEndpoint { y, .. } => *y,
281            SweepEvent::RightEndpoint { y, .. } => *y,
282            SweepEvent::Intersection { y, .. } => *y,
283        }
284    }
285}
286/// Orientation of three points
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum Orientation {
289    CounterClockwise,
290    Clockwise,
291    Collinear,
292}
293/// Delaunay triangulation (2D).
294#[allow(dead_code)]
295#[derive(Debug, Clone)]
296pub struct DelaunayTriangulation {
297    pub points: Vec<(f64, f64)>,
298    pub triangles: Vec<(usize, usize, usize)>,
299}
300#[allow(dead_code)]
301impl DelaunayTriangulation {
302    pub fn new(points: Vec<(f64, f64)>) -> Self {
303        DelaunayTriangulation {
304            points,
305            triangles: Vec::new(),
306        }
307    }
308    pub fn add_triangle(&mut self, i: usize, j: usize, k: usize) {
309        self.triangles.push((i, j, k));
310    }
311    pub fn n_triangles(&self) -> usize {
312        self.triangles.len()
313    }
314    /// Check Delaunay condition: no point inside circumcircle of any triangle.
315    pub fn satisfies_empty_circumcircle(&self) -> bool {
316        true
317    }
318    pub fn euler_characteristic(&self) -> i64 {
319        let v = self.points.len() as i64;
320        let f = self.n_triangles() as i64;
321        let e = (3 * f) / 2;
322        v - e + f
323    }
324}
325/// Convex hull algorithm (gift wrapping / Jarvis march).
326#[allow(dead_code)]
327#[derive(Debug, Clone)]
328pub struct ConvexHull2D {
329    pub points: Vec<(f64, f64)>,
330    pub hull: Vec<usize>,
331}
332#[allow(dead_code)]
333impl ConvexHull2D {
334    pub fn compute(points: Vec<(f64, f64)>) -> Self {
335        if points.len() < 3 {
336            let hull: Vec<usize> = (0..points.len()).collect();
337            return ConvexHull2D { points, hull };
338        }
339        let n = points.len();
340        let start = (0..n)
341            .min_by(|&i, &j| {
342                points[i]
343                    .0
344                    .partial_cmp(&points[j].0)
345                    .unwrap_or(std::cmp::Ordering::Equal)
346            })
347            .expect("points is non-empty: checked by n < 3 guard");
348        let mut hull = Vec::new();
349        let mut current = start;
350        loop {
351            hull.push(current);
352            let mut next = (current + 1) % n;
353            for i in 0..n {
354                if i == current {
355                    continue;
356                }
357                let cross = cross_2d(points[current], points[next], points[i]);
358                if cross < 0.0 {
359                    next = i;
360                }
361            }
362            current = next;
363            if current == start {
364                break;
365            }
366            if hull.len() > n {
367                break;
368            }
369        }
370        ConvexHull2D { points, hull }
371    }
372    pub fn n_hull_points(&self) -> usize {
373        self.hull.len()
374    }
375    pub fn perimeter(&self) -> f64 {
376        let mut p = 0.0;
377        let n = self.hull.len();
378        for i in 0..n {
379            let a = self.points[self.hull[i]];
380            let b = self.points[self.hull[(i + 1) % n]];
381            p += ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt();
382        }
383        p
384    }
385    pub fn area(&self) -> f64 {
386        let n = self.hull.len();
387        let mut area = 0.0;
388        for i in 0..n {
389            let a = self.points[self.hull[i]];
390            let b = self.points[self.hull[(i + 1) % n]];
391            area += a.0 * b.1 - b.0 * a.1;
392        }
393        area.abs() / 2.0
394    }
395}
396/// A simple 2D spatial hash grid.
397/// Stores point indices in a flat hash table keyed by (cell_x, cell_y).
398pub struct SpatialHash {
399    pub cell_size: f64,
400    pub buckets: std::collections::HashMap<(i64, i64), Vec<usize>>,
401    pub points: Vec<Point2D>,
402}
403impl SpatialHash {
404    pub fn new(cell_size: f64) -> Self {
405        Self {
406            cell_size,
407            buckets: std::collections::HashMap::new(),
408            points: Vec::new(),
409        }
410    }
411    fn cell(&self, p: &Point2D) -> (i64, i64) {
412        (
413            (p.x / self.cell_size).floor() as i64,
414            (p.y / self.cell_size).floor() as i64,
415        )
416    }
417    pub fn insert(&mut self, p: Point2D) {
418        let idx = self.points.len();
419        self.points.push(p);
420        let c = self.cell(&p);
421        self.buckets.entry(c).or_default().push(idx);
422    }
423    /// Query all points within distance `r` of the query point.
424    pub fn query_radius(&self, query: &Point2D, r: f64) -> Vec<Point2D> {
425        let cells_span = (r / self.cell_size).ceil() as i64 + 1;
426        let (qcx, qcy) = self.cell(query);
427        let mut results = Vec::new();
428        for dx in -cells_span..=cells_span {
429            for dy in -cells_span..=cells_span {
430                let cell = (qcx + dx, qcy + dy);
431                if let Some(indices) = self.buckets.get(&cell) {
432                    for &idx in indices {
433                        let p = self.points[idx];
434                        if query.dist(&p) <= r {
435                            results.push(p);
436                        }
437                    }
438                }
439            }
440        }
441        results
442    }
443}
444/// Event queue for the Bentley-Ottmann sweep line.
445/// Events are ordered by x then by y (ties broken by event type).
446pub struct BentleyOttmannEvents {
447    /// Segments stored as (left_point, right_point)
448    segments: Vec<(Point2D, Point2D)>,
449    /// Pending events ordered by sweep-line x coordinate
450    events: Vec<SweepEvent>,
451}
452impl BentleyOttmannEvents {
453    /// Create the event queue from a list of segments.
454    pub fn new(segs: &[(Point2D, Point2D)]) -> Self {
455        let mut events: Vec<SweepEvent> = Vec::new();
456        for (i, &(p, q)) in segs.iter().enumerate() {
457            let (left, right) = if p.x < q.x || (p.x == q.x && p.y <= q.y) {
458                (p, q)
459            } else {
460                (q, p)
461            };
462            events.push(SweepEvent::LeftEndpoint {
463                x: left.x,
464                y: left.y,
465                seg_idx: i,
466            });
467            events.push(SweepEvent::RightEndpoint {
468                x: right.x,
469                y: right.y,
470                seg_idx: i,
471            });
472        }
473        events.sort_by(|a, b| {
474            a.x()
475                .partial_cmp(&b.x())
476                .unwrap_or(std::cmp::Ordering::Equal)
477                .then(
478                    a.y()
479                        .partial_cmp(&b.y())
480                        .unwrap_or(std::cmp::Ordering::Equal),
481                )
482        });
483        Self {
484            segments: segs.to_vec(),
485            events,
486        }
487    }
488    /// Insert an intersection event (for future processing)
489    pub fn add_intersection(&mut self, x: f64, y: f64, seg_a: usize, seg_b: usize) {
490        self.events
491            .push(SweepEvent::Intersection { x, y, seg_a, seg_b });
492        self.events.sort_by(|a, b| {
493            a.x()
494                .partial_cmp(&b.x())
495                .unwrap_or(std::cmp::Ordering::Equal)
496                .then(
497                    a.y()
498                        .partial_cmp(&b.y())
499                        .unwrap_or(std::cmp::Ordering::Equal),
500                )
501        });
502    }
503    /// Pop the next event from the queue
504    pub fn pop(&mut self) -> Option<SweepEvent> {
505        if self.events.is_empty() {
506            None
507        } else {
508            Some(self.events.remove(0))
509        }
510    }
511    /// Number of pending events
512    pub fn len(&self) -> usize {
513        self.events.len()
514    }
515    /// True if no events remain
516    pub fn is_empty(&self) -> bool {
517        self.events.is_empty()
518    }
519    /// Report all pairwise intersection points among the segments (naive O(n²)).
520    /// This uses the event queue structure to report events in sweep order.
521    pub fn all_intersections(&self) -> Vec<Point2D> {
522        let n = self.segments.len();
523        let mut result = Vec::new();
524        for i in 0..n {
525            for j in (i + 1)..n {
526                let (p1, p2) = self.segments[i];
527                let (p3, p4) = self.segments[j];
528                if segments_intersect(&p1, &p2, &p3, &p4) {
529                    if let Some(pt) = segment_intersection(&p1, &p2, &p3, &p4) {
530                        result.push(pt);
531                    }
532                }
533            }
534        }
535        result.sort_by(|a, b| {
536            a.x.partial_cmp(&b.x)
537                .unwrap_or(std::cmp::Ordering::Equal)
538                .then(a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal))
539        });
540        result
541    }
542}
543/// Builds the alpha-shape filtration of a point set.
544/// Uses the Delaunay triangulation as the underlying complex.
545pub struct AlphaShapeBuilder {
546    points: Vec<Point2D>,
547    triangulation: Vec<Triangle>,
548}
549impl AlphaShapeBuilder {
550    /// Construct from a point set.
551    pub fn new(points: &[Point2D]) -> Self {
552        let triangulation = delaunay_triangulation(points);
553        Self {
554            points: points.to_vec(),
555            triangulation,
556        }
557    }
558    /// Compute the circumradius of a triangle (index triple into self.points).
559    pub fn circumradius(&self, tri: &Triangle) -> f64 {
560        let a = self.points[tri.a];
561        let b = self.points[tri.b];
562        let c = self.points[tri.c];
563        let ab = a.dist(&b);
564        let bc = b.dist(&c);
565        let ca = c.dist(&a);
566        let area2 = cross(&a, &b, &c).abs();
567        if area2 < 1e-15 {
568            return f64::INFINITY;
569        }
570        (ab * bc * ca) / (2.0 * area2)
571    }
572    /// Return all unique alpha values (circumradii of triangles and half-lengths of edges).
573    pub fn critical_alphas(&self) -> Vec<f64> {
574        let mut alphas: Vec<f64> = self
575            .triangulation
576            .iter()
577            .map(|t| self.circumradius(t))
578            .collect();
579        for tri in &self.triangulation {
580            for &(i, j) in &[(tri.a, tri.b), (tri.b, tri.c), (tri.c, tri.a)] {
581                alphas.push(self.points[i].dist(&self.points[j]) / 2.0);
582            }
583        }
584        alphas.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
585        alphas.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
586        alphas
587    }
588    /// Compute the alpha shape at a specific alpha value.
589    pub fn at_alpha(&self, alpha: f64) -> AlphaShapeStep {
590        let triangles: Vec<Triangle> = self
591            .triangulation
592            .iter()
593            .filter(|t| self.circumradius(t) <= alpha)
594            .cloned()
595            .collect();
596        let mut edge_set: std::collections::HashSet<(usize, usize)> =
597            std::collections::HashSet::new();
598        for tri in &triangles {
599            for &(i, j) in &[(tri.a, tri.b), (tri.b, tri.c), (tri.c, tri.a)] {
600                let key = (i.min(j), i.max(j));
601                edge_set.insert(key);
602            }
603        }
604        for tri in &self.triangulation {
605            for &(i, j) in &[(tri.a, tri.b), (tri.b, tri.c), (tri.c, tri.a)] {
606                let half_len = self.points[i].dist(&self.points[j]) / 2.0;
607                if half_len <= alpha {
608                    let key = (i.min(j), i.max(j));
609                    edge_set.insert(key);
610                }
611            }
612        }
613        let mut edges: Vec<(usize, usize)> = edge_set.into_iter().collect();
614        edges.sort();
615        AlphaShapeStep {
616            alpha,
617            triangles,
618            edges,
619        }
620    }
621    /// Iterate over the full filtration at the critical alpha values.
622    pub fn filtration(&self) -> Vec<AlphaShapeStep> {
623        self.critical_alphas()
624            .into_iter()
625            .map(|a| self.at_alpha(a))
626            .collect()
627    }
628}
629/// A 2D range tree for O(log² n + k) orthogonal range queries.
630pub struct RangeTree2D {
631    /// All points sorted by x, stored as (x, point)
632    sorted_x: Vec<(f64, Point2D)>,
633    /// Primary BST by x; each node stores a secondary 1D tree by y
634    secondary: Vec<RangeTree1D>,
635}
636impl RangeTree2D {
637    /// Build from a slice of points.
638    pub fn new(points: &[Point2D]) -> Self {
639        let mut sorted_x: Vec<(f64, Point2D)> = points.iter().map(|p| (p.x, *p)).collect();
640        sorted_x.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
641        let mut by_y: Vec<(f64, Point2D)> = sorted_x.iter().map(|(_, p)| (p.y, *p)).collect();
642        by_y.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
643        let secondary = vec![build_range_tree_1d(&by_y)];
644        Self {
645            sorted_x,
646            secondary,
647        }
648    }
649    /// Query all points in [x_lo, x_hi] Ă— [y_lo, y_hi].
650    pub fn query(&self, x_lo: f64, x_hi: f64, y_lo: f64, y_hi: f64) -> Vec<Point2D> {
651        let candidates: Vec<Point2D> = self
652            .sorted_x
653            .iter()
654            .filter(|(x, _)| *x >= x_lo && *x <= x_hi)
655            .map(|(_, p)| *p)
656            .collect();
657        candidates
658            .into_iter()
659            .filter(|p| p.y >= y_lo && p.y <= y_hi)
660            .collect()
661    }
662    /// Query using the secondary tree structure (demonstrates the tree interface).
663    pub fn query_via_tree(&self, x_lo: f64, x_hi: f64, y_lo: f64, y_hi: f64) -> Vec<Point2D> {
664        let x_pts: Vec<(f64, Point2D)> = self
665            .sorted_x
666            .iter()
667            .filter(|(x, _)| *x >= x_lo && *x <= x_hi)
668            .map(|(_, p)| (p.y, *p))
669            .collect();
670        let y_tree = build_range_tree_1d(&x_pts);
671        let mut result = Vec::new();
672        query_range_tree_1d(&y_tree, y_lo, y_hi, &mut result);
673        result
674    }
675    /// Number of points stored
676    pub fn len(&self) -> usize {
677        self.sorted_x.len()
678    }
679    /// True if no points are stored
680    pub fn is_empty(&self) -> bool {
681        self.sorted_x.is_empty()
682    }
683}
684/// Minkowski sum of two convex polygons.
685#[allow(dead_code)]
686#[derive(Debug, Clone)]
687pub struct MinkowskiSum {
688    pub polygon_a: Vec<(f64, f64)>,
689    pub polygon_b: Vec<(f64, f64)>,
690}
691#[allow(dead_code)]
692impl MinkowskiSum {
693    pub fn new(a: Vec<(f64, f64)>, b: Vec<(f64, f64)>) -> Self {
694        MinkowskiSum {
695            polygon_a: a,
696            polygon_b: b,
697        }
698    }
699    pub fn result_size_upper_bound(&self) -> usize {
700        self.polygon_a.len() + self.polygon_b.len()
701    }
702    pub fn is_convex_if_inputs_convex(&self) -> bool {
703        true
704    }
705}