#![allow(dead_code)]
#[derive(Debug, Clone, Copy)]
pub struct SlicePlane {
pub origin: [f32; 3],
pub normal: [f32; 3],
}
impl SlicePlane {
pub fn new(origin: [f32; 3], normal: [f32; 3]) -> Self {
let n = normalize3(normal);
Self { origin, normal: n }
}
pub fn signed_distance(&self, point: [f32; 3]) -> f32 {
let d = [
point[0] - self.origin[0],
point[1] - self.origin[1],
point[2] - self.origin[2],
];
dot3(d, self.normal)
}
}
fn dot3(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn normalize3(v: [f32; 3]) -> [f32; 3] {
let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
if len < 1e-12 {
return [0.0, 1.0, 0.0];
}
[v[0] / len, v[1] / len, v[2] / len]
}
#[derive(Debug, Clone, Default)]
pub struct SliceResult {
pub above_verts: Vec<[f32; 3]>,
pub above_tris: Vec<[u32; 3]>,
pub below_verts: Vec<[f32; 3]>,
pub below_tris: Vec<[u32; 3]>,
pub cross_section_verts: Vec<[f32; 3]>,
}
pub fn classify_vertices(verts: &[[f32; 3]], plane: &SlicePlane) -> Vec<f32> {
verts.iter().map(|&v| plane.signed_distance(v)).collect()
}
const MIN_TRIANGLE_AREA: f32 = 1e-10;
#[inline]
fn triangle_area(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> f32 {
let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let cx = ab[1] * ac[2] - ab[2] * ac[1];
let cy = ab[2] * ac[0] - ab[0] * ac[2];
let cz = ab[0] * ac[1] - ab[1] * ac[0];
0.5 * (cx * cx + cy * cy + cz * cz).sqrt()
}
#[inline]
fn push_tri(
verts_out: &mut Vec<[f32; 3]>,
tris_out: &mut Vec<[u32; 3]>,
a: [f32; 3],
b: [f32; 3],
c: [f32; 3],
) {
if triangle_area(a, b, c) < MIN_TRIANGLE_AREA {
return;
}
let base = verts_out.len() as u32;
verts_out.push(a);
verts_out.push(b);
verts_out.push(c);
tris_out.push([base, base + 1, base + 2]);
}
pub fn slice_mesh_with_plane(
verts: &[[f32; 3]],
tris: &[[u32; 3]],
plane: &SlicePlane,
) -> SliceResult {
const EPS: f32 = 1e-7;
let mut above_verts: Vec<[f32; 3]> = Vec::new();
let mut above_tris: Vec<[u32; 3]> = Vec::new();
let mut below_verts: Vec<[f32; 3]> = Vec::new();
let mut below_tris: Vec<[u32; 3]> = Vec::new();
let mut cross_section_verts: Vec<[f32; 3]> = Vec::new();
for tri in tris {
let [ia, ib, ic] = [tri[0] as usize, tri[1] as usize, tri[2] as usize];
if ia >= verts.len() || ib >= verts.len() || ic >= verts.len() {
continue;
}
let pa = verts[ia];
let pb = verts[ib];
let pc = verts[ic];
let da = plane.signed_distance(pa);
let db = plane.signed_distance(pb);
let dc = plane.signed_distance(pc);
let sa = da >= -EPS;
let sb = db >= -EPS;
let sc = dc >= -EPS;
let above_count = sa as u8 + sb as u8 + sc as u8;
match above_count {
3 => {
push_tri(&mut above_verts, &mut above_tris, pa, pb, pc);
}
0 => {
push_tri(&mut below_verts, &mut below_tris, pa, pb, pc);
}
1 => {
let (p_lone, d_lone, p1, d1, p2, d2) = if sa {
(pa, da, pb, db, pc, dc)
} else if sb {
(pb, db, pa, da, pc, dc)
} else {
(pc, dc, pa, da, pb, db)
};
let q1 = edge_plane_intersect(p_lone, p1, d_lone, d1);
let q2 = edge_plane_intersect(p_lone, p2, d_lone, d2);
cross_section_verts.push(q1);
cross_section_verts.push(q2);
push_tri(&mut above_verts, &mut above_tris, p_lone, q1, q2);
push_tri(&mut below_verts, &mut below_tris, p1, p2, q1);
push_tri(&mut below_verts, &mut below_tris, p2, q2, q1);
}
2 => {
let (p_lone, d_lone, p1, d1, p2, d2) = if !sa {
(pa, da, pb, db, pc, dc)
} else if !sb {
(pb, db, pa, da, pc, dc)
} else {
(pc, dc, pa, da, pb, db)
};
let q1 = edge_plane_intersect(p_lone, p1, d_lone, d1);
let q2 = edge_plane_intersect(p_lone, p2, d_lone, d2);
cross_section_verts.push(q1);
cross_section_verts.push(q2);
push_tri(&mut below_verts, &mut below_tris, p_lone, q1, q2);
push_tri(&mut above_verts, &mut above_tris, p1, p2, q1);
push_tri(&mut above_verts, &mut above_tris, p2, q2, q1);
}
_ => unreachable!(),
}
}
SliceResult {
above_verts,
above_tris,
below_verts,
below_tris,
cross_section_verts,
}
}
pub fn count_triangles_per_side(
verts: &[[f32; 3]],
tris: &[[u32; 3]],
plane: &SlicePlane,
) -> (usize, usize) {
let mut above = 0usize;
let mut below = 0usize;
for tri in tris {
let mut sum = 0.0f32;
for &idx in tri.iter() {
let vi = idx as usize;
if vi < verts.len() {
sum += plane.signed_distance(verts[vi]);
}
}
if sum >= 0.0 {
above += 1;
} else {
below += 1;
}
}
(above, below)
}
pub fn edge_plane_intersect(a: [f32; 3], b: [f32; 3], da: f32, db: f32) -> [f32; 3] {
let denom = da - db;
if denom.abs() < 1e-12 {
return a;
}
let t = da / denom;
[
a[0] + t * (b[0] - a[0]),
a[1] + t * (b[1] - a[1]),
a[2] + t * (b[2] - a[2]),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plane_distance_above() {
let p = SlicePlane::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!(p.signed_distance([0.0, 1.0, 0.0]) > 0.0 );
}
#[test]
fn test_plane_distance_below() {
let p = SlicePlane::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!(p.signed_distance([0.0, -1.0, 0.0]) < 0.0 );
}
#[test]
fn test_plane_distance_on() {
let p = SlicePlane::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!(p.signed_distance([1.0, 0.0, 0.0]).abs() < 1e-6 );
}
#[test]
fn test_classify_vertices_signs() {
let p = SlicePlane::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let verts = vec![[0.0f32, 1.0, 0.0], [0.0, -1.0, 0.0]];
let dists = classify_vertices(&verts, &p);
assert!(dists[0] > 0.0 );
assert!(dists[1] < 0.0 );
}
#[test]
fn test_slice_separates_triangles() {
let p = SlicePlane::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let verts = vec![
[0.0f32, 1.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 2.0, 0.0],
[0.0, -1.0, 0.0],
[1.0, -1.0, 0.0],
[0.0, -2.0, 0.0],
];
let tris = vec![[0u32, 1, 2], [3, 4, 5]];
let result = slice_mesh_with_plane(&verts, &tris, &p);
assert!(!result.above_tris.is_empty() );
assert!(!result.below_tris.is_empty() );
}
#[test]
fn test_count_per_side() {
let p = SlicePlane::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let verts = vec![
[0.0f32, 1.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 2.0, 0.0],
[0.0, -1.0, 0.0],
[1.0, -1.0, 0.0],
[0.0, -2.0, 0.0],
];
let tris = vec![[0u32, 1, 2], [3, 4, 5]];
let (a, b) = count_triangles_per_side(&verts, &tris, &p);
assert_eq!(a + b, 2 );
}
#[test]
fn test_edge_intersect_midpoint() {
let a = [0.0f32, -1.0, 0.0];
let b = [0.0f32, 1.0, 0.0];
let pt = edge_plane_intersect(a, b, -1.0, 1.0);
assert!(pt[1].abs() < 1e-5 );
}
#[test]
fn test_slice_empty_mesh() {
let p = SlicePlane::new([0.0; 3], [0.0, 1.0, 0.0]);
let result = slice_mesh_with_plane(&[], &[], &p);
assert!(result.above_tris.is_empty() );
assert!(result.below_tris.is_empty() );
}
#[test]
fn test_normalize_zero_vec() {
let n = normalize3([0.0, 0.0, 0.0]);
assert_eq!(n, [0.0, 1.0, 0.0] );
}
fn unit_cube() -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
let verts = vec![
[-0.5f32, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5], ];
let tris = vec![
[0u32, 2, 1],
[0, 3, 2],
[4, 5, 6],
[4, 6, 7],
[0, 4, 7],
[0, 7, 3],
[1, 2, 6],
[1, 6, 5],
[0, 1, 5],
[0, 5, 4],
[3, 7, 6],
[3, 6, 2],
];
(verts, tris)
}
#[test]
fn slice_cube_midplane() {
let (verts, tris) = unit_cube();
let plane = SlicePlane::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let result = slice_mesh_with_plane(&verts, &tris, &plane);
assert!(
!result.above_tris.is_empty(),
"cube slice must produce above-triangles"
);
assert!(
!result.below_tris.is_empty(),
"cube slice must produce below-triangles"
);
for &v in &result.cross_section_verts {
assert!(
v[1].abs() < 1e-4,
"cross-section vertex y must be ≈0, got {}",
v[1]
);
}
}
#[test]
fn slice_plane_cuts_straddling_triangle() {
let verts = vec![
[0.0f32, -1.0, 0.0], [1.0, 1.0, 0.0], [-1.0, 1.0, 0.0], ];
let tris = vec![[0u32, 1, 2]];
let plane = SlicePlane::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let result = slice_mesh_with_plane(&verts, &tris, &plane);
assert!(
!result.below_tris.is_empty(),
"straddling triangle must produce below triangles"
);
assert!(
!result.above_tris.is_empty(),
"straddling triangle must produce above triangles"
);
assert_eq!(
result.cross_section_verts.len(),
2,
"exactly 2 cross-section vertices for a single straddling triangle"
);
for &v in &result.cross_section_verts {
assert!(
v[1].abs() < 1e-4,
"cross-section vertex y must be ≈0, got {}",
v[1]
);
}
}
}