#![allow(dead_code)]
pub struct SplitConfig {
pub plane_normal: [f32; 3],
pub plane_d: f32,
pub cap_holes: bool,
pub keep_both_sides: bool,
}
pub struct SplitMesh {
pub positions: Vec<[f32; 3]>,
pub triangles: Vec<[u32; 3]>,
pub side: SplitSide,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SplitSide {
Front,
Back,
On,
}
pub struct MeshSplitResult {
pub front: SplitMesh,
pub back: SplitMesh,
pub cut_edge_count: usize,
}
#[allow(dead_code)]
pub fn dot_v3_split(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[allow(dead_code)]
pub fn default_split_config(normal: [f32; 3], d: f32) -> SplitConfig {
SplitConfig {
plane_normal: normal,
plane_d: d,
cap_holes: true,
keep_both_sides: true,
}
}
#[allow(dead_code)]
pub fn classify_vertex(pos: [f32; 3], normal: [f32; 3], d: f32) -> SplitSide {
const EPS: f32 = 1e-5;
let dist = dot_v3_split(pos, normal) - d;
if dist > EPS {
SplitSide::Front
} else if dist < -EPS {
SplitSide::Back
} else {
SplitSide::On
}
}
#[allow(dead_code)]
pub fn split_mesh_by_plane(
positions: &[[f32; 3]],
triangles: &[[u32; 3]],
cfg: &SplitConfig,
) -> MeshSplitResult {
let mut front_pos: Vec<[f32; 3]> = Vec::new();
let mut front_tri: Vec<[u32; 3]> = Vec::new();
let mut back_pos: Vec<[f32; 3]> = Vec::new();
let mut back_tri: Vec<[u32; 3]> = Vec::new();
let mut cut_edge_count = 0usize;
for tri in triangles {
let a = positions[tri[0] as usize];
let b = positions[tri[1] as usize];
let c = positions[tri[2] as usize];
let sa = classify_vertex(a, cfg.plane_normal, cfg.plane_d);
let sb = classify_vertex(b, cfg.plane_normal, cfg.plane_d);
let sc = classify_vertex(c, cfg.plane_normal, cfg.plane_d);
let front_count = [sa, sb, sc]
.iter()
.filter(|&&s| s == SplitSide::Front || s == SplitSide::On)
.count();
let back_count = [sa, sb, sc]
.iter()
.filter(|&&s| s == SplitSide::Back)
.count();
if back_count == 0 {
let base = front_pos.len() as u32;
front_pos.extend_from_slice(&[a, b, c]);
front_tri.push([base, base + 1, base + 2]);
} else if front_count == 0 {
let base = back_pos.len() as u32;
back_pos.extend_from_slice(&[a, b, c]);
back_tri.push([base, base + 1, base + 2]);
} else {
cut_edge_count += 1;
let pts = [a, b, c];
let sides = [sa, sb, sc];
let mut f_pts: Vec<[f32; 3]> = Vec::new();
let mut bk_pts: Vec<[f32; 3]> = Vec::new();
for (i, &s) in sides.iter().enumerate() {
match s {
SplitSide::Front | SplitSide::On => f_pts.push(pts[i]),
SplitSide::Back => bk_pts.push(pts[i]),
}
}
let mut mids: Vec<[f32; 3]> = Vec::new();
for &fp in &f_pts {
for &bp in &bk_pts {
let t = plane_edge_t(fp, bp, cfg.plane_normal, cfg.plane_d);
mids.push(lerp_v3(fp, bp, t));
}
}
for &mid in &mids {
f_pts.push(mid);
bk_pts.push(mid);
}
if f_pts.len() >= 3 {
let base = front_pos.len() as u32;
front_pos.extend_from_slice(&f_pts);
for i in 1..(f_pts.len() as u32 - 1) {
front_tri.push([base, base + i, base + i + 1]);
}
}
if bk_pts.len() >= 3 {
let base = back_pos.len() as u32;
back_pos.extend_from_slice(&bk_pts);
for i in 1..(bk_pts.len() as u32 - 1) {
back_tri.push([base, base + i, base + i + 1]);
}
}
}
}
MeshSplitResult {
front: SplitMesh {
positions: front_pos,
triangles: front_tri,
side: SplitSide::Front,
},
back: SplitMesh {
positions: back_pos,
triangles: back_tri,
side: SplitSide::Back,
},
cut_edge_count,
}
}
#[allow(dead_code)]
pub fn side_name(side: &SplitSide) -> &'static str {
match side {
SplitSide::Front => "front",
SplitSide::Back => "back",
SplitSide::On => "on",
}
}
#[allow(dead_code)]
pub fn split_mesh_vertex_count(m: &SplitMesh) -> usize {
m.positions.len()
}
#[allow(dead_code)]
pub fn split_mesh_face_count(m: &SplitMesh) -> usize {
m.triangles.len()
}
#[allow(dead_code)]
pub fn front_face_count(r: &MeshSplitResult) -> usize {
r.front.triangles.len()
}
#[allow(dead_code)]
pub fn back_face_count(r: &MeshSplitResult) -> usize {
r.back.triangles.len()
}
#[allow(dead_code)]
pub fn split_result_to_json(r: &MeshSplitResult) -> String {
format!(
"{{\"front_vertices\":{},\"front_faces\":{},\"back_vertices\":{},\"back_faces\":{},\"cut_edges\":{}}}",
r.front.positions.len(),
r.front.triangles.len(),
r.back.positions.len(),
r.back.triangles.len(),
r.cut_edge_count,
)
}
fn plane_edge_t(a: [f32; 3], b: [f32; 3], normal: [f32; 3], d: f32) -> f32 {
let da = dot_v3_split(a, normal) - d;
let db = dot_v3_split(b, normal) - d;
let denom = da - db;
if denom.abs() < 1e-9 {
0.5
} else {
da / denom
}
}
fn lerp_v3(a: [f32; 3], b: [f32; 3], t: f32) -> [f32; 3] {
[
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::*;
fn simple_quad_positions() -> Vec<[f32; 3]> {
vec![
[-1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 2.0, 0.0],
[-1.0, 2.0, 0.0],
]
}
fn simple_quad_triangles() -> Vec<[u32; 3]> {
vec![[0, 1, 2], [0, 2, 3]]
}
#[test]
fn classify_vertex_front() {
let s = classify_vertex([1.0, 0.0, 0.0], [1.0, 0.0, 0.0], 0.0);
assert_eq!(s, SplitSide::Front);
}
#[test]
fn classify_vertex_back() {
let s = classify_vertex([-1.0, 0.0, 0.0], [1.0, 0.0, 0.0], 0.0);
assert_eq!(s, SplitSide::Back);
}
#[test]
fn classify_vertex_on() {
let s = classify_vertex([0.0, 1.0, 0.0], [1.0, 0.0, 0.0], 0.0);
assert_eq!(s, SplitSide::On);
}
#[test]
fn split_all_front() {
let cfg = default_split_config([0.0, 1.0, 0.0], -10.0);
let pos = simple_quad_positions();
let tri = simple_quad_triangles();
let res = split_mesh_by_plane(&pos, &tri, &cfg);
assert_eq!(res.front.triangles.len(), 2);
assert_eq!(res.back.triangles.len(), 0);
assert_eq!(res.cut_edge_count, 0);
}
#[test]
fn split_all_back() {
let cfg = default_split_config([0.0, 1.0, 0.0], 10.0);
let pos = simple_quad_positions();
let tri = simple_quad_triangles();
let res = split_mesh_by_plane(&pos, &tri, &cfg);
assert_eq!(res.front.triangles.len(), 0);
assert_eq!(res.back.triangles.len(), 2);
assert_eq!(res.cut_edge_count, 0);
}
#[test]
fn split_through_mesh_cuts() {
let cfg = default_split_config([0.0, 1.0, 0.0], 1.0);
let pos = simple_quad_positions();
let tri = simple_quad_triangles();
let res = split_mesh_by_plane(&pos, &tri, &cfg);
assert!(res.cut_edge_count > 0);
assert!(!res.front.triangles.is_empty());
assert!(!res.back.triangles.is_empty());
}
#[test]
fn side_name_correct() {
assert_eq!(side_name(&SplitSide::Front), "front");
assert_eq!(side_name(&SplitSide::Back), "back");
assert_eq!(side_name(&SplitSide::On), "on");
}
#[test]
fn split_result_to_json_contains_fields() {
let cfg = default_split_config([0.0, 1.0, 0.0], -10.0);
let pos = simple_quad_positions();
let tri = simple_quad_triangles();
let res = split_mesh_by_plane(&pos, &tri, &cfg);
let json = split_result_to_json(&res);
assert!(json.contains("front_vertices"));
assert!(json.contains("cut_edges"));
}
#[test]
fn dot_v3_split_correct() {
assert!((dot_v3_split([1.0, 2.0, 3.0], [4.0, 5.0, 6.0]) - 32.0).abs() < 1e-6);
}
#[test]
fn vertex_count_and_face_count() {
let cfg = default_split_config([0.0, 1.0, 0.0], -10.0);
let pos = simple_quad_positions();
let tri = simple_quad_triangles();
let res = split_mesh_by_plane(&pos, &tri, &cfg);
assert_eq!(split_mesh_vertex_count(&res.front), res.front.positions.len());
assert_eq!(split_mesh_face_count(&res.front), res.front.triangles.len());
assert_eq!(front_face_count(&res), 2);
assert_eq!(back_face_count(&res), 0);
}
}