Skip to main content

algebraeon_geometry/
simplex_overlap.rs

1use crate::{ambient_space::common_space, simplex::Simplex, vector::DotProduct};
2use algebraeon_rings::{
3    linear::{
4        finitely_free_module::FinitelyFreeModuleStructure,
5        finitely_free_submodule::FinitelyFreeSubmoduleStructure,
6    },
7    matrix::{Matrix, MatrixStructure},
8    structure::{FieldSignature, OrderedRingSignature, ZeroEqSignature},
9};
10use itertools::Itertools;
11use std::collections::HashSet;
12use std::hash::Hash;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum SimplexOverlapResult {
16    Disjoint, //the closures of the simplexes are disjoint
17    Touching, //the closures of the simplexes overlap but the interiors are disjoint
18    Overlap,  //the interiors of the simplexes overlap
19}
20
21/*
22Calculate whether two simplexes in the same space overlap.
23
24The idea is to use the Hyperplane Separation Theorem https://en.wikipedia.org/wiki/Hyperplane_separation_theorem:
25    Two convex sets are disjoint if and only if there exists a hyperplane separating them
26
27For a pair of simplexes, it suffices to check for separating hyperplanes parallel to the minkowski sum of the simplexes
28The linear cosets defined by the hyperplanar faces of the minkowski sum are given by the linear coset spanned by the k-dimensional subsimplexes of A and the (n-k)-dimensional subsimplexes of B for k=1,2,...,n-1 where n+1 = the dimension of the minkowski sum
29
30In the algorithm, we work with the normal vectors to hyperplanes rather than the hyperplanes themselves
31*/
32// If RETURN_IF_TOUCHING=true then this function may return SimplexOverlapResult::Touching even if the simplexes are actually disjoint. That gives a performance boost if it only matters that the interiors are disjoint
33fn simplex_overlap_impl<
34    'f,
35    FS: OrderedRingSignature + FieldSignature,
36    const RETURN_IF_TOUCHING: bool,
37>(
38    a: &Simplex<'f, FS>,
39    b: &Simplex<'f, FS>,
40) -> SimplexOverlapResult
41where
42    FS::Set: Hash,
43{
44    let space = common_space(a.ambient_space(), b.ambient_space()).unwrap();
45
46    // If either A or B is the null-simplex then they are disjoint.
47    if a.n() == 0 || b.n() == 0 {
48        return SimplexOverlapResult::Disjoint;
49    }
50    debug_assert!(space.linear_dimension().is_some()); // non-empty space
51    let space_dim = space.linear_dimension().unwrap();
52    let field = space.field();
53
54    // Now since either A nor B is the null simplex we know they are both non-empty, so can pick a root point for each
55    let a_root = a.point(0);
56    let b_root = b.point(0);
57
58    // For each point of A and B other than the root, take the vector from the root to that point
59    let a_vecs = (1..a.n()).map(|i| a.point(i) - a_root).collect::<Vec<_>>();
60    let b_vecs = (1..b.n()).map(|i| b.point(i) - b_root).collect::<Vec<_>>();
61
62    // A vector starting in A and ending in B
63    let a_to_b_vec = a_root - b_root;
64
65    // This is the linear subspace obtained by rooting A and B at the origin and taking their combined linear span
66    // The sum of A and B lives within a coset of this linear span
67    let linear_span_foo = space.affine_subspace_from_root_and_linear_span(
68        &space.origin().unwrap(),
69        a_vecs.iter().chain(b_vecs.iter()).collect(),
70    );
71    let linear_span_foo_submodule = MatrixStructure::new(space.field().clone()).row_span(
72        Matrix::construct(a_vecs.len() + b_vecs.len(), space_dim, |r, c| {
73            if r < a_vecs.len() {
74                a_vecs[r].coordinate(c).clone()
75            } else {
76                b_vecs[r - a_vecs.len()].coordinate(c).clone()
77            }
78        }),
79    );
80    debug_assert_eq!(linear_span_foo_submodule.module_rank(), space_dim);
81
82    // This is the linear subspace obtained by rooting the affine span of A and B
83    // In typical cases, it is equal to linear_span_foo
84    //  In that case, we need only check the (finitely many) normals to the hyperplanes (in linear_span_foo) of the sum of A and B
85    // In degenerate cases, it has dimension one greater than linear_span_foo
86    //  In that case, in addition to the normals to the hyperplanes of the sum of A and B, we need to also check the normal vector to the hyperplane (in linear_span_bar) given by the sum of A and B
87    let linear_span_bar = MatrixStructure::new(space.field().clone()).row_span(Matrix::construct(
88        a_vecs.len() + b_vecs.len() + 1,
89        space_dim,
90        |r, c| {
91            if r < a_vecs.len() {
92                a_vecs[r].coordinate(c).clone()
93            } else if r < a_vecs.len() + b_vecs.len() {
94                b_vecs[r - a_vecs.len()].coordinate(c).clone()
95            } else {
96                a_to_b_vec.coordinate(c).clone()
97            }
98        },
99    ));
100    debug_assert_eq!(linear_span_bar.module_rank(), space_dim);
101    debug_assert!(
102        (linear_span_bar.rank() == linear_span_foo_submodule.rank())
103            || (linear_span_bar.rank() == linear_span_foo_submodule.rank() + 1)
104    );
105
106    // Accumulate all the normals we need to check, excluding any duplicates
107    let mut normals = HashSet::new();
108    // Record whether A and B just touch when projected along any of the normals
109    // If A and B don't overlap then they are touching iff they are touching when projected on some normal in our list
110    let mut just_touching = false;
111
112    let n = linear_span_foo.embedded_space().linear_dimension().unwrap();
113
114    for normal in {
115        if linear_span_bar.rank() != linear_span_foo_submodule.rank() {
116            // Add the normal to the sum of A and B for the degenerate case
117
118            let normal_space =
119                FinitelyFreeSubmoduleStructure::new(
120                    FinitelyFreeModuleStructure::<FS, &'f FS>::new(field, space_dim),
121                )
122                .intersect(
123                    MatrixStructure::new(space.field().clone()).col_kernel(Matrix::construct(
124                        a_vecs.len() + b_vecs.len(),
125                        space_dim,
126                        |r, c| {
127                            if r < a_vecs.len() {
128                                a_vecs[r].coordinate(c).clone()
129                            } else {
130                                b_vecs[r - a_vecs.len()].coordinate(c).clone()
131                            }
132                        },
133                    )),
134                    linear_span_bar.clone(),
135                );
136            debug_assert!(normal_space.rank() == 1);
137            let normal = space.vector(normal_space.basis().first().unwrap().iter().cloned());
138            for vec in &a_vecs {
139                debug_assert!(field.is_zero(&normal.dot(vec)));
140            }
141            for vec in &b_vecs {
142                debug_assert!(field.is_zero(&normal.dot(vec)));
143            }
144            Some(normal)
145        } else {
146            None
147        }
148    }
149    .into_iter()
150    // Add the normals comming from the faces of the sum of A and B
151    .chain((0..n).map(|i| (i, n - i - 1)).flat_map(|(i, j)| {
152        (0..a.n())
153            .combinations(i + 1)
154            .cartesian_product((0..b.n()).combinations(j + 1))
155            .filter_map(|(a_pts, b_pts)| {
156                let i = a_pts.len() - 1;
157                let j = b_pts.len() - 1;
158
159                let a_sub = a.sub_simplex(a_pts.clone());
160                let b_sub = b.sub_simplex(b_pts);
161
162                let a_sub_root = a_sub.point(0);
163                let b_sub_root = b_sub.point(0);
164
165                let a_sub_vecs = (0..i)
166                    .map(|k| a_sub.point(k + 1) - a_sub_root)
167                    .collect::<Vec<_>>();
168                let b_sub_vecs = (0..j)
169                    .map(|k| b_sub.point(k + 1) - b_sub_root)
170                    .collect::<Vec<_>>();
171
172                let normal_space = FinitelyFreeSubmoduleStructure::new(
173                    FinitelyFreeModuleStructure::<FS, &'f FS>::new(field, space_dim),
174                )
175                .intersect(
176                    MatrixStructure::new(space.field().clone()).col_kernel(Matrix::construct(
177                        i + j,
178                        space_dim,
179                        |r, c| {
180                            if r < i {
181                                a_sub_vecs[r].coordinate(c).clone()
182                            } else {
183                                b_sub_vecs[r - i].coordinate(c).clone()
184                            }
185                        },
186                    )),
187                    linear_span_foo_submodule.clone(),
188                );
189
190                debug_assert!(normal_space.rank() >= 1);
191
192                // If normal_space.rank() >= 1 then this is a degenerate face, there is no unique normal, and we need not check it
193                if normal_space.rank() == 1 {
194                    let normal =
195                        space.vector(normal_space.basis().first().unwrap().iter().cloned());
196                    for vec in &a_sub_vecs {
197                        debug_assert!(field.is_zero(&normal.dot(vec)));
198                    }
199                    for vec in &b_sub_vecs {
200                        debug_assert!(field.is_zero(&normal.dot(vec)));
201                    }
202                    Some(normal)
203                } else {
204                    None
205                }
206            })
207    })) {
208        if !normals.contains(&normal) {
209            // Find the min and max projections of points of A
210            let mut a_points = a.points().iter();
211            let a_proj = normal.dot(a_points.next().unwrap());
212            let mut min_a_proj = a_proj.clone();
213            let mut max_a_proj = a_proj;
214            for a_pt in a_points {
215                let proj = normal.dot(a_pt);
216                if field.cmp(&proj, &min_a_proj) == std::cmp::Ordering::Less {
217                    min_a_proj = proj.clone();
218                }
219                if field.cmp(&proj, &max_a_proj) == std::cmp::Ordering::Greater {
220                    max_a_proj = proj.clone();
221                }
222            }
223
224            // Find the min and max projections of points of B
225            let mut b_points = b.points().iter();
226            let b_proj = normal.dot(b_points.next().unwrap());
227            let mut min_b_proj = b_proj.clone();
228            let mut max_b_proj = b_proj;
229            for b_pt in b_points {
230                let proj = normal.dot(b_pt);
231                if field.cmp(&proj, &min_b_proj) == std::cmp::Ordering::Less {
232                    min_b_proj = proj.clone();
233                }
234                if field.cmp(&proj, &max_b_proj) == std::cmp::Ordering::Greater {
235                    max_b_proj = proj.clone();
236                }
237            }
238
239            // Check how the projections of A and B overlap
240            match (
241                field.cmp(&max_a_proj, &min_b_proj),
242                field.cmp(&max_b_proj, &min_a_proj),
243            ) {
244                (std::cmp::Ordering::Less, _) | (_, std::cmp::Ordering::Less) => {
245                    return SimplexOverlapResult::Disjoint;
246                }
247                (std::cmp::Ordering::Greater, std::cmp::Ordering::Greater) => {}
248                (std::cmp::Ordering::Equal, _) | (_, std::cmp::Ordering::Equal) => {
249                    if RETURN_IF_TOUCHING {
250                        return SimplexOverlapResult::Touching;
251                    }
252                    just_touching = true;
253                }
254            }
255
256            normals.insert(normal);
257        }
258    }
259
260    match just_touching {
261        false => SimplexOverlapResult::Overlap,
262        true => SimplexOverlapResult::Touching,
263    }
264}
265
266pub fn simplex_interior_overlap<'f, FS: OrderedRingSignature + FieldSignature>(
267    a: &Simplex<'f, FS>,
268    b: &Simplex<'f, FS>,
269) -> bool
270where
271    FS::Set: Hash,
272{
273    match simplex_overlap_impl::<FS, true>(a, b) {
274        SimplexOverlapResult::Disjoint => false,
275        SimplexOverlapResult::Touching => false,
276        SimplexOverlapResult::Overlap => true,
277    }
278}
279
280pub fn simplex_closure_overlap<'f, FS: OrderedRingSignature + FieldSignature>(
281    a: &Simplex<'f, FS>,
282    b: &Simplex<'f, FS>,
283) -> bool
284where
285    FS::Set: Hash,
286{
287    match simplex_overlap_impl::<FS, false>(a, b) {
288        SimplexOverlapResult::Disjoint => false,
289        SimplexOverlapResult::Touching => true,
290        SimplexOverlapResult::Overlap => true,
291    }
292}
293
294pub fn simplex_overlap<'f, FS: OrderedRingSignature + FieldSignature>(
295    a: &Simplex<'f, FS>,
296    b: &Simplex<'f, FS>,
297) -> SimplexOverlapResult
298where
299    FS::Set: Hash,
300{
301    simplex_overlap_impl::<FS, false>(a, b)
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    use crate::ambient_space::AffineSpace;
308    use algebraeon_nzq::Rational;
309
310    #[test]
311    fn something_and_null() {
312        let space3 = AffineSpace::new_linear(Rational::structure_ref(), 3);
313
314        assert_eq!(
315            simplex_overlap(
316                &space3.simplex(vec![space3.vector([1, 2, 3])]).unwrap(),
317                &space3.simplex(vec![]).unwrap()
318            ),
319            SimplexOverlapResult::Disjoint
320        );
321
322        assert_eq!(
323            simplex_overlap(
324                &space3
325                    .simplex(vec![space3.vector([1, 2, 3]), space3.vector([4, 5, 6])])
326                    .unwrap(),
327                &space3.simplex(vec![]).unwrap()
328            ),
329            SimplexOverlapResult::Disjoint
330        );
331    }
332
333    #[test]
334    fn two_points() {
335        let space3 = AffineSpace::new_linear(Rational::structure_ref(), 3);
336
337        // different points
338        assert_eq!(
339            simplex_overlap(
340                &space3.simplex(vec![space3.vector([1, 2, 3])]).unwrap(),
341                &space3.simplex(vec![space3.vector([1, 3, 2])]).unwrap()
342            ),
343            SimplexOverlapResult::Disjoint
344        );
345
346        // same point
347        assert_eq!(
348            simplex_overlap(
349                &space3.simplex(vec![space3.vector([1, 2, 3])]).unwrap(),
350                &space3.simplex(vec![space3.vector([1, 2, 3])]).unwrap()
351            ),
352            SimplexOverlapResult::Overlap
353        );
354    }
355
356    #[test]
357    fn point_and_line() {
358        let space3 = AffineSpace::new_linear(Rational::structure_ref(), 3);
359
360        // point in the middle of the line
361        assert_eq!(
362            simplex_overlap(
363                &space3
364                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([2, 0, 0])])
365                    .unwrap(),
366                &space3.simplex(vec![space3.vector([1, 0, 0])]).unwrap()
367            ),
368            SimplexOverlapResult::Overlap
369        );
370
371        // point at the end of the line
372        assert_eq!(
373            simplex_overlap(
374                &space3
375                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([2, 0, 0])])
376                    .unwrap(),
377                &space3.simplex(vec![space3.vector([2, 0, 0])]).unwrap()
378            ),
379            SimplexOverlapResult::Touching
380        );
381
382        // point disjoint from line
383        assert_eq!(
384            simplex_overlap(
385                &space3
386                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([2, 0, 0])])
387                    .unwrap(),
388                &space3.simplex(vec![space3.vector([0, 1, 0])]).unwrap()
389            ),
390            SimplexOverlapResult::Disjoint
391        );
392        assert_eq!(
393            simplex_overlap(
394                &space3
395                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([2, 0, 0])])
396                    .unwrap(),
397                &space3.simplex(vec![space3.vector([1, 1, 0])]).unwrap()
398            ),
399            SimplexOverlapResult::Disjoint
400        );
401        assert_eq!(
402            simplex_overlap(
403                &space3
404                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([2, 0, 0])])
405                    .unwrap(),
406                &space3.simplex(vec![space3.vector([3, 0, 0])]).unwrap()
407            ),
408            SimplexOverlapResult::Disjoint
409        );
410        assert_eq!(
411            simplex_overlap(
412                &space3
413                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([2, 0, 0])])
414                    .unwrap(),
415                &space3.simplex(vec![space3.vector([2, 1, 0])]).unwrap()
416            ),
417            SimplexOverlapResult::Disjoint
418        );
419        assert_eq!(
420            simplex_overlap(
421                &space3
422                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([2, 0, 0])])
423                    .unwrap(),
424                &space3.simplex(vec![space3.vector([3, 1, 0])]).unwrap()
425            ),
426            SimplexOverlapResult::Disjoint
427        );
428    }
429
430    #[test]
431    fn point_and_triangle() {
432        let space3 = AffineSpace::new_linear(Rational::structure_ref(), 3);
433
434        let triangle = space3
435            .simplex(vec![
436                space3.vector([6, 0, 0]),
437                space3.vector([0, 6, 0]),
438                space3.vector([0, 0, 6]),
439            ])
440            .unwrap();
441
442        assert_eq!(
443            simplex_overlap(
444                &triangle,
445                &space3.simplex(vec![space3.vector([0, 0, 0])]).unwrap()
446            ),
447            SimplexOverlapResult::Disjoint
448        );
449        assert_eq!(
450            simplex_overlap(
451                &triangle,
452                &space3.simplex(vec![space3.vector([4, 4, 4])]).unwrap()
453            ),
454            SimplexOverlapResult::Disjoint
455        );
456
457        assert_eq!(
458            simplex_overlap(
459                &triangle,
460                &space3.simplex(vec![space3.vector([2, 2, 2])]).unwrap()
461            ),
462            SimplexOverlapResult::Overlap
463        );
464
465        assert_eq!(
466            simplex_overlap(
467                &triangle,
468                &space3.simplex(vec![space3.vector([0, 6, 0])]).unwrap()
469            ),
470            SimplexOverlapResult::Touching
471        );
472
473        assert_eq!(
474            simplex_overlap(
475                &triangle,
476                &space3.simplex(vec![space3.vector([3, 3, 0])]).unwrap()
477            ),
478            SimplexOverlapResult::Touching
479        );
480    }
481
482    #[test]
483    fn line_and_triangle() {
484        let space3 = AffineSpace::new_linear(Rational::structure_ref(), 3);
485
486        let triangle = space3
487            .simplex(vec![
488                space3.vector([6, 0, 0]),
489                space3.vector([0, 6, 0]),
490                space3.vector([0, 0, 6]),
491            ])
492            .unwrap();
493
494        assert_eq!(
495            simplex_overlap(
496                &triangle,
497                &space3
498                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([1, 1, 1])])
499                    .unwrap()
500            ),
501            SimplexOverlapResult::Disjoint
502        );
503        assert_eq!(
504            simplex_overlap(
505                &triangle,
506                &space3
507                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([2, 2, 2])])
508                    .unwrap()
509            ),
510            SimplexOverlapResult::Touching
511        );
512        assert_eq!(
513            simplex_overlap(
514                &triangle,
515                &space3
516                    .simplex(vec![space3.vector([0, 0, 0]), space3.vector([3, 3, 3])])
517                    .unwrap()
518            ),
519            SimplexOverlapResult::Overlap
520        );
521
522        assert_eq!(
523            simplex_overlap(
524                &triangle,
525                &space3
526                    .simplex(vec![space3.vector([6, 0, 0]), space3.vector([7, 0, 0])])
527                    .unwrap()
528            ),
529            SimplexOverlapResult::Touching
530        );
531
532        assert_eq!(
533            simplex_overlap(
534                &triangle,
535                &space3
536                    .simplex(vec![space3.vector([0, -6, 0]), space3.vector([6, 0, 0])])
537                    .unwrap()
538            ),
539            SimplexOverlapResult::Touching
540        );
541
542        assert_eq!(
543            simplex_overlap(
544                &triangle,
545                &space3
546                    .simplex(vec![space3.vector([6, -12, 0]), space3.vector([12, -6, 0])])
547                    .unwrap()
548            ),
549            SimplexOverlapResult::Disjoint
550        );
551    }
552
553    #[test]
554    fn triangle_and_triangle() {
555        let space3 = AffineSpace::new_linear(Rational::structure_ref(), 3);
556
557        assert_eq!(
558            simplex_overlap(
559                &space3
560                    .simplex(vec![
561                        space3.vector([0, -1, 0]),
562                        space3.vector([0, 1, 0]),
563                        space3.vector([1, 0, 0])
564                    ])
565                    .unwrap(),
566                &space3
567                    .simplex(vec![
568                        space3.vector([0, 0, -1]),
569                        space3.vector([0, 0, 1]),
570                        space3.vector([-1, 0, 0])
571                    ])
572                    .unwrap()
573            ),
574            SimplexOverlapResult::Touching
575        );
576
577        assert_eq!(
578            simplex_overlap(
579                &space3
580                    .simplex(vec![
581                        space3.vector([0, -1, 0]),
582                        space3.vector([0, 1, 0]),
583                        space3.vector([1, 0, 0])
584                    ])
585                    .unwrap(),
586                &space3
587                    .simplex(vec![
588                        space3.vector([1, 0, -1]),
589                        space3.vector([1, 0, 1]),
590                        space3.vector([0, 0, 0])
591                    ])
592                    .unwrap()
593            ),
594            SimplexOverlapResult::Overlap
595        );
596
597        assert_eq!(
598            simplex_overlap(
599                &space3
600                    .simplex(vec![
601                        space3.vector([0, -1, 0]),
602                        space3.vector([0, 1, 0]),
603                        space3.vector([1, 0, 0])
604                    ])
605                    .unwrap(),
606                &space3
607                    .simplex(vec![
608                        space3.vector([-1, 0, -1]),
609                        space3.vector([-1, 0, 1]),
610                        space3.vector([-2, 0, 0])
611                    ])
612                    .unwrap()
613            ),
614            SimplexOverlapResult::Disjoint
615        );
616    }
617}