use bevy::prelude::*;
use crate::model::{
CALM_FLICKER_DEPTH, FLICKER_DEPTH, WHIPPING_FLICKER_DEPTH, fire_lumens, flame_light_offset, flicker_rate,
};
use crate::{FlameEmbers, FlameSite, Flicker, MAX_EMBER_RATE, PoolCandidate};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Reflect)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FireKind {
Candle,
Lantern,
Torch,
Brazier,
Campfire,
Bonfire,
}
impl FireKind {
pub const ALL: [FireKind; 6] = [
FireKind::Candle,
FireKind::Lantern,
FireKind::Torch,
FireKind::Brazier,
FireKind::Campfire,
FireKind::Bonfire,
];
pub const fn diameter(self) -> f32 {
match self {
FireKind::Candle => 0.013,
FireKind::Lantern => 0.04,
FireKind::Torch => 0.10,
FireKind::Brazier => 0.34,
FireKind::Campfire => 0.8,
FireKind::Bonfire => 3.0,
}
}
pub const fn depth(self) -> f32 {
match self {
FireKind::Candle => 0.10,
FireKind::Lantern => CALM_FLICKER_DEPTH,
FireKind::Torch => WHIPPING_FLICKER_DEPTH,
FireKind::Brazier => FLICKER_DEPTH,
FireKind::Campfire => 0.20,
FireKind::Bonfire => 0.25,
}
}
pub const fn height(self) -> f32 {
match self {
FireKind::Candle => 0.04,
FireKind::Lantern => 0.12,
FireKind::Torch => 0.35,
FireKind::Brazier => 1.1,
FireKind::Campfire => 0.7,
FireKind::Bonfire => 2.0,
}
}
pub const fn reach(self) -> f32 {
match self {
FireKind::Candle => 1.0,
FireKind::Lantern => 1.5,
FireKind::Torch => 2.0,
FireKind::Brazier => 4.0,
FireKind::Campfire => 5.0,
FireKind::Bonfire => 9.0,
}
}
pub fn lumens(self, lux: f32, over: f32) -> f32 {
fire_lumens(lux, self.reach(), over)
}
pub fn light(self, lux: f32, over: f32) -> Flicker {
self.flicker(self.lumens(lux, over))
}
pub fn candidate(self, lux: f32, over: f32) -> PoolCandidate {
PoolCandidate {
offset: Vec3::from(flame_light_offset(self.height())),
diameter: Some(self.diameter()),
..PoolCandidate::at_mean(self.lumens(lux, over), self.depth())
}
}
pub fn rate(self) -> f32 {
flicker_rate(self.diameter())
}
pub fn embers(self) -> Option<FlameEmbers> {
let rate = match self {
FireKind::Candle | FireKind::Lantern => return None,
FireKind::Torch => 2.0,
FireKind::Brazier => return Some(FlameEmbers::default()),
FireKind::Campfire => 5.0,
FireKind::Bonfire => MAX_EMBER_RATE,
};
Some(FlameEmbers { rate })
}
pub fn fire(self, lux: f32, over: f32) -> (FlameSite, PoolCandidate) {
(self.site(), self.candidate(lux, over))
}
pub const fn name(self) -> &'static str {
match self {
FireKind::Candle => "Candle",
FireKind::Lantern => "Lantern",
FireKind::Torch => "Torch",
FireKind::Brazier => "Brazier",
FireKind::Campfire => "Campfire",
FireKind::Bonfire => "Bonfire",
}
}
pub fn flicker(self, mean: f32) -> Flicker {
Flicker { diameter: Some(self.diameter()), ..Flicker::at_mean(mean, self.depth()) }
}
pub fn site(self) -> FlameSite {
FlameSite {
height: self.height(),
phase: 0.0,
kindle: 0.0,
diameter: Some(self.diameter()),
depth: self.depth(),
depth_fade: false,
fire: None,
}
}
}
impl core::fmt::Display for FireKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnknownFireKind(pub String);
impl core::fmt::Display for UnknownFireKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "unknown fire kind {:?}; expected one of ", self.0)?;
for (i, kind) in FireKind::ALL.iter().enumerate() {
f.write_str(if i == 0 { "" } else { ", " })?;
f.write_str(kind.name())?;
}
Ok(())
}
}
impl core::error::Error for UnknownFireKind {}
impl core::str::FromStr for FireKind {
type Err = UnknownFireKind;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
FireKind::ALL
.into_iter()
.find(|k| k.name().eq_ignore_ascii_case(s))
.ok_or_else(|| UnknownFireKind(s.to_owned()))
}
}