#![allow(dead_code)]
#[allow(dead_code)]
pub struct HairClump {
pub id: usize,
pub root: [f32; 3],
pub strand_count: usize,
pub clump_factor: f32,
}
#[allow(dead_code)]
#[derive(Default)]
pub struct HairClumpExport {
pub clumps: Vec<HairClump>,
}
#[allow(dead_code)]
pub fn new_hair_clump_export() -> HairClumpExport {
HairClumpExport::default()
}
#[allow(dead_code)]
pub fn add_hair_clump(
export: &mut HairClumpExport,
root: [f32; 3],
strand_count: usize,
clump_factor: f32,
) {
let id = export.clumps.len();
export.clumps.push(HairClump {
id,
root,
strand_count,
clump_factor,
});
}
#[allow(dead_code)]
pub fn hair_clump_count(export: &HairClumpExport) -> usize {
export.clumps.len()
}
#[allow(dead_code)]
pub fn total_strand_count_hc(export: &HairClumpExport) -> usize {
export.clumps.iter().map(|c| c.strand_count).sum()
}
#[allow(dead_code)]
pub fn avg_clump_factor(export: &HairClumpExport) -> f32 {
if export.clumps.is_empty() {
return 0.0;
}
export.clumps.iter().map(|c| c.clump_factor).sum::<f32>() / export.clumps.len() as f32
}
#[allow(dead_code)]
pub fn largest_clump(export: &HairClumpExport) -> Option<&HairClump> {
export.clumps.iter().max_by_key(|c| c.strand_count)
}
#[allow(dead_code)]
pub fn validate_clump_factors(export: &HairClumpExport) -> bool {
export
.clumps
.iter()
.all(|c| (0.0..=1.0).contains(&c.clump_factor))
}
#[allow(dead_code)]
pub fn hair_clump_to_json(export: &HairClumpExport) -> String {
format!(
r#"{{"clumps":{},"total_strands":{}}}"#,
export.clumps.len(),
total_strand_count_hc(export)
)
}
#[allow(dead_code)]
pub fn clump_roots_bounds(export: &HairClumpExport) -> ([f32; 3], [f32; 3]) {
if export.clumps.is_empty() {
return ([0.0; 3], [0.0; 3]);
}
let mut mn = export.clumps[0].root;
let mut mx = export.clumps[0].root;
for c in &export.clumps {
for i in 0..3 {
mn[i] = mn[i].min(c.root[i]);
mx[i] = mx[i].max(c.root[i]);
}
}
(mn, mx)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_and_count() {
let mut e = new_hair_clump_export();
add_hair_clump(&mut e, [0.0; 3], 10, 0.5);
assert_eq!(hair_clump_count(&e), 1);
}
#[test]
fn total_strands() {
let mut e = new_hair_clump_export();
add_hair_clump(&mut e, [0.0; 3], 10, 0.5);
add_hair_clump(&mut e, [1.0, 0.0, 0.0], 5, 0.5);
assert_eq!(total_strand_count_hc(&e), 15);
}
#[test]
fn avg_factor() {
let mut e = new_hair_clump_export();
add_hair_clump(&mut e, [0.0; 3], 5, 0.4);
add_hair_clump(&mut e, [0.0; 3], 5, 0.6);
assert!((avg_clump_factor(&e) - 0.5).abs() < 1e-5);
}
#[test]
fn largest_clump_found() {
let mut e = new_hair_clump_export();
add_hair_clump(&mut e, [0.0; 3], 5, 0.5);
add_hair_clump(&mut e, [0.0; 3], 20, 0.5);
assert_eq!(largest_clump(&e).expect("should succeed").strand_count, 20);
}
#[test]
fn validate_factors_valid() {
let mut e = new_hair_clump_export();
add_hair_clump(&mut e, [0.0; 3], 5, 0.5);
assert!(validate_clump_factors(&e));
}
#[test]
fn validate_factor_out_of_range() {
let mut e = new_hair_clump_export();
add_hair_clump(&mut e, [0.0; 3], 5, 1.5);
assert!(!validate_clump_factors(&e));
}
#[test]
fn json_has_clumps() {
let e = new_hair_clump_export();
let j = hair_clump_to_json(&e);
assert!(j.contains("\"clumps\":0"));
}
#[test]
fn bounds_single() {
let mut e = new_hair_clump_export();
add_hair_clump(&mut e, [1.0, 2.0, 3.0], 1, 0.5);
let (mn, mx) = clump_roots_bounds(&e);
assert_eq!(mn, mx);
}
#[test]
fn empty_avg_factor() {
let e = new_hair_clump_export();
assert!((avg_clump_factor(&e) - 0.0).abs() < 1e-6);
}
#[test]
fn largest_none_empty() {
let e = new_hair_clump_export();
assert!(largest_clump(&e).is_none());
}
}