#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct HairGuideCurve {
pub points: Vec<[f32; 3]>,
pub width: f32,
pub stiffness: f32,
}
impl HairGuideCurve {
pub fn straight(root: [f32; 3], tip: [f32; 3], segments: usize) -> Self {
let n = (segments + 1).max(2);
let points = (0..n)
.map(|i| {
let t = i as f32 / (n - 1) as f32;
[
root[0] + t * (tip[0] - root[0]),
root[1] + t * (tip[1] - root[1]),
root[2] + t * (tip[2] - root[2]),
]
})
.collect();
Self {
points,
width: 0.002,
stiffness: 0.8,
}
}
pub fn arc_length(&self) -> f32 {
self.points
.windows(2)
.map(|w| {
let d = [w[1][0] - w[0][0], w[1][1] - w[0][1], w[1][2] - w[0][2]];
(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt()
})
.sum()
}
pub fn segment_count(&self) -> usize {
self.points.len().saturating_sub(1)
}
}
#[derive(Debug, Clone, Default)]
pub struct HairGuideMesh {
pub guides: Vec<HairGuideCurve>,
}
impl HairGuideMesh {
pub fn add_guide(&mut self, g: HairGuideCurve) {
self.guides.push(g);
}
pub fn total_points(&self) -> usize {
self.guides.iter().map(|g| g.points.len()).sum()
}
}
pub fn uniform_guide_grid(
origin: [f32; 3],
rows: usize,
cols: usize,
spacing: f32,
length: f32,
segments: usize,
) -> HairGuideMesh {
let mut mesh = HairGuideMesh::default();
for r in 0..rows {
for c in 0..cols {
let root = [
origin[0] + c as f32 * spacing,
origin[1],
origin[2] + r as f32 * spacing,
];
let tip = [root[0], root[1] + length, root[2]];
mesh.add_guide(HairGuideCurve::straight(root, tip, segments));
}
}
mesh
}
pub fn validate_guides(mesh: &HairGuideMesh) -> bool {
mesh.guides.iter().all(|g| g.points.len() >= 2)
}
pub fn average_guide_length(mesh: &HairGuideMesh) -> f32 {
if mesh.guides.is_empty() {
return 0.0;
}
let total: f32 = mesh.guides.iter().map(|g| g.arc_length()).sum();
total / mesh.guides.len() as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn straight_guide_point_count() {
let g = HairGuideCurve::straight([0.0; 3], [0.0, 1.0, 0.0], 4);
assert_eq!(g.points.len(), 5);
}
#[test]
fn arc_length_correct() {
let g = HairGuideCurve::straight([0.0; 3], [0.0, 1.0, 0.0], 4);
assert!((g.arc_length() - 1.0).abs() < 1e-5);
}
#[test]
fn segment_count() {
let g = HairGuideCurve::straight([0.0; 3], [1.0, 0.0, 0.0], 4);
assert_eq!(g.segment_count(), 4);
}
#[test]
fn uniform_grid_guide_count() {
let m = uniform_guide_grid([0.0; 3], 2, 3, 0.1, 0.5, 4);
assert_eq!(m.guides.len(), 6);
}
#[test]
fn total_points_count() {
let m = uniform_guide_grid([0.0; 3], 2, 3, 0.1, 0.5, 4);
assert_eq!(m.total_points(), 30);
}
#[test]
fn validate_guides_ok() {
let m = uniform_guide_grid([0.0; 3], 2, 2, 0.1, 0.3, 2);
assert!(validate_guides(&m));
}
#[test]
fn validate_guides_fails_empty() {
let mut m = HairGuideMesh::default();
m.guides.push(HairGuideCurve {
points: vec![],
width: 0.002,
stiffness: 0.8,
});
assert!(!validate_guides(&m));
}
#[test]
fn average_length_empty_mesh() {
let m = HairGuideMesh::default();
assert_eq!(average_guide_length(&m), 0.0);
}
#[test]
fn average_length_nonzero() {
let m = uniform_guide_grid([0.0; 3], 2, 2, 0.1, 1.0, 4);
assert!(average_guide_length(&m) > 0.0);
}
#[test]
fn default_width() {
let g = HairGuideCurve::straight([0.0; 3], [1.0, 0.0, 0.0], 2);
assert!((g.width - 0.002).abs() < 1e-7);
}
}