pub type Cutout = (Vec<[f64; 3]>, Vec<[u32; 3]>);
pub fn inside_ring(point: [f64; 2], ring: &[[f64; 2]]) -> bool {
if ring.len() < 3 {
return false;
}
let [x, y] = point;
let mut inside = false;
let mut previous = ring[ring.len() - 1];
for ¤t in ring {
let [x1, y1] = previous;
let [x2, y2] = current;
let cross = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1);
if cross.abs() < 1e-12
&& x >= x1.min(x2) - 1e-12
&& x <= x1.max(x2) + 1e-12
&& y >= y1.min(y2) - 1e-12
&& y <= y1.max(y2) + 1e-12
{
return true;
}
if (y1 > y) != (y2 > y) {
let at = (x2 - x1) * (y - y1) / (y2 - y1) + x1;
if x < at {
inside = !inside;
}
}
previous = current;
}
inside
}
pub fn trim(points: &[[f64; 3]], triangles: &[[u32; 3]], ring: &[[f64; 2]], keep_inside: bool) -> Cutout {
let mut kept: Vec<[u32; 3]> = Vec::new();
for triangle in triangles {
let corners: Vec<&[f64; 3]> = triangle
.iter()
.filter_map(|&index| points.get(index as usize))
.collect();
if corners.len() != 3 || corners.iter().any(|c| c.iter().any(|v| !v.is_finite())) {
continue;
}
let centre = [
(corners[0][0] + corners[1][0] + corners[2][0]) / 3.0,
(corners[0][1] + corners[1][1] + corners[2][1]) / 3.0,
];
if inside_ring(centre, ring) == keep_inside {
kept.push(*triangle);
}
}
compact(points, &kept)
}
pub fn side_of_line(point: [f64; 2], line: &[[f64; 2]]) -> f64 {
let [x, y] = point;
let mut best = f64::MAX;
let mut side = 0.0;
for pair in line.windows(2) {
let [a, b] = [pair[0], pair[1]];
let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
let length2 = dx * dx + dy * dy;
if length2 < 1e-24 {
continue;
}
let t = (((x - a[0]) * dx + (y - a[1]) * dy) / length2).clamp(0.0, 1.0);
let (px, py) = (a[0] + dx * t, a[1] + dy * t);
let d2 = (x - px) * (x - px) + (y - py) * (y - py);
if d2 < best {
best = d2;
side = dx * (y - a[1]) - dy * (x - a[0]);
}
}
side
}
pub fn split_by_line(points: &[[f64; 3]], triangles: &[[u32; 3]], line: &[[f64; 2]]) -> [Cutout; 2] {
let mut left: Vec<[u32; 3]> = Vec::new();
let mut right: Vec<[u32; 3]> = Vec::new();
for triangle in triangles {
let corners: Vec<&[f64; 3]> = triangle
.iter()
.filter_map(|&index| points.get(index as usize))
.collect();
if corners.len() != 3 || corners.iter().any(|c| c.iter().any(|v| !v.is_finite())) {
continue;
}
let centre = [
(corners[0][0] + corners[1][0] + corners[2][0]) / 3.0,
(corners[0][1] + corners[1][1] + corners[2][1]) / 3.0,
];
if side_of_line(centre, line) >= 0.0 {
left.push(*triangle);
} else {
right.push(*triangle);
}
}
[compact(points, &left), compact(points, &right)]
}
fn compact(points: &[[f64; 3]], kept: &[[u32; 3]]) -> Cutout {
let mut remap: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
let mut out_points: Vec<[f64; 3]> = Vec::new();
let mut out_triangles: Vec<[u32; 3]> = Vec::with_capacity(kept.len());
for triangle in kept {
let mut mapped = [0u32; 3];
for (slot, &index) in triangle.iter().enumerate() {
let next = *remap.entry(index).or_insert_with(|| {
out_points.push(points[index as usize]);
(out_points.len() - 1) as u32
});
mapped[slot] = next;
}
out_triangles.push(mapped);
}
(out_points, out_triangles)
}
pub fn nodes_in_ring(points: &[f64], ring: &[f64], outside: bool) -> Result<Vec<u32>, String> {
if !points.len().is_multiple_of(3) {
return Err(format!("{} coordinate(s) - nodes are triplets", points.len()));
}
if !ring.len().is_multiple_of(2) {
return Err(format!("{} coordinate(s) - a ring in plan is pairs", ring.len()));
}
let corners: Vec<[f64; 2]> = ring.as_chunks::<2>().0.iter().map(|c| [c[0], c[1]]).collect();
if corners.len() < 3 {
return Err(format!(
"a ring of {} corner(s) encloses nothing - three at least",
corners.len()
));
}
Ok(points
.as_chunks::<3>()
.0
.iter()
.enumerate()
.filter(|(_, p)| p.iter().all(|c| c.is_finite()))
.filter(|(_, p)| inside_ring([p[0], p[1]], &corners) != outside)
.map(|(at, _)| at as u32)
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_nodes_inside_a_ring_and_those_outside() {
let ring = [0.0, 0.0, 10.0, 0.0, 10.0, 10.0, 0.0, 10.0];
let points = [
5.0,
5.0,
-100.0,
20.0,
5.0,
0.0,
10.0,
5.0,
3.0,
f64::NAN,
5.0,
0.0,
];
assert_eq!(nodes_in_ring(&points, &ring, false).unwrap(), vec![0, 2]);
assert_eq!(nodes_in_ring(&points, &ring, true).unwrap(), vec![1]);
}
#[test]
fn a_ring_of_two_corners_is_refused() {
assert!(
nodes_in_ring(&[0.0; 3], &[0.0, 0.0, 1.0, 1.0], false)
.unwrap_err()
.contains("three")
);
assert!(nodes_in_ring(&[0.0; 4], &[0.0; 6], false).is_err());
}
fn a_meshed_square() -> (Vec<[f64; 3]>, Vec<[u32; 3]>) {
let mut points = Vec::new();
for j in 0..5 {
for i in 0..5 {
points.push([i as f64, j as f64, 1.0]);
}
}
let mut triangles = Vec::new();
for j in 0..4u32 {
for i in 0..4u32 {
let a = j * 5 + i;
triangles.push([a, a + 1, a + 6]);
triangles.push([a, a + 6, a + 5]);
}
}
(points, triangles)
}
#[test]
fn inside_and_outside_partition_the_surface() {
let (points, triangles) = a_meshed_square();
let ring = [[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]];
let (in_points, in_triangles) = trim(&points, &triangles, &ring, true);
let (out_points, out_triangles) = trim(&points, &triangles, &ring, false);
assert_eq!(in_triangles.len() + out_triangles.len(), triangles.len());
assert_eq!(in_triangles.len(), 8, "2×2 cells of two triangles each");
assert_eq!(in_points.len(), 9, "the 3×3 lattice of the cut quarter");
assert!(out_points.len() < points.len());
for triangle in &in_triangles {
assert!(triangle.iter().all(|&index| (index as usize) < in_points.len()));
}
}
#[test]
fn the_boundary_counts_as_inside_deterministically() {
let ring = [[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]];
assert!(inside_ring([1.0, 1.0], &ring));
assert!(
inside_ring([2.0, 1.0], &ring),
"on the edge: inside, without flickering"
);
assert!(inside_ring([0.0, 0.0], &ring), "a corner too");
assert!(!inside_ring([3.0, 1.0], &ring));
}
#[test]
fn an_empty_catch_is_an_empty_answer_not_a_panic() {
let (points, triangles) = a_meshed_square();
let ring = [[10.0, 10.0], [11.0, 10.0], [11.0, 11.0], [10.0, 11.0]];
let (kept_points, kept) = trim(&points, &triangles, &ring, true);
assert!(kept.is_empty());
assert!(kept_points.is_empty());
}
#[test]
fn a_line_down_the_middle_splits_the_square_in_halves() {
let (points, triangles) = a_meshed_square();
let line = [[2.0, -1.0], [2.0, 5.0]];
let [(left_points, left), (right_points, right)] = split_by_line(&points, &triangles, &line);
assert_eq!(left.len() + right.len(), triangles.len());
assert_eq!(
left.len(),
right.len(),
"a vertical down the middle shares it out evenly"
);
for (half_points, half) in [(&left_points, &left), (&right_points, &right)] {
for triangle in half.iter() {
assert!(triangle.iter().all(|&index| (index as usize) < half_points.len()));
}
}
for point in &left_points {
assert!(point[0] <= 2.0 + 1e-9);
}
}
#[test]
fn a_nan_corner_drops_its_triangle_from_both_sides() {
let (mut points, triangles) = a_meshed_square();
points[0][2] = f64::NAN;
let ring = [[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]];
let (_, inside) = trim(&points, &triangles, &ring, true);
let (_, outside) = trim(&points, &triangles, &ring, false);
assert_eq!(inside.len() + outside.len(), triangles.len() - 2);
}
}