oxihuman_morph/
proximity_wrap.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
9pub struct ProximityWrapConfig {
10 pub max_distance: f32,
12 pub falloff: f32,
14}
15
16impl Default for ProximityWrapConfig {
17 fn default() -> Self {
18 ProximityWrapConfig {
19 max_distance: 0.5,
20 falloff: 2.0,
21 }
22 }
23}
24
25#[derive(Debug, Clone)]
27pub struct ProximityWrap {
28 pub config: ProximityWrapConfig,
29 pub weights: Vec<f32>,
31}
32
33impl ProximityWrap {
34 pub fn new(vertex_count: usize) -> Self {
35 ProximityWrap {
36 config: ProximityWrapConfig::default(),
37 weights: vec![0.0; vertex_count],
38 }
39 }
40}
41
42pub fn new_proximity_wrap(vertex_count: usize) -> ProximityWrap {
44 ProximityWrap::new(vertex_count)
45}
46
47pub fn proximity_influence(distance: f32, config: &ProximityWrapConfig) -> f32 {
49 if distance >= config.max_distance {
50 return 0.0;
51 }
52 let t = 1.0 - distance / config.max_distance;
53 t.powf(config.falloff)
54}
55
56#[allow(clippy::needless_range_loop)]
58pub fn bake_proximity_weights(wrap: &mut ProximityWrap, distances: &[f32]) {
59 let n = wrap.weights.len().min(distances.len());
60 for i in 0..n {
61 wrap.weights[i] = proximity_influence(distances[i], &wrap.config);
62 }
63}
64
65pub fn proximity_vertex_count(wrap: &ProximityWrap) -> usize {
67 wrap.weights.len()
68}
69
70pub fn proximity_wrap_to_json(wrap: &ProximityWrap) -> String {
72 format!(
73 r#"{{"max_distance":{:.4},"falloff":{:.4},"vertices":{}}}"#,
74 wrap.config.max_distance,
75 wrap.config.falloff,
76 wrap.weights.len()
77 )
78}
79
80pub fn proximity_average_weight(wrap: &ProximityWrap) -> f32 {
82 if wrap.weights.is_empty() {
83 return 0.0;
84 }
85 let sum: f32 = wrap.weights.iter().sum();
86 sum / wrap.weights.len() as f32
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn test_new_wrap_correct_vertex_count() {
95 let w = new_proximity_wrap(10);
96 assert_eq!(
97 proximity_vertex_count(&w),
98 10, );
100 }
101
102 #[test]
103 fn test_influence_at_zero_distance_is_one() {
104 let cfg = ProximityWrapConfig::default();
105 let inf = proximity_influence(0.0, &cfg);
106 assert!((inf - 1.0).abs() < 1e-5, );
107 }
108
109 #[test]
110 fn test_influence_at_max_distance_is_zero() {
111 let cfg = ProximityWrapConfig::default();
112 let inf = proximity_influence(cfg.max_distance, &cfg);
113 assert!((inf).abs() < 1e-5, );
114 }
115
116 #[test]
117 fn test_influence_beyond_max_is_zero() {
118 let cfg = ProximityWrapConfig::default();
119 let inf = proximity_influence(cfg.max_distance + 1.0, &cfg);
120 assert!((inf).abs() < 1e-5, );
121 }
122
123 #[test]
124 fn test_bake_weights_fills_correctly() {
125 let mut w = new_proximity_wrap(3);
126 bake_proximity_weights(&mut w, &[0.0, 0.25, 1.0]);
127 assert!(w.weights[0] > 0.0, );
128 assert!((w.weights[2]).abs() < 1e-5, );
129 }
130
131 #[test]
132 fn test_average_weight_empty() {
133 let w = new_proximity_wrap(0);
134 assert!((proximity_average_weight(&w)).abs() < 1e-6, );
135 }
136
137 #[test]
138 fn test_average_weight_all_zero() {
139 let w = new_proximity_wrap(5);
140 assert!((proximity_average_weight(&w)).abs() < 1e-6, );
141 }
142
143 #[test]
144 fn test_to_json_contains_max_distance() {
145 let w = new_proximity_wrap(4);
146 let j = proximity_wrap_to_json(&w);
147 assert!(j.contains("max_distance"), );
148 }
149
150 #[test]
151 fn test_falloff_default_is_two() {
152 let w = new_proximity_wrap(1);
153 assert!((w.config.falloff - 2.0).abs() < 1e-5, );
154 }
155
156 #[test]
157 fn test_weights_initialized_zero() {
158 let w = new_proximity_wrap(8);
159 for &wt in &w.weights {
160 assert!((wt).abs() < 1e-6 ,);
161 }
162 }
163}