~starkingdoms/starkingdoms

ref: 461d3d233fe9f2f94f9a1f1b9ccf9d1cdc9a9edb starkingdoms/crates/unified/src/particles.rs -rw-r--r-- 4.2 KiB
461d3d23TerraMaster85 fix starfield alignment(?) 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
128
129
130
131
132
133
134
135
136
137
138
139
use std::collections::BTreeMap;
use std::ops::Bound::{Included, Excluded, 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; }
        
        if self.0.len() == 1 && at == self.0.iter().nth(0).unwrap().0.0 {
            return Some(*self.0.iter().nth(0).unwrap().1);
        }

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

        let (b_key, b_val) = if at == self.0.iter().last().unwrap().0.0 {
            self.0.iter().last().unwrap()
        } else {
            // Find B, the point "to the right" of `at`
            let mut r2 = self.0.range((Excluded(OrderedFloat(at)), Unbounded));
            r2.next()?
        };

        if a_key == b_key && b_key == self.0.iter().last().unwrap().0 {
            return Some(*b_val);
        }

        // 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))
    }
}