#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ProximityWrapConfig {
pub max_distance: f32,
pub falloff: f32,
}
impl Default for ProximityWrapConfig {
fn default() -> Self {
ProximityWrapConfig {
max_distance: 0.5,
falloff: 2.0,
}
}
}
#[derive(Debug, Clone)]
pub struct ProximityWrap {
pub config: ProximityWrapConfig,
pub weights: Vec<f32>,
}
impl ProximityWrap {
pub fn new(vertex_count: usize) -> Self {
ProximityWrap {
config: ProximityWrapConfig::default(),
weights: vec![0.0; vertex_count],
}
}
}
pub fn new_proximity_wrap(vertex_count: usize) -> ProximityWrap {
ProximityWrap::new(vertex_count)
}
pub fn proximity_influence(distance: f32, config: &ProximityWrapConfig) -> f32 {
if distance >= config.max_distance {
return 0.0;
}
let t = 1.0 - distance / config.max_distance;
t.powf(config.falloff)
}
#[allow(clippy::needless_range_loop)]
pub fn bake_proximity_weights(wrap: &mut ProximityWrap, distances: &[f32]) {
let n = wrap.weights.len().min(distances.len());
for i in 0..n {
wrap.weights[i] = proximity_influence(distances[i], &wrap.config);
}
}
pub fn proximity_vertex_count(wrap: &ProximityWrap) -> usize {
wrap.weights.len()
}
pub fn proximity_wrap_to_json(wrap: &ProximityWrap) -> String {
format!(
r#"{{"max_distance":{:.4},"falloff":{:.4},"vertices":{}}}"#,
wrap.config.max_distance,
wrap.config.falloff,
wrap.weights.len()
)
}
pub fn proximity_average_weight(wrap: &ProximityWrap) -> f32 {
if wrap.weights.is_empty() {
return 0.0;
}
let sum: f32 = wrap.weights.iter().sum();
sum / wrap.weights.len() as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_wrap_correct_vertex_count() {
let w = new_proximity_wrap(10);
assert_eq!(
proximity_vertex_count(&w),
10,
);
}
#[test]
fn test_influence_at_zero_distance_is_one() {
let cfg = ProximityWrapConfig::default();
let inf = proximity_influence(0.0, &cfg);
assert!((inf - 1.0).abs() < 1e-5, );
}
#[test]
fn test_influence_at_max_distance_is_zero() {
let cfg = ProximityWrapConfig::default();
let inf = proximity_influence(cfg.max_distance, &cfg);
assert!((inf).abs() < 1e-5, );
}
#[test]
fn test_influence_beyond_max_is_zero() {
let cfg = ProximityWrapConfig::default();
let inf = proximity_influence(cfg.max_distance + 1.0, &cfg);
assert!((inf).abs() < 1e-5, );
}
#[test]
fn test_bake_weights_fills_correctly() {
let mut w = new_proximity_wrap(3);
bake_proximity_weights(&mut w, &[0.0, 0.25, 1.0]);
assert!(w.weights[0] > 0.0, );
assert!((w.weights[2]).abs() < 1e-5, );
}
#[test]
fn test_average_weight_empty() {
let w = new_proximity_wrap(0);
assert!((proximity_average_weight(&w)).abs() < 1e-6, );
}
#[test]
fn test_average_weight_all_zero() {
let w = new_proximity_wrap(5);
assert!((proximity_average_weight(&w)).abs() < 1e-6, );
}
#[test]
fn test_to_json_contains_max_distance() {
let w = new_proximity_wrap(4);
let j = proximity_wrap_to_json(&w);
assert!(j.contains("max_distance"), );
}
#[test]
fn test_falloff_default_is_two() {
let w = new_proximity_wrap(1);
assert!((w.config.falloff - 2.0).abs() < 1e-5, );
}
#[test]
fn test_weights_initialized_zero() {
let w = new_proximity_wrap(8);
for &wt in &w.weights {
assert!((wt).abs() < 1e-6 ,);
}
}
}