#![allow(clippy::float_cmp)]
use super::*;
use alloc::vec;
const H: f64 = 1.0;
#[test]
fn crossing_triangles_are_detected() {
let p = vec![
[0.0, 0.0, 0.0],
[2.0, 0.0, 0.0],
[0.0, 2.0, 0.0],
[0.5, 0.5, -1.0],
[0.5, 0.5, 1.0],
[1.5, 1.5, 0.0],
];
let idx = vec![0, 1, 2, 3, 4, 5];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert_eq!(r.pairs, [[0, 1]]);
assert_eq!(r.count(), 1);
assert_eq!(r.triangles, 2);
assert!((r.per_thousand_triangles() - 500.0).abs() < 1e-9);
assert!(!r.is_intersection_free());
}
#[test]
fn edge_adjacent_triangles_are_not_counted() {
let p = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
];
let idx = vec![0, 1, 2, 0, 1, 3];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert!(r.is_intersection_free(), "{r}");
assert_eq!(r.adjacent_pairs_skipped, 1);
assert_eq!(r.tested_pairs, 0);
}
#[test]
fn vertex_adjacent_triangles_are_not_counted() {
let p = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[-1.0, 0.0, 0.0],
[-1.0, -1.0, 0.0],
];
let idx = vec![0, 1, 2, 0, 3, 4];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert!(r.is_intersection_free(), "{r}");
assert_eq!(r.adjacent_pairs_skipped, 1);
}
#[test]
fn distant_triangles_are_not_counted() {
let p = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[50.0, 50.0, 50.0],
[51.0, 50.0, 50.0],
[50.0, 51.0, 50.0],
];
let idx = vec![0, 1, 2, 3, 4, 5];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert!(r.is_intersection_free());
assert_eq!(r.tested_pairs, 0);
}
#[test]
fn coplanar_overlapping_triangles_are_detected() {
let p = vec![
[0.0, 0.0, 0.0],
[2.0, 0.0, 0.0],
[0.0, 2.0, 0.0],
[0.5, 0.5, 0.0],
[2.5, 0.5, 0.0],
[0.5, 2.5, 0.0],
];
let idx = vec![0, 1, 2, 3, 4, 5];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert_eq!(r.count(), 1, "{r}");
}
#[test]
fn coplanar_disjoint_triangles_are_not_counted() {
let p = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[3.0, 3.0, 0.0],
[4.0, 3.0, 0.0],
[3.0, 4.0, 0.0],
];
let idx = vec![0, 1, 2, 3, 4, 5];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert!(r.is_intersection_free(), "{r}");
}
#[test]
fn coplanar_touching_triangles_are_not_counted() {
let p = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
];
let idx = vec![0, 1, 2, 3, 4, 5];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert!(r.is_intersection_free(), "{r}");
assert_eq!(r.adjacent_pairs_skipped, 0, "these share no index");
assert_eq!(r.tested_pairs, 1, "the exact test really did run");
}
#[test]
fn a_tetrahedron_is_intersection_free() {
let p = vec![
[1.0, 1.0, 1.0],
[1.0, -1.0, -1.0],
[-1.0, 1.0, -1.0],
[-1.0, -1.0, 1.0],
];
let idx = vec![0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert!(r.is_intersection_free(), "{r}");
assert_eq!(r.triangles, 4);
assert_eq!(r.adjacent_pairs_skipped, 6);
assert_eq!(r.per_thousand_triangles(), 0.0);
}
#[test]
fn a_torus_grid_is_intersection_free() {
let (p, idx) = torus_grid(12, 8);
let r = self_intersections(&p, &idx, 0.25).expect("self intersections");
assert!(r.is_intersection_free(), "{r}");
assert_eq!(r.triangles, 192);
assert!(
r.tested_pairs > 0,
"the grid must not have separated everything"
);
}
fn torus_grid(m: u32, n: u32) -> (alloc::vec::Vec<[f64; 3]>, alloc::vec::Vec<u32>) {
let tau = 2.0 * core::f64::consts::PI;
let (major, minor) = (1.0f64, 0.3f64);
let mut positions = alloc::vec::Vec::new();
for i in 0..m {
let theta = tau * f64::from(i) / f64::from(m);
for j in 0..n {
let phi = tau * f64::from(j) / f64::from(n);
let ring = major + minor * Real::cos(phi);
positions.push([
ring * Real::cos(theta),
minor * Real::sin(phi),
ring * Real::sin(theta),
]);
}
}
let at = |i: u32, j: u32| (i % m) * n + (j % n);
let mut indices = alloc::vec::Vec::new();
for i in 0..m {
for j in 0..n {
let (a, b, c, d) = (at(i, j), at(i + 1, j), at(i + 1, j + 1), at(i, j + 1));
indices.extend_from_slice(&[a, b, c, a, c, d]);
}
}
(positions, indices)
}
#[test]
fn degenerate_triangles_are_excluded_and_reported() {
let p = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[2.0, 0.0, 0.0], [0.5, -1.0, 0.0],
[0.5, 1.0, 0.0],
[0.5, 0.0, 1.0],
];
let idx = vec![0, 1, 2, 3, 4, 5];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert_eq!(r.degenerate_triangles, 1);
assert_eq!(r.tested_pairs, 0);
assert!(r.is_intersection_free());
}
#[test]
fn the_rate_is_per_thousand_triangles() {
let empty: alloc::vec::Vec<[f64; 3]> = alloc::vec::Vec::new();
let r = self_intersections(&empty, &[], H).expect("self intersections");
assert_eq!(r.triangles, 0);
assert_eq!(r.per_thousand_triangles(), 0.0, "no divide by zero");
assert!(r.is_intersection_free());
let (p, idx) = torus_grid(12, 8);
let r = self_intersections(&p, &idx, 0.25).expect("self intersections");
assert_eq!(r.per_thousand_triangles(), 0.0);
}
#[test]
fn malformed_indices_do_not_panic() {
let p = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0f64]];
let idx = vec![0, 1, 9, 0, 1, 1, 0, 1, 2, 0];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert_eq!(r.triangles, 1, "only the well-formed triangle survives");
}
#[test]
fn results_are_deterministic() {
let (p, idx) = torus_grid(10, 6);
let a = self_intersections(&p, &idx, 0.25).expect("self intersections");
let b = self_intersections(&p, &idx, 0.25).expect("self intersections");
assert_eq!(a, b);
assert_eq!(alloc::format!("{a}"), alloc::format!("{b}"));
}
#[test]
fn pairs_come_back_sorted_so_they_can_be_bucketed_later() {
let p = vec![
[-1.0, -1.0, 0.0],
[3.0, -1.0, 0.0],
[-1.0, 3.0, 0.0],
[0.5, -1.0, -1.0],
[0.5, 3.0, -1.0],
[0.5, 0.5, 1.0],
[-1.0, 0.5, -1.0],
[3.0, 0.5, -1.0],
[0.5, 0.5, 1.5],
];
let idx = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
let r = self_intersections(&p, &idx, 2.0).expect("self intersections");
assert!(r.count() >= 2, "{r}");
let mut sorted = r.pairs.clone();
sorted.sort_unstable();
assert_eq!(r.pairs, sorted);
for pair in &r.pairs {
assert!(pair[0] < pair[1], "each pair is [low, high]");
}
}
#[test]
fn a_meaningless_cell_size_is_rejected() {
let p = vec![[0.0, 0.0, 0.0f64]];
let error = self_intersections(&p, &[], 0.0).expect_err("zero is not a spacing");
assert_eq!(error, crate::Error::InvalidCellSize { value: 0.0 });
}
#[test]
fn a_cell_size_that_does_not_describe_the_mesh_is_rejected() {
let p = vec![[0.0, 0.0, 0.0], [1000.0, 0.0, 0.0], [0.0, 1000.0, 0.0f64]];
let idx = vec![0, 1, 2];
let error = self_intersections(&p, &idx, 1e-3).expect_err("spacing far finer than the mesh");
assert!(
matches!(error, crate::Error::CellSizeMismatch { .. }),
"{error}"
);
}
#[test]
fn a_point_contact_is_not_an_intersection() {
let p = vec![
[-2.0, -1.0, 0.0],
[2.0, -1.0, 0.0],
[0.0, 2.0, 0.0],
[0.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
[-1.0, 1.0, 1.0],
];
let idx = vec![0, 1, 2, 3, 4, 5];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert_eq!(r.count(), 0, "a point contact was counted as a crossing");
}
#[test]
fn an_edge_in_the_plane_is_not_an_intersection() {
let p = vec![
[-2.0, -1.0, 0.0],
[2.0, -1.0, 0.0],
[0.0, 2.0, 0.0],
[-0.5, 0.0, 0.0],
[0.5, 0.0, 0.0],
[0.0, 0.0, 1.0],
];
let idx = vec![0, 1, 2, 3, 4, 5];
let r = self_intersections(&p, &idx, H).expect("self intersections");
assert_eq!(
r.count(),
0,
"an edge-in-plane contact was counted as a crossing"
);
}