#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct FeatherParams {
pub shaft_length: f32,
pub max_barb_length: f32,
pub barb_count: usize,
pub taper: f32,
}
impl Default for FeatherParams {
fn default() -> Self {
Self {
shaft_length: 1.0,
max_barb_length: 0.4,
barb_count: 10,
taper: 0.8,
}
}
}
#[derive(Debug, Clone)]
pub struct Barb {
pub root: [f32; 3],
pub tip: [f32; 3],
}
impl Barb {
pub fn length(&self) -> f32 {
let dx = self.tip[0] - self.root[0];
let dy = self.tip[1] - self.root[1];
(dx * dx + dy * dy).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct Feather {
pub shaft_start: [f32; 3],
pub shaft_end: [f32; 3],
pub barbs_left: Vec<Barb>,
pub barbs_right: Vec<Barb>,
}
impl Feather {
pub fn total_barb_count(&self) -> usize {
self.barbs_left.len() + self.barbs_right.len()
}
pub fn average_barb_length(&self) -> f32 {
let all: Vec<&Barb> = self
.barbs_left
.iter()
.chain(self.barbs_right.iter())
.collect();
if all.is_empty() {
return 0.0;
}
let sum: f32 = all.iter().map(|b| b.length()).sum();
sum / all.len() as f32
}
}
pub fn generate_feather(params: &FeatherParams, origin: [f32; 3]) -> Feather {
let shaft_end = [origin[0], origin[1] + params.shaft_length, origin[2]];
let mut barbs_left = Vec::new();
let mut barbs_right = Vec::new();
let n = params.barb_count;
for i in 0..n {
let t = if n <= 1 {
0.5
} else {
i as f32 / (n - 1) as f32
};
let taper_factor = 1.0 - t * (1.0 - params.taper);
let barb_len = params.max_barb_length * taper_factor;
let y = origin[1] + params.shaft_length * t;
let root = [origin[0], y, origin[2]];
let tip_l = [origin[0] - barb_len, y, origin[2]];
let tip_r = [origin[0] + barb_len, y, origin[2]];
barbs_left.push(Barb { root, tip: tip_l });
barbs_right.push(Barb { root, tip: tip_r });
}
Feather {
shaft_start: origin,
shaft_end,
barbs_left,
barbs_right,
}
}
pub fn total_segment_count(feather: &Feather) -> usize {
1 + feather.total_barb_count()
}
#[cfg(test)]
mod tests {
use super::*;
fn default_feather() -> Feather {
generate_feather(&FeatherParams::default(), [0.0, 0.0, 0.0])
}
#[test]
fn test_total_barb_count() {
let f = default_feather();
assert_eq!(f.total_barb_count(), 20);
}
#[test]
fn test_average_barb_length_positive() {
let f = default_feather();
assert!(f.average_barb_length() > 0.0);
}
#[test]
fn test_total_segment_count() {
let f = default_feather();
assert_eq!(total_segment_count(&f), 21);
}
#[test]
fn test_shaft_length() {
let p = FeatherParams::default();
let f = generate_feather(&p, [0.0, 0.0, 0.0]);
let dy = f.shaft_end[1] - f.shaft_start[1];
assert!((dy - p.shaft_length).abs() < 1e-5);
}
#[test]
fn test_barbs_symmetric() {
let f = default_feather();
assert_eq!(f.barbs_left.len(), f.barbs_right.len());
}
#[test]
fn test_barb_length_positive() {
let f = default_feather();
for b in &f.barbs_left {
assert!(b.length() > 0.0);
}
}
#[test]
fn test_zero_barb_count() {
let p = FeatherParams {
barb_count: 0,
..Default::default()
};
let f = generate_feather(&p, [0.0, 0.0, 0.0]);
assert_eq!(f.total_barb_count(), 0);
}
#[test]
fn test_custom_origin() {
let p = FeatherParams::default();
let f = generate_feather(&p, [1.0, 2.0, 3.0]);
assert!((f.shaft_start[0] - 1.0).abs() < 1e-5);
assert!((f.shaft_start[1] - 2.0).abs() < 1e-5);
}
}