#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FaceVisibility {
FrontFacing,
BackFacing,
Grazing,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct VisibilityResult {
pub face_vis: Vec<FaceVisibility>,
pub front_count: usize,
pub back_count: usize,
}
#[allow(dead_code)]
pub fn face_normal_vis(p0: [f32; 3], p1: [f32; 3], p2: [f32; 3]) -> [f32; 3] {
let e1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
let e2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
[
e1[1] * e2[2] - e1[2] * e2[1],
e1[2] * e2[0] - e1[0] * e2[2],
e1[0] * e2[1] - e1[1] * e2[0],
]
}
#[allow(dead_code)]
pub fn dot3_vis(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[allow(dead_code)]
pub fn classify_visibility(
positions: &[[f32; 3]],
indices: &[u32],
view_dir: [f32; 3],
grazing_threshold: f32,
) -> VisibilityResult {
let tri_count = indices.len() / 3;
let mut face_vis = Vec::with_capacity(tri_count);
let mut front_count = 0usize;
let mut back_count = 0usize;
for t in 0..tri_count {
let i0 = indices[t * 3] as usize;
let i1 = indices[t * 3 + 1] as usize;
let i2 = indices[t * 3 + 2] as usize;
if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
face_vis.push(FaceVisibility::BackFacing);
back_count += 1;
continue;
}
let n = face_normal_vis(positions[i0], positions[i1], positions[i2]);
let dot = dot3_vis(n, view_dir);
let vis = if dot.abs() <= grazing_threshold {
FaceVisibility::Grazing
} else if dot > 0.0 {
FaceVisibility::FrontFacing
} else {
FaceVisibility::BackFacing
};
match vis {
FaceVisibility::FrontFacing => front_count += 1,
FaceVisibility::BackFacing => back_count += 1,
FaceVisibility::Grazing => {}
}
face_vis.push(vis);
}
VisibilityResult {
face_vis,
front_count,
back_count,
}
}
#[allow(dead_code)]
pub fn front_facing_ratio(result: &VisibilityResult) -> f32 {
let total = result.face_vis.len();
if total == 0 {
return 0.0;
}
result.front_count as f32 / total as f32
}
#[allow(dead_code)]
pub fn grazing_angle_deg(threshold: f32) -> f32 {
threshold.clamp(-1.0, 1.0).acos() * 180.0 / PI
}
#[allow(dead_code)]
pub fn visibility_to_json(result: &VisibilityResult) -> String {
format!(
"{{\"front\":{},\"back\":{},\"grazing\":{}}}",
result.front_count,
result.back_count,
result.face_vis.len() - result.front_count - result.back_count
)
}
#[cfg(test)]
mod tests {
use super::*;
fn quad_positions() -> Vec<[f32; 3]> {
vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
]
}
#[test]
fn test_face_normal_z_up() {
let n = face_normal_vis([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!(n[2] > 0.0);
}
#[test]
fn test_dot3_vis() {
let d = dot3_vis([1.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
assert!((d - 1.0).abs() < 1e-6);
}
#[test]
fn test_classify_front_facing() {
let pos = quad_positions();
let idx = vec![0u32, 1, 2, 0, 2, 3];
let view = [0.0, 0.0, 1.0];
let r = classify_visibility(&pos, &idx, view, 0.01);
assert!(r.front_count > 0);
}
#[test]
fn test_classify_back_facing() {
let pos = quad_positions();
let idx = vec![0u32, 1, 2, 0, 2, 3];
let view = [0.0, 0.0, -1.0];
let r = classify_visibility(&pos, &idx, view, 0.01);
assert!(r.back_count > 0);
}
#[test]
fn test_front_facing_ratio_full() {
let r = VisibilityResult {
face_vis: vec![FaceVisibility::FrontFacing; 4],
front_count: 4,
back_count: 0,
};
assert!((front_facing_ratio(&r) - 1.0).abs() < 1e-6);
}
#[test]
fn test_front_facing_ratio_empty() {
let r = VisibilityResult {
face_vis: vec![],
front_count: 0,
back_count: 0,
};
assert!((front_facing_ratio(&r) - 0.0).abs() < 1e-6);
}
#[test]
fn test_grazing_angle_deg_zero() {
let ang = grazing_angle_deg(1.0);
assert!(ang.abs() < 1e-4);
}
#[test]
fn test_grazing_angle_deg_ninety() {
let ang = grazing_angle_deg(0.0);
assert!((ang - 90.0).abs() < 1e-3);
}
#[test]
fn test_visibility_to_json() {
let r = VisibilityResult {
face_vis: vec![FaceVisibility::FrontFacing],
front_count: 1,
back_count: 0,
};
let j = visibility_to_json(&r);
assert!(j.contains("\"front\":1"));
}
#[test]
fn test_empty_mesh() {
let r = classify_visibility(&[], &[], [0.0, 0.0, 1.0], 0.01);
assert_eq!(r.face_vis.len(), 0);
}
}