~starkingdoms/starkingdoms

ref: c286244e20a5c15a2e001b481016d1cb51c87d4a starkingdoms/crates/unified/src/particles.rs -rw-r--r-- 3.8 KiB
c286244eghostly_zsh more insanity 5 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::collections::BTreeMap;
use std::ops::Bound::{Included, Unbounded};
use bevy::color::{Color, LinearRgba};
use bevy::math::Vec2;
use bevy::prelude::Component;
use ordered_float::{FloatCore, OrderedFloat};
use rand::Rng;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Component, Clone)]
pub struct ParticleEffect {
    // -- lifetime / spawning -- //

    /// Particle lifetime in seconds
    pub lifetime_seconds: RandF32,
    /// Delay inbetween each batch of particles spawned
    pub batch_spawn_delay_seconds: RandF32,
    /// Number of distinct particles spawned per batch
    pub particles_in_batch: RandUsize,

    // -- velocity -- //

    /// Initial linear velocity added to the particle's velocity when it is spawned
    pub initial_linear_velocity: RandVec2,
    /// Initial angular velocity added to the particle's rotation when it is spawned
    pub initial_angular_velocity: RandF32,

    // -- scale -- //

    // Scale curve over the lifetime of the particle
    pub scale: LifetimeCurve<f32>,

    // -- color -- //

    // Color curve over the lifetime of the particle
    pub color: LifetimeCurve<Color>,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct LifetimeCurve<P: Lerp>(pub BTreeMap<OrderedFloat<f32>, P>);
impl<P: Lerp + Copy> LifetimeCurve<P> {
    pub fn new<'a>(points: impl IntoIterator<Item = &'a (f32, P)>) -> Self where P: 'a {
        Self(BTreeMap::from_iter(points.into_iter().map(|u| (OrderedFloat(u.0), u.1.clone()))))
    }

    /// Sample for the value at T. Returns None if the curve has no points, or if T is outside
    /// the domain of the points specified.
    pub fn sample(&self, at: f32) -> Option<P> {
        if self.0.is_empty() { return None; }

        // Find A, the point "to the left" of `at`
        let mut r1 = self.0.range((Included(OrderedFloat(at)), Unbounded));
        let (a_key, a_val) = r1.next()?;

        // Find B, the point "to the right" of `at`
        let mut r2 = self.0.range((Unbounded, Included(OrderedFloat(at))));
        let (b_key, b_val) = r2.next()?;

        // Calculate `t` (value from 0 - 1 indicating our progress from A to B)
        let t = at - *(a_key / (b_key - a_key));

        // Lerp
        Some(a_val.lerp(b_val, t))
    }

    /// Given an input time value, use the ends of the spline's time to clamp
    /// the input value.
    pub fn clamp_time(&self, time: f32) -> Option<f32> {
        let Some(first) = self.0.iter().nth(0) else { return None; };
        let Some(last) = self.0.iter().last() else { return None; };

        Some(time.clamp(first.0.0, last.0.0))
    }
}


pub trait Lerp {
    fn lerp(&self, other: &Self, t: f32) -> Self;
}
impl Lerp for f32 {
    fn lerp(&self, other: &Self, t: f32) -> Self {
        (1.0 - t) * self + t * other
    }
}
impl Lerp for Color {
    fn lerp(&self, other: &Self, t: f32) -> Self {
        let a: LinearRgba = (*self).into();
        let b: LinearRgba = (*other).into();
        Color::from(a * (1.0 - t) + b * t)
    }
}


#[derive(Deserialize, Serialize, Clone, Copy)]
pub struct RandF32 {
    pub value: f32,
    pub randomness: f32
}
impl RandF32 {
    pub fn sample(&self, rng: &mut impl Rng) -> f32 {
        rng.random_range(self.value-self.randomness .. self.value+self.randomness)
    }
}

#[derive(Deserialize, Serialize, Clone, Copy)]
pub struct RandUsize {
    pub value: usize,
    pub randomness: usize
}
impl RandUsize {
    pub fn sample(&self, rng: &mut impl Rng) -> usize {
        let lower_bound = self.value.checked_sub(self.randomness).unwrap_or(0);
        rng.random_range(lower_bound .. self.value+self.randomness)
    }
}


#[derive(Deserialize, Serialize, Clone, Copy)]
pub struct RandVec2 {
    pub x: RandF32,
    pub y: RandF32,
}
impl RandVec2 {
    pub fn sample(&self, rng: &mut impl Rng) -> Vec2 {
        Vec2::new(self.x.sample(rng), self.y.sample(rng))
    }
}