use crate::define_param;
use crate::pattern::params::{ParamType, PatternParam};
use std::any::Any;
define_param!(num Fire, IntensityParam, "intensity", "Controls the brightness and strength of the flames", 0.1, 2.0, 1.0);
define_param!(num Fire, SpeedParam, "speed", "Controls the animation speed of the flames", 0.1, 5.0, 1.0);
define_param!(num Fire, TurbulenceParam, "turbulence", "Controls the amount of flame distortion and detail", 0.0, 1.0, 0.5);
define_param!(num Fire, HeightParam, "height", "Controls the maximum height of the flames", 0.1, 2.0, 1.0);
define_param!(bool Fire, WindParam, "wind", "Enables horizontal wind effect on flames", true);
define_param!(num Fire, WindStrengthParam, "wind_strength", "Controls the intensity of the wind effect", 0.0, 1.0, 0.3);
#[derive(Debug, Clone)]
pub struct FireParams {
pub intensity: f64,
pub speed: f64,
pub turbulence: f64,
pub height: f64,
pub wind: bool,
pub wind_strength: f64,
}
impl FireParams {
const INTENSITY_PARAM: FireIntensityParam = FireIntensityParam;
const SPEED_PARAM: FireSpeedParam = FireSpeedParam;
const TURBULENCE_PARAM: FireTurbulenceParam = FireTurbulenceParam;
const HEIGHT_PARAM: FireHeightParam = FireHeightParam;
const WIND_PARAM: FireWindParam = FireWindParam;
const WIND_STRENGTH_PARAM: FireWindStrengthParam = FireWindStrengthParam;
}
impl Default for FireParams {
fn default() -> Self {
Self {
intensity: 1.0,
speed: 1.0,
turbulence: 0.5,
height: 1.0,
wind: true,
wind_strength: 0.3,
}
}
}
define_param!(validate FireParams,
INTENSITY_PARAM: FireIntensityParam,
SPEED_PARAM: FireSpeedParam,
TURBULENCE_PARAM: FireTurbulenceParam,
HEIGHT_PARAM: FireHeightParam,
WIND_PARAM: FireWindParam,
WIND_STRENGTH_PARAM: FireWindStrengthParam
);
impl PatternParam for FireParams {
fn name(&self) -> &'static str {
"fire"
}
fn description(&self) -> &'static str {
"Dynamic fire effect with realistic flame movement"
}
fn param_type(&self) -> ParamType {
ParamType::Composite
}
fn default_value(&self) -> String {
format!(
"intensity={},speed={},turbulence={},height={},wind={},wind_strength={}",
self.intensity, self.speed, self.turbulence, self.height, self.wind, self.wind_strength
)
}
fn validate(&self, value: &str) -> Result<(), String> {
self.validate_params(value)
}
fn parse(&self, value: &str) -> Result<Box<dyn PatternParam>, String> {
let mut params = FireParams::default();
for part in value.split(',') {
let kv: Vec<&str> = part.split('=').collect();
if kv.len() != 2 {
continue;
}
match kv[0] {
"intensity" => {
Self::INTENSITY_PARAM.validate(kv[1])?;
params.intensity = kv[1].parse().unwrap();
}
"speed" => {
Self::SPEED_PARAM.validate(kv[1])?;
params.speed = kv[1].parse().unwrap();
}
"turbulence" => {
Self::TURBULENCE_PARAM.validate(kv[1])?;
params.turbulence = kv[1].parse().unwrap();
}
"height" => {
Self::HEIGHT_PARAM.validate(kv[1])?;
params.height = kv[1].parse().unwrap();
}
"wind" => {
Self::WIND_PARAM.validate(kv[1])?;
params.wind = kv[1].parse().unwrap();
}
"wind_strength" => {
Self::WIND_STRENGTH_PARAM.validate(kv[1])?;
params.wind_strength = kv[1].parse().unwrap();
}
invalid_param => {
return Err(format!("Invalid parameter name: {}", invalid_param));
}
}
}
Ok(Box::new(params))
}
fn sub_params(&self) -> Vec<Box<dyn PatternParam>> {
vec![
Box::new(Self::INTENSITY_PARAM),
Box::new(Self::SPEED_PARAM),
Box::new(Self::TURBULENCE_PARAM),
Box::new(Self::HEIGHT_PARAM),
Box::new(Self::WIND_PARAM),
Box::new(Self::WIND_STRENGTH_PARAM),
]
}
fn clone_param(&self) -> Box<dyn PatternParam> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl super::Patterns {
#[inline(always)]
pub fn fire(&self, x_norm: f64, y_norm: f64, params: FireParams) -> f64 {
let time = self.time * params.speed;
let x_pos = x_norm + 0.5;
let y_pos = if self.time == 0.0 {
(y_norm + 0.5).rem_euclid(0.3) * 3.0
} else {
1.0 - (y_norm + 0.5)
};
if y_pos > params.height {
return 0.0;
}
let base_intensity = (1.0 - y_pos / params.height).powf(0.35);
let turbulence = {
let noise1 = self
.utils
.noise2d(x_pos * 6.0 + time * 2.0, y_pos * 6.0 + time * 3.0);
let noise2 = self
.utils
.noise2d(x_pos * 12.0 - time * 1.5, y_pos * 12.0 + time * 2.5);
let noise3 = self
.utils
.noise2d(x_pos * 18.0 + time * 3.0, y_pos * 15.0 - time * 4.0);
let combined = noise1 * 0.5 + noise2 * 0.3 + noise3 * 0.2;
combined * params.turbulence * (1.0 + y_pos * 0.5)
};
let mut intensity = base_intensity * (1.0 + turbulence);
intensity = intensity.powf(0.8);
if y_pos < 0.3 {
let spot_noise = self
.utils
.noise2d(x_pos * 15.0 + time * 4.0, y_pos * 10.0 - time * 3.0);
if spot_noise > 0.5 {
intensity = intensity.max(0.85);
}
}
intensity = match intensity {
i if i < 0.2 => i,
i if i < 0.4 => 0.2 + (i - 0.2) * 2.0,
i if i < 0.6 => 0.4 + (i - 0.4) * 2.0,
i if i < 0.8 => 0.6 + (i - 0.6) * 2.0,
i => 0.8 + (i - 0.8) * 2.0,
};
if params.wind {
let wind_time = time * 1.5;
let wind_offset = self.utils.noise2d(x_pos + wind_time, y_pos * 2.5)
* params.wind_strength
* y_pos.powf(0.8);
let x_sample = (x_pos + wind_offset).rem_euclid(1.0);
intensity = self.fire(
x_sample - 0.5,
y_norm,
FireParams {
wind: false,
..params
},
);
}
intensity.clamp(0.0, 1.0)
}
}