use std::collections::HashMap;
use crate::data::{CellArray, Points, PolyData};
pub fn extract_silhouette(input: &PolyData, viewpoint: [f64; 3]) -> PolyData {
let num_faces: usize = input.polys.num_cells();
if num_faces == 0 {
return PolyData::default();
}
let mut face_facing: Vec<bool> = Vec::with_capacity(num_faces);
for cell in input.polys.iter() {
if cell.len() < 3 {
face_facing.push(false);
continue;
}
let p0 = input.points.get(cell[0] as usize);
let p1 = input.points.get(cell[1] as usize);
let p2 = input.points.get(cell[2] as usize);
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]];
let nx: f64 = e1[1] * e2[2] - e1[2] * e2[1];
let ny: f64 = e1[2] * e2[0] - e1[0] * e2[2];
let nz: f64 = e1[0] * e2[1] - e1[1] * e2[0];
let cx: f64 = (p0[0] + p1[0] + p2[0]) / 3.0;
let cy: f64 = (p0[1] + p1[1] + p2[1]) / 3.0;
let cz: f64 = (p0[2] + p1[2] + p2[2]) / 3.0;
let vx: f64 = viewpoint[0] - cx;
let vy: f64 = viewpoint[1] - cy;
let vz: f64 = viewpoint[2] - cz;
let dot: f64 = nx * vx + ny * vy + nz * vz;
face_facing.push(dot > 0.0);
}
let mut edge_faces: HashMap<(i64, i64), Vec<usize>> = HashMap::new();
for (fi, cell) in input.polys.iter().enumerate() {
let n: usize = cell.len();
for k in 0..n {
let a: i64 = cell[k];
let b: i64 = cell[(k + 1) % n];
let key = if a < b { (a, b) } else { (b, a) };
edge_faces.entry(key).or_default().push(fi);
}
}
let mut out_points: Points<f64> = Points::new();
let mut out_lines: CellArray = CellArray::new();
let mut point_map: HashMap<i64, i64> = HashMap::new();
let mut get_or_insert = |vid: i64, pts: &mut Points<f64>, map: &mut HashMap<i64, i64>| -> i64 {
if let Some(&id) = map.get(&vid) {
return id;
}
let id: i64 = pts.len() as i64;
pts.push(input.points.get(vid as usize));
map.insert(vid, id);
id
};
for (&(a, b), faces) in &edge_faces {
let is_silhouette: bool = if faces.len() == 1 {
face_facing[faces[0]]
} else if faces.len() == 2 {
face_facing[faces[0]] != face_facing[faces[1]]
} else {
false
};
if is_silhouette {
let id_a: i64 = get_or_insert(a, &mut out_points, &mut point_map);
let id_b: i64 = get_or_insert(b, &mut out_points, &mut point_map);
out_lines.push_cell(&[id_a, id_b]);
}
}
let mut pd = PolyData::new();
pd.points = out_points;
pd.lines = out_lines;
pd
}
#[cfg(test)]
mod tests {
use super::*;
fn make_double_triangle() -> PolyData {
let mut points: Points<f64> = Points::new();
points.push([0.0, 0.0, 0.0]);
points.push([1.0, 0.0, 0.0]);
points.push([0.5, 1.0, 0.0]);
points.push([0.5, 0.5, -1.0]);
let mut polys = CellArray::new();
polys.push_cell(&[0, 1, 2]);
polys.push_cell(&[1, 2, 3]);
let mut pd = PolyData::new();
pd.points = points;
pd.polys = polys;
pd
}
#[test]
fn silhouette_from_above() {
let mesh = make_double_triangle();
let result = extract_silhouette(&mesh, [0.5, 0.5, 10.0]);
assert!(result.lines.num_cells() > 0, "Expected silhouette edges");
}
#[test]
fn single_triangle_boundary() {
let mut points: Points<f64> = Points::new();
points.push([0.0, 0.0, 0.0]);
points.push([1.0, 0.0, 0.0]);
points.push([0.0, 1.0, 0.0]);
let mut polys = CellArray::new();
polys.push_cell(&[0, 1, 2]);
let mut mesh = PolyData::new();
mesh.points = points;
mesh.polys = polys;
let result = extract_silhouette(&mesh, [0.0, 0.0, 10.0]);
assert_eq!(result.lines.num_cells(), 3, "3 boundary edges");
}
#[test]
fn empty_mesh() {
let mesh = PolyData::default();
let result = extract_silhouette(&mesh, [0.0, 0.0, 1.0]);
assert_eq!(result.lines.num_cells(), 0);
}
}