use bevy::reflect::Reflect;
use std::f32::consts::PI;
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Reflect)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CrownShape {
#[default]
Cylindrical,
Conical,
Spherical,
Hemispherical,
TaperedCylindrical,
Flame,
InverseConical,
TendFlame,
Spreading,
}
impl CrownShape {
pub fn length_multiplier(self, height_ratio: f32) -> f32 {
let t = height_ratio.clamp(0.0, 1.0);
match self {
CrownShape::Cylindrical => 1.0,
CrownShape::Conical => 1.0 - 0.8 * t,
CrownShape::Spherical => 0.2 + 0.8 * (PI * t).sin(),
CrownShape::Hemispherical => 0.2 + 0.8 * (PI * 0.5 * t).sin(),
CrownShape::TaperedCylindrical => 1.0 - 0.5 * t,
CrownShape::Flame => {
let peak = 0.7;
if t < peak {
0.3 + 0.7 * (t / peak)
} else {
1.0 - 0.8 * ((t - peak) / (1.0 - peak))
}
}
CrownShape::InverseConical => 0.2 + 0.8 * t,
CrownShape::TendFlame => {
let peak = 0.6;
if t < peak {
0.4 + 0.6 * (t / peak)
} else {
let falloff = (t - peak) / (1.0 - peak);
1.0 - 0.6 * falloff * falloff
}
}
CrownShape::Spreading => {
let peak = 0.4;
if t < peak {
0.5 + 0.5 * (t / peak)
} else {
let falloff = (t - peak) / (1.0 - peak);
1.0 - 0.4 * falloff
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cylindrical_uniform() {
let shape = CrownShape::Cylindrical;
assert_eq!(shape.length_multiplier(0.0), 1.0);
assert_eq!(shape.length_multiplier(0.5), 1.0);
assert_eq!(shape.length_multiplier(1.0), 1.0);
}
#[test]
fn conical_decreases_with_height() {
let shape = CrownShape::Conical;
let bottom = shape.length_multiplier(0.0);
let middle = shape.length_multiplier(0.5);
let top = shape.length_multiplier(1.0);
assert!(bottom > middle);
assert!(middle > top);
assert!((bottom - 1.0).abs() < 0.001);
assert!((top - 0.2).abs() < 0.001);
}
#[test]
fn spherical_peaks_in_middle() {
let shape = CrownShape::Spherical;
let bottom = shape.length_multiplier(0.0);
let middle = shape.length_multiplier(0.5);
let top = shape.length_multiplier(1.0);
assert!(middle > bottom);
assert!(middle > top);
}
#[test]
fn flame_peaks_at_70_percent() {
let shape = CrownShape::Flame;
assert!(shape.length_multiplier(0.7) > shape.length_multiplier(0.5));
assert!(shape.length_multiplier(0.7) > shape.length_multiplier(0.9));
}
#[test]
fn inverse_conical_increases_with_height() {
let shape = CrownShape::InverseConical;
let bottom = shape.length_multiplier(0.0);
let top = shape.length_multiplier(1.0);
assert!(bottom < top);
assert!((bottom - 0.2).abs() < 0.001);
assert!((top - 1.0).abs() < 0.001);
}
#[test]
fn clamping() {
let shape = CrownShape::Conical;
assert_eq!(shape.length_multiplier(-0.5), shape.length_multiplier(0.0));
assert_eq!(shape.length_multiplier(1.5), shape.length_multiplier(1.0));
}
#[test]
fn spreading_peaks_at_40_percent() {
let shape = CrownShape::Spreading;
let at_peak = shape.length_multiplier(0.4);
let before_peak = shape.length_multiplier(0.2);
let after_peak = shape.length_multiplier(0.7);
let at_top = shape.length_multiplier(1.0);
assert!(at_peak > before_peak);
assert!(at_peak > after_peak);
assert!(after_peak > at_top);
assert!((at_peak - 1.0).abs() < 0.001);
}
}