#![allow(dead_code)]
#[derive(Debug, Clone, Copy)]
pub struct OutlineConfig {
pub extrude_distance: f32,
pub only_silhouette: bool,
pub cull_backface: bool,
}
impl Default for OutlineConfig {
fn default() -> Self {
Self {
extrude_distance: 0.02,
only_silhouette: true,
cull_backface: true,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct OutlineEdge {
pub v0: u32,
pub v1: u32,
pub is_silhouette: bool,
}
#[derive(Debug, Default, Clone)]
pub struct OutlineMesh {
pub positions: Vec<[f32; 3]>,
pub normals: Vec<[f32; 3]>,
pub edges: Vec<OutlineEdge>,
pub config: OutlineConfig,
}
impl OutlineMesh {
pub fn new(config: OutlineConfig) -> Self {
Self {
config,
..Default::default()
}
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
pub fn silhouette_edges(&self) -> Vec<&OutlineEdge> {
self.edges.iter().filter(|e| e.is_silhouette).collect()
}
}
pub fn extrude_vertex(position: [f32; 3], normal: [f32; 3], distance: f32) -> [f32; 3] {
[
position[0] + normal[0] * distance,
position[1] + normal[1] * distance,
position[2] + normal[2] * distance,
]
}
pub fn is_silhouette_edge(n0: [f32; 3], n1: [f32; 3], view_dir: [f32; 3]) -> bool {
let dot0 = n0[0] * view_dir[0] + n0[1] * view_dir[1] + n0[2] * view_dir[2];
let dot1 = n1[0] * view_dir[0] + n1[1] * view_dir[1] + n1[2] * view_dir[2];
(dot0 >= 0.0) != (dot1 >= 0.0)
}
pub fn generate_outline_mesh(
positions: &[[f32; 3]],
normals: &[[f32; 3]],
config: OutlineConfig,
) -> OutlineMesh {
let n = positions.len().min(normals.len());
let extruded: Vec<[f32; 3]> = (0..n)
.map(|i| extrude_vertex(positions[i], normals[i], config.extrude_distance))
.collect();
let mut mesh = OutlineMesh::new(config);
mesh.positions = extruded;
mesh.normals = normals[..n].to_vec();
mesh
}
pub fn normalize(v: [f32; 3]) -> [f32; 3] {
let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
if len < f32::EPSILON {
return [0.0; 3];
}
[v[0] / len, v[1] / len, v[2] / len]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
assert!((OutlineConfig::default().extrude_distance - 0.02).abs() < f32::EPSILON);
}
#[test]
fn test_extrude_vertex_along_z() {
let pos = [0.0f32, 0.0, 0.0];
let norm = [0.0f32, 0.0, 1.0];
let result = extrude_vertex(pos, norm, 0.5);
assert!((result[2] - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_extrude_zero_distance() {
let pos = [1.0f32, 2.0, 3.0];
let norm = [0.0f32, 1.0, 0.0];
assert_eq!(extrude_vertex(pos, norm, 0.0), pos);
}
#[test]
fn test_is_silhouette_edge_true() {
let n0 = [0.0f32, 0.0, 1.0];
let n1 = [0.0f32, 0.0, -1.0];
let view = [0.0f32, 0.0, 1.0];
assert!(is_silhouette_edge(n0, n1, view));
}
#[test]
fn test_is_silhouette_edge_false() {
let n = [0.0f32, 0.0, 1.0];
let view = [0.0f32, 0.0, 1.0];
assert!(!is_silhouette_edge(n, n, view));
}
#[test]
fn test_generate_outline_mesh_vertex_count() {
let pos = vec![[0.0f32; 3]; 5];
let norm = vec![[0.0f32, 0.0, 1.0]; 5];
let mesh = generate_outline_mesh(&pos, &norm, OutlineConfig::default());
assert_eq!(mesh.positions.len(), 5);
}
#[test]
fn test_edge_count_empty() {
assert_eq!(OutlineMesh::new(OutlineConfig::default()).edge_count(), 0);
}
#[test]
fn test_normalize_unit_vector() {
let v = [1.0f32, 0.0, 0.0];
let n = normalize(v);
assert!((n[0] - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_normalize_zero_vector() {
let n = normalize([0.0; 3]);
assert_eq!(n, [0.0; 3]);
}
#[test]
fn test_silhouette_edges_filter() {
let mut mesh = OutlineMesh::new(OutlineConfig::default());
mesh.edges.push(OutlineEdge {
v0: 0,
v1: 1,
is_silhouette: true,
});
mesh.edges.push(OutlineEdge {
v0: 1,
v1: 2,
is_silhouette: false,
});
assert_eq!(mesh.silhouette_edges().len(), 1);
}
}