use chromacat::pattern::params::PatternParam;
use chromacat::pattern::patterns::{FireParams, Patterns};
#[test]
fn test_fire_params_validation() {
let params = FireParams::default();
assert!(params
.validate("intensity=1.0,speed=2.0,turbulence=0.5,height=1.0,wind=true,wind_strength=0.3")
.is_ok());
assert!(params.validate("intensity=0.05,speed=1.0").is_err());
assert!(params.validate("intensity=2.1,speed=1.0").is_err());
assert!(params.validate("intensity=1.0,speed=0.05").is_err());
assert!(params.validate("intensity=1.0,speed=5.1").is_err());
assert!(params.validate("turbulence=-0.1").is_err());
assert!(params.validate("turbulence=1.1").is_err());
assert!(params.validate("height=0.05").is_err());
assert!(params.validate("height=2.1").is_err());
assert!(params.validate("wind_strength=-0.1").is_err());
assert!(params.validate("wind_strength=1.1").is_err());
assert!(params.validate("intensity=1.0,invalid").is_err());
}
#[test]
fn test_fire_params_parsing() {
let params = FireParams::default();
let parsed = params
.parse("intensity=1.5,speed=2.0,turbulence=0.7,height=1.2,wind=false,wind_strength=0.5")
.unwrap();
let fire_params = parsed
.as_any()
.downcast_ref::<FireParams>()
.expect("Failed to downcast parsed parameters");
assert_eq!(fire_params.intensity, 1.5);
assert_eq!(fire_params.speed, 2.0);
assert_eq!(fire_params.turbulence, 0.7);
assert_eq!(fire_params.height, 1.2);
assert_eq!(fire_params.wind, false);
assert_eq!(fire_params.wind_strength, 0.5);
}
#[test]
fn test_fire_params_defaults() {
let params = FireParams::default();
assert_eq!(params.intensity, 1.0);
assert_eq!(params.speed, 1.0);
assert_eq!(params.turbulence, 0.5);
assert_eq!(params.height, 1.0);
assert!(params.wind);
assert_eq!(params.wind_strength, 0.3);
}
#[test]
fn test_fire_params_bounds() {
let params = FireParams::default();
assert!(params.validate("intensity=0.1").is_ok());
assert!(params.validate("intensity=2.0").is_ok());
assert!(params.validate("intensity=0.09").is_err());
assert!(params.validate("intensity=2.1").is_err());
assert!(params.validate("speed=0.1").is_ok());
assert!(params.validate("speed=5.0").is_ok());
assert!(params.validate("speed=0.09").is_err());
assert!(params.validate("speed=5.1").is_err());
assert!(params.validate("turbulence=0.0").is_ok());
assert!(params.validate("turbulence=1.0").is_ok());
assert!(params.validate("turbulence=-0.1").is_err());
assert!(params.validate("turbulence=1.1").is_err());
assert!(params.validate("height=0.1").is_ok());
assert!(params.validate("height=2.0").is_ok());
assert!(params.validate("height=0.09").is_err());
assert!(params.validate("height=2.1").is_err());
assert!(params.validate("wind_strength=0.0").is_ok());
assert!(params.validate("wind_strength=1.0").is_ok());
assert!(params.validate("wind_strength=-0.1").is_err());
assert!(params.validate("wind_strength=1.1").is_err());
}
#[test]
fn test_fire_animation_behavior() {
let mut patterns = Patterns::new(100, 100, 0.0, 0);
let params = FireParams::default();
let samples = [
(0.0, 0.9, "bottom"), (0.0, 0.5, "middle"), (0.0, 0.1, "top"), ];
for (x, y, position) in samples.iter() {
let value = patterns.fire(*x, *y, params.clone());
assert!(
(0.0..=1.0).contains(&value),
"Fire intensity at {} should be between 0 and 1, got {}",
position,
value
);
}
let params_with_wind = FireParams {
wind: true,
wind_strength: 1.0,
turbulence: 0.8,
speed: 2.0,
..params.clone()
};
let params_no_wind = FireParams {
wind: false,
wind_strength: 0.0,
turbulence: 0.0,
speed: 2.0,
..params.clone()
};
let x_samples = [-0.4, -0.3, -0.2, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4];
let y_test = 0.5;
let mut wind_total_var = 0.0;
let mut no_wind_total_var = 0.0;
let sample_count = 5;
for i in 0..sample_count {
patterns = Patterns::new(100, 100, i as f64 * 0.2, 0);
let wind_samples: Vec<f64> = x_samples
.iter()
.map(|x| patterns.fire(*x, y_test, params_with_wind.clone()))
.collect();
let no_wind_samples: Vec<f64> = x_samples
.iter()
.map(|x| patterns.fire(*x, y_test, params_no_wind.clone()))
.collect();
wind_total_var += variance(&wind_samples);
no_wind_total_var += variance(&no_wind_samples);
}
let wind_var = wind_total_var / sample_count as f64;
let no_wind_var = no_wind_total_var / sample_count as f64;
assert!(
wind_var > no_wind_var,
"Wind should create more horizontal variation. With wind: {}, Without wind: {}",
wind_var,
no_wind_var
);
let params_temporal = FireParams {
turbulence: 1.0,
speed: 5.0,
..params
};
let mut initial_samples = Vec::new();
let mut later_samples = Vec::new();
let test_points = [(-0.3, 0.3), (-0.1, 0.4), (0.0, 0.5), (0.1, 0.4), (0.3, 0.3)];
patterns = Patterns::new(100, 100, 0.0, 0);
for (x, y) in &test_points {
initial_samples.push(patterns.fire(*x, *y, params_temporal.clone()));
}
patterns = Patterns::new(100, 100, 5.0, 0);
for (x, y) in &test_points {
later_samples.push(patterns.fire(*x, *y, params_temporal.clone()));
}
let avg_diff: f64 = initial_samples
.iter()
.zip(later_samples.iter())
.map(|(a, b)| (a - b).abs())
.sum::<f64>()
/ test_points.len() as f64;
assert!(
avg_diff > 0.01,
"Fire should show temporal variation. Average difference: {}, Initial samples: {:?}, Later samples: {:?}",
avg_diff,
initial_samples,
later_samples
);
}
fn variance(values: &[f64]) -> f64 {
let mean = values.iter().sum::<f64>() / values.len() as f64;
values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64
}