use std::ops::Range;
use bevy_reflect::{FromReflect, Reflect};
use bevy_render::prelude::Color;
use rand::seq::SliceRandom;
use rand::{prelude::ThreadRng, Rng};
#[derive(Debug, Clone, Reflect, FromReflect)]
pub enum RandomValue<T: Reflect + Clone + FromReflect> {
Constant(T),
RandomChoice(Vec<T>),
}
impl<T: Reflect + Clone + FromReflect> From<T> for RandomValue<T> {
fn from(t: T) -> Self {
RandomValue::Constant(t)
}
}
impl<T: Reflect + Clone + FromReflect> From<Range<T>> for RandomValue<T>
where
Range<T>: Iterator<Item = T>,
{
fn from(r: Range<T>) -> Self {
RandomValue::RandomChoice(r.collect())
}
}
impl<T: Reflect + Clone + FromReflect> From<Vec<T>> for RandomValue<T> {
fn from(v: Vec<T>) -> Self {
RandomValue::RandomChoice(v)
}
}
impl<T: Reflect + Clone + FromReflect> RandomValue<T> {
pub fn get_value(&self, rng: &mut ThreadRng) -> T {
match self {
Self::Constant(t) => t.clone(),
Self::RandomChoice(v) => {
assert!(
!v.is_empty(),
"RandomValue::RandomChoice has no values to choose from!"
);
v.choose(rng).unwrap().clone()
}
}
}
}
#[derive(Debug, Clone, Reflect, FromReflect)]
pub struct JitteredValue {
pub value: f32,
pub jitter_range: Option<Range<f32>>,
}
impl JitteredValue {
pub const fn new(f: f32) -> Self {
Self {
value: f,
jitter_range: None,
}
}
pub const fn jittered(f: f32, jitter_range: Range<f32>) -> Self {
Self {
value: f,
jitter_range: Some(jitter_range),
}
}
pub const fn with_jitter(&self, jitter_range: Range<f32>) -> Self {
Self {
value: self.value,
jitter_range: Some(jitter_range),
}
}
pub fn get_value(&self, rng: &mut ThreadRng) -> f32 {
match &self.jitter_range {
Some(r) => self.value + rng.gen_range(r.clone()),
None => self.value,
}
}
}
impl From<f32> for JitteredValue {
fn from(f: f32) -> Self {
JitteredValue::new(f)
}
}
pub trait Lerpable<T> {
fn lerp(&self, other: T, pct: f32) -> T;
}
impl Lerpable<f32> for f32 {
fn lerp(&self, other: f32, pct: f32) -> f32 {
lerp(*self, other, pct.clamp(0.0, 1.0))
}
}
impl Lerpable<Color> for Color {
fn lerp(&self, other: Color, pct: f32) -> Color {
let clamped_pct = pct.clamp(0.0, 1.0);
Color::rgba(
self.r().lerp(other.r(), clamped_pct),
self.g().lerp(other.g(), clamped_pct),
self.b().lerp(other.b(), clamped_pct),
self.a().lerp(other.a(), clamped_pct),
)
}
}
fn lerp(a: f32, b: f32, pct: f32) -> f32 {
a * (1.0 - pct) + b * pct
}
pub trait RoughlyEqual<T> {
fn roughly_equal(&self, other: T) -> bool;
}
impl RoughlyEqual<f32> for f32 {
#[inline]
fn roughly_equal(&self, other: f32) -> bool {
(self - other).abs() < f32::EPSILON
}
}
impl RoughlyEqual<f64> for f64 {
#[inline]
fn roughly_equal(&self, other: f64) -> bool {
(self - other).abs() < f64::EPSILON
}
}
#[derive(Debug, Clone, Copy, Reflect, FromReflect)]
pub struct ColorPoint {
pub color: Color,
pub point: f32,
}
impl ColorPoint {
pub fn new(color: Color, point: f32) -> Self {
Self { color, point }
}
}
#[derive(Debug, Clone, Reflect, FromReflect)]
pub struct Gradient(Vec<ColorPoint>);
impl Gradient {
pub fn new(points: Vec<ColorPoint>) -> Self {
debug_assert!(
points.len() >= 2,
"Cannot have a gradient with less than two colors"
);
debug_assert!(
points[0].point.roughly_equal(0.0),
"Gradient must start at 0.0"
);
debug_assert!(
points[points.len() - 1].point.roughly_equal(1.0),
"Gradients must end at 1.0"
);
#[cfg(dev)]
for i in 1..points.len() {
debug_assert!(
points[i - 1].point < points[i].point,
"Gradient points must be sorted, with no identical points"
);
}
Self(points)
}
pub fn get_color(&self, pct: f32) -> Color {
let clamped_pct = pct.clamp(0.0, 1.0);
if clamped_pct == 0.0 {
return self.0[0].color;
}
if clamped_pct.roughly_equal(1.0) {
return self.0[self.0.len() - 1].color;
}
if self.0.len() == 2 {
return self.0[0].color.lerp(
self.0[1].color,
(clamped_pct - self.0[0].point) / (self.0[1].point - self.0[0].point).abs(),
);
}
for i in 0..self.0.len() - 1 {
if self.0[i].point.roughly_equal(clamped_pct) {
return self.0[i].color;
}
if clamped_pct > self.0[i].point && clamped_pct < self.0[i + 1].point {
return self.0[i].color.lerp(
self.0[i + 1].color,
(clamped_pct - self.0[i].point) / (self.0[i + 1].point - self.0[i].point).abs(),
);
}
continue;
}
Color::FUCHSIA
}
}
#[derive(Debug, Clone, Reflect)]
pub enum ColorOverTime {
Constant(Color),
Gradient(Gradient),
}
impl Default for ColorOverTime {
fn default() -> Self {
ColorOverTime::Constant(Color::WHITE)
}
}
impl From<Color> for ColorOverTime {
fn from(color: Color) -> Self {
ColorOverTime::Constant(color)
}
}
impl From<Vec<ColorPoint>> for ColorOverTime {
fn from(gradient: Vec<ColorPoint>) -> Self {
ColorOverTime::Gradient(Gradient::new(gradient))
}
}
impl ColorOverTime {
pub fn at_lifetime_pct(&self, pct: f32) -> Color {
match self {
Self::Constant(color) => *color,
Self::Gradient(gradient) => gradient.get_color(pct),
}
}
}
#[derive(Debug, Clone, Reflect)]
pub enum ValueOverTime {
Lerp(Lerp),
Sin(SinWave),
Constant(f32),
}
impl From<f32> for ValueOverTime {
fn from(f: f32) -> Self {
ValueOverTime::Constant(f)
}
}
impl From<Range<f32>> for ValueOverTime {
fn from(r: Range<f32>) -> Self {
ValueOverTime::Lerp(Lerp::new(r.start, r.end))
}
}
impl ValueOverTime {
pub fn at_lifetime_pct(&self, pct: f32) -> f32 {
match self {
Self::Lerp(l) => l.a.lerp(l.b, pct),
Self::Sin(s) => {
s.amplitude * (s.period * (pct * std::f32::consts::TAU) - s.phase_shift).sin()
+ s.vertical_shift
}
Self::Constant(c) => *c,
}
}
}
#[derive(Debug, Clone, Reflect, FromReflect)]
pub struct Lerp {
pub a: f32,
pub b: f32,
}
impl Lerp {
pub const fn new(a: f32, b: f32) -> Self {
Self { a, b }
}
}
impl Default for Lerp {
fn default() -> Self {
Self { a: 0.0, b: 1.0 }
}
}
#[derive(Debug, Clone, Reflect, FromReflect)]
pub struct SinWave {
pub amplitude: f32,
pub period: f32,
pub phase_shift: f32,
pub vertical_shift: f32,
}
impl SinWave {
pub fn new() -> Self {
Self::default()
}
}
impl Default for SinWave {
fn default() -> Self {
Self {
amplitude: 1.0,
period: 1.0,
phase_shift: 0.0,
vertical_shift: 0.0,
}
}
}