use glam::Vec3;
#[derive(Debug, Clone)]
pub struct CellSliceResult {
pub vertices: Vec<Vec3>,
pub interpolation: Vec<(u32, u32, f32)>,
}
impl CellSliceResult {
#[must_use]
pub fn empty() -> Self {
Self {
vertices: Vec::new(),
interpolation: Vec::new(),
}
}
#[must_use]
pub fn has_intersection(&self) -> bool {
self.vertices.len() >= 3
}
}
#[must_use]
pub fn slice_tet(
v0: Vec3,
v1: Vec3,
v2: Vec3,
v3: Vec3,
plane_origin: Vec3,
plane_normal: Vec3,
) -> CellSliceResult {
let verts = [v0, v1, v2, v3];
let d: [f32; 4] = std::array::from_fn(|i| (verts[i] - plane_origin).dot(plane_normal));
let mut intersections = Vec::new();
let mut interp_data = Vec::new();
let edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)];
for &(i, j) in &edges {
if d[i] * d[j] < 0.0 {
let t = d[i] / (d[i] - d[j]);
let point = verts[i].lerp(verts[j], t);
intersections.push(point);
interp_data.push((i as u32, j as u32, t));
}
}
if intersections.len() >= 3 {
order_polygon_vertices(&mut intersections, &mut interp_data, plane_normal);
}
CellSliceResult {
vertices: intersections,
interpolation: interp_data,
}
}
#[must_use]
pub fn slice_hex(vertices: [Vec3; 8], plane_origin: Vec3, plane_normal: Vec3) -> CellSliceResult {
let tet_indices = [
[0, 1, 3, 4],
[1, 2, 3, 6],
[1, 4, 5, 6],
[3, 4, 6, 7],
[1, 3, 4, 6], ];
let mut all_vertices = Vec::new();
let mut all_interp = Vec::new();
for tet in &tet_indices {
let result = slice_tet(
vertices[tet[0]],
vertices[tet[1]],
vertices[tet[2]],
vertices[tet[3]],
plane_origin,
plane_normal,
);
for (local_a, local_b, t) in result.interpolation {
let hex_a = tet[local_a as usize] as u32;
let hex_b = tet[local_b as usize] as u32;
all_interp.push((hex_a, hex_b, t));
}
all_vertices.extend(result.vertices);
}
merge_slice_vertices(&mut all_vertices, &mut all_interp);
if all_vertices.len() >= 3 {
order_polygon_vertices(&mut all_vertices, &mut all_interp, plane_normal);
}
CellSliceResult {
vertices: all_vertices,
interpolation: all_interp,
}
}
fn order_polygon_vertices(
vertices: &mut Vec<Vec3>,
interp: &mut Vec<(u32, u32, f32)>,
normal: Vec3,
) {
if vertices.len() < 3 {
return;
}
let centroid: Vec3 = vertices.iter().copied().sum::<Vec3>() / vertices.len() as f32;
let ref_dir = if let Some(first) = vertices.first() {
(*first - centroid).normalize()
} else {
return;
};
let mut indices: Vec<usize> = (0..vertices.len()).collect();
indices.sort_by(|&a, &b| {
let va = (vertices[a] - centroid).normalize();
let vb = (vertices[b] - centroid).normalize();
let cross_a = ref_dir.cross(va);
let cross_b = ref_dir.cross(vb);
let angle_a = cross_a.dot(normal).atan2(ref_dir.dot(va));
let angle_b = cross_b.dot(normal).atan2(ref_dir.dot(vb));
angle_a
.partial_cmp(&angle_b)
.unwrap_or(std::cmp::Ordering::Equal)
});
let sorted_verts: Vec<Vec3> = indices.iter().map(|&i| vertices[i]).collect();
let sorted_interp: Vec<_> = indices.iter().map(|&i| interp[i]).collect();
*vertices = sorted_verts;
*interp = sorted_interp;
}
fn merge_slice_vertices(vertices: &mut Vec<Vec3>, interp: &mut Vec<(u32, u32, f32)>) {
const EPSILON: f32 = 1e-4;
if vertices.len() <= 1 {
return;
}
let mut merged_verts = Vec::new();
let mut merged_interp = Vec::new();
for i in 0..vertices.len() {
let v = vertices[i];
let mut is_duplicate = false;
for merged_v in &merged_verts {
let diff: Vec3 = v - *merged_v;
if diff.length_squared() < EPSILON * EPSILON {
is_duplicate = true;
break;
}
}
if !is_duplicate {
merged_verts.push(v);
merged_interp.push(interp[i]);
}
}
*vertices = merged_verts;
*interp = merged_interp;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slice_tet_no_intersection() {
let result = slice_tet(
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(1.0, 1.0, 0.0),
Vec3::new(0.5, 1.0, 1.0),
Vec3::new(0.5, 2.0, 0.5),
Vec3::ZERO,
Vec3::Y,
);
assert!(result.vertices.is_empty());
assert!(!result.has_intersection());
}
#[test]
fn test_slice_tet_triangle() {
let result = slice_tet(
Vec3::new(0.0, -1.0, 0.0), Vec3::new(1.0, 1.0, 0.0), Vec3::new(-1.0, 1.0, 0.0), Vec3::new(0.0, 1.0, 1.0), Vec3::ZERO,
Vec3::Y,
);
assert_eq!(result.vertices.len(), 3);
assert!(result.has_intersection());
assert_eq!(result.interpolation.len(), 3);
for (_, _, t) in &result.interpolation {
assert!(*t >= 0.0 && *t <= 1.0);
}
}
#[test]
fn test_slice_tet_quad() {
let result = slice_tet(
Vec3::new(0.0, -1.0, 0.0), Vec3::new(1.0, -1.0, 0.0), Vec3::new(0.5, 1.0, 0.0), Vec3::new(0.5, 1.0, 1.0), Vec3::ZERO,
Vec3::Y,
);
assert_eq!(result.vertices.len(), 4);
assert!(result.has_intersection());
}
#[test]
fn test_slice_tet_interpolation_values() {
let v0 = Vec3::new(0.0, -1.0, 0.0);
let v1 = Vec3::new(0.0, 1.0, 0.0);
let v2 = Vec3::new(1.0, 0.0, 0.0);
let v3 = Vec3::new(0.0, 0.0, 1.0);
let result = slice_tet(v0, v1, v2, v3, Vec3::ZERO, Vec3::Y);
for (i, (a, b, t)) in result.interpolation.iter().enumerate() {
let verts = [v0, v1, v2, v3];
let computed = verts[*a as usize].lerp(verts[*b as usize], *t);
let diff = (computed - result.vertices[i]).length();
assert!(diff < 1e-5, "Interpolation mismatch at index {}", i);
}
}
#[test]
fn test_slice_hex_no_intersection() {
let vertices = [
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(1.0, 1.0, 0.0),
Vec3::new(1.0, 1.0, 1.0),
Vec3::new(0.0, 1.0, 1.0),
Vec3::new(0.0, 2.0, 0.0),
Vec3::new(1.0, 2.0, 0.0),
Vec3::new(1.0, 2.0, 1.0),
Vec3::new(0.0, 2.0, 1.0),
];
let result = slice_hex(vertices, Vec3::ZERO, Vec3::Y);
assert!(result.vertices.is_empty());
}
#[test]
fn test_slice_hex_quad() {
let vertices = [
Vec3::new(-0.5, -0.5, -0.5),
Vec3::new(0.5, -0.5, -0.5),
Vec3::new(0.5, -0.5, 0.5),
Vec3::new(-0.5, -0.5, 0.5),
Vec3::new(-0.5, 0.5, -0.5),
Vec3::new(0.5, 0.5, -0.5),
Vec3::new(0.5, 0.5, 0.5),
Vec3::new(-0.5, 0.5, 0.5),
];
let result = slice_hex(vertices, Vec3::ZERO, Vec3::Y);
assert!(result.has_intersection());
assert!(
result.vertices.len() >= 3,
"Expected at least 3 vertices, got {}",
result.vertices.len()
);
for v in &result.vertices {
assert!(v.y.abs() < 1e-5, "Vertex y={} should be 0", v.y);
}
let expected_corners = [
Vec3::new(-0.5, 0.0, -0.5),
Vec3::new(0.5, 0.0, -0.5),
Vec3::new(0.5, 0.0, 0.5),
Vec3::new(-0.5, 0.0, 0.5),
];
for corner in &expected_corners {
let has_near = result
.vertices
.iter()
.any(|v| (*v - *corner).length() < 0.1);
assert!(has_near, "Expected vertex near corner {:?}", corner);
}
}
#[test]
fn test_polygon_ordering() {
let v0 = Vec3::new(0.0, -1.0, 0.0);
let v1 = Vec3::new(1.0, 1.0, 0.0);
let v2 = Vec3::new(-1.0, 1.0, 0.0);
let v3 = Vec3::new(0.0, 1.0, 1.0);
let result = slice_tet(v0, v1, v2, v3, Vec3::ZERO, Vec3::Y);
if result.vertices.len() >= 3 {
let e1 = result.vertices[1] - result.vertices[0];
let e2 = result.vertices[2] - result.vertices[0];
let computed_normal = e1.cross(e2).normalize();
let dot = computed_normal.dot(Vec3::Y);
assert!(
dot.abs() > 0.9,
"Polygon normal should align with plane normal"
);
}
}
}