#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CutPlane {
XY(f32),
YZ(f32),
XZ(f32),
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MeshCutConfig {
pub epsilon: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CutResult {
pub above_verts: Vec<usize>,
pub below_verts: Vec<usize>,
pub cut_edges: Vec<(usize, usize)>,
pub above_faces: Vec<usize>,
pub below_faces: Vec<usize>,
pub crossing_faces: Vec<usize>,
}
#[allow(dead_code)]
pub fn default_cut_config() -> MeshCutConfig {
MeshCutConfig { epsilon: 1e-6 }
}
#[allow(dead_code)]
pub fn cut_mesh(
verts: &[[f32; 3]],
faces: &[[u32; 3]],
plane: CutPlane,
cfg: &MeshCutConfig,
) -> CutResult {
let sides: Vec<f32> = verts.iter().map(|&v| vertex_side(v, plane)).collect();
let mut above_verts: Vec<usize> = Vec::new();
let mut below_verts: Vec<usize> = Vec::new();
for (i, &s) in sides.iter().enumerate() {
if s >= -cfg.epsilon {
above_verts.push(i);
} else {
below_verts.push(i);
}
}
let mut above_faces: Vec<usize> = Vec::new();
let mut below_faces: Vec<usize> = Vec::new();
let mut crossing_faces: Vec<usize> = Vec::new();
let mut cut_edge_set: std::collections::HashSet<(usize, usize)> =
std::collections::HashSet::new();
for (fi, face) in faces.iter().enumerate() {
let s: Vec<f32> = face.iter().map(|&v| sides[v as usize]).collect();
let any_above = s.iter().any(|&x| x >= -cfg.epsilon);
let any_below = s.iter().any(|&x| x < -cfg.epsilon);
match (any_above, any_below) {
(true, false) => above_faces.push(fi),
(false, true) => below_faces.push(fi),
_ => {
crossing_faces.push(fi);
let vf = [face[0] as usize, face[1] as usize, face[2] as usize];
for k in 0..3 {
let a = vf[k];
let b = vf[(k + 1) % 3];
let sa = sides[a];
let sb = sides[b];
if (sa >= -cfg.epsilon) != (sb >= -cfg.epsilon) {
let key = if a < b { (a, b) } else { (b, a) };
cut_edge_set.insert(key);
}
}
}
}
}
let mut cut_edges: Vec<(usize, usize)> = cut_edge_set.into_iter().collect();
cut_edges.sort_unstable();
CutResult {
above_verts,
below_verts,
cut_edges,
above_faces,
below_faces,
crossing_faces,
}
}
#[allow(dead_code)]
pub fn cut_result_above_count(result: &CutResult) -> usize {
result.above_verts.len()
}
#[allow(dead_code)]
pub fn cut_result_below_count(result: &CutResult) -> usize {
result.below_verts.len()
}
#[allow(dead_code)]
pub fn cut_edge_count(result: &CutResult) -> usize {
result.cut_edges.len()
}
#[allow(dead_code)]
pub fn vertex_side(v: [f32; 3], plane: CutPlane) -> f32 {
match plane {
CutPlane::XY(offset) => v[2] - offset,
CutPlane::YZ(offset) => v[0] - offset,
CutPlane::XZ(offset) => v[1] - offset,
}
}
#[allow(dead_code)]
pub fn cut_plane_offset(plane: CutPlane) -> f32 {
match plane {
CutPlane::XY(o) | CutPlane::YZ(o) | CutPlane::XZ(o) => o,
}
}
#[allow(dead_code)]
pub fn cut_plane_name(plane: CutPlane) -> &'static str {
match plane {
CutPlane::XY(_) => "XY",
CutPlane::YZ(_) => "YZ",
CutPlane::XZ(_) => "XZ",
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cfg() -> MeshCutConfig {
default_cut_config()
}
#[test]
fn test_all_above_xz_plane() {
let verts: Vec<[f32; 3]> = vec![[0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.5, 1.0, 1.0]];
let faces = vec![[0u32, 1, 2]];
let result = cut_mesh(&verts, &faces, CutPlane::XY(0.0), &cfg());
assert_eq!(cut_result_above_count(&result), 3);
assert_eq!(cut_result_below_count(&result), 0);
assert_eq!(cut_edge_count(&result), 0);
assert_eq!(result.above_faces.len(), 1);
assert!(result.crossing_faces.is_empty());
}
#[test]
fn test_all_below_xy_plane() {
let verts: Vec<[f32; 3]> = vec![[0.0, 0.0, -1.0], [1.0, 0.0, -1.0], [0.5, 1.0, -1.0]];
let faces = vec![[0u32, 1, 2]];
let result = cut_mesh(&verts, &faces, CutPlane::XY(0.0), &cfg());
assert_eq!(cut_result_above_count(&result), 0);
assert_eq!(cut_result_below_count(&result), 3);
assert_eq!(cut_edge_count(&result), 0);
assert_eq!(result.below_faces.len(), 1);
}
#[test]
fn test_crossing_triangle() {
let verts: Vec<[f32; 3]> = vec![[0.0, 0.0, -1.0], [1.0, 0.0, -1.0], [0.5, 1.0, 1.0]];
let faces = vec![[0u32, 1, 2]];
let result = cut_mesh(&verts, &faces, CutPlane::XY(0.0), &cfg());
assert_eq!(result.crossing_faces.len(), 1);
assert_eq!(cut_edge_count(&result), 2);
}
#[test]
fn test_vertex_side_sign() {
assert!(vertex_side([0.0, 0.0, 1.0], CutPlane::XY(0.0)) > 0.0);
assert!(vertex_side([0.0, 0.0, -1.0], CutPlane::XY(0.0)) < 0.0);
assert_eq!(vertex_side([0.0, 0.0, 0.0], CutPlane::XY(0.0)), 0.0);
}
#[test]
fn test_yz_plane_split() {
let verts: Vec<[f32; 3]> = vec![[2.0, 0.0, 0.0], [-2.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = vec![[0u32, 1, 2]];
let result = cut_mesh(&verts, &faces, CutPlane::YZ(0.0), &cfg());
assert_eq!(result.crossing_faces.len(), 1);
}
#[test]
fn test_xz_plane_split() {
let verts: Vec<[f32; 3]> = vec![[0.0, 2.0, 0.0], [0.0, -2.0, 0.0], [1.0, 0.0, 0.0]];
let faces = vec![[0u32, 1, 2]];
let result = cut_mesh(&verts, &faces, CutPlane::XZ(0.0), &cfg());
assert_eq!(result.crossing_faces.len(), 1);
}
#[test]
fn test_cut_plane_name() {
assert_eq!(cut_plane_name(CutPlane::XY(0.0)), "XY");
assert_eq!(cut_plane_name(CutPlane::YZ(1.0)), "YZ");
assert_eq!(cut_plane_name(CutPlane::XZ(-1.0)), "XZ");
}
#[test]
fn test_cut_plane_offset() {
assert!((cut_plane_offset(CutPlane::XY(3.5)) - 3.5).abs() < 1e-9);
assert!((cut_plane_offset(CutPlane::YZ(-1.0)) - (-1.0)).abs() < 1e-9);
}
#[test]
fn test_offset_plane() {
let verts: Vec<[f32; 3]> =
vec![[0.0, 0.0, 1.5], [1.0, 0.0, 2.5], [0.5, 1.0, 2.5]];
let faces = vec![[0u32, 1, 2]];
let result = cut_mesh(&verts, &faces, CutPlane::XY(2.0), &cfg());
assert_eq!(cut_result_above_count(&result), 2);
assert_eq!(cut_result_below_count(&result), 1);
}
}