~starkingdoms/starkingdoms

ref: 268047aaf3cc14686d94757d6232fc5083cc2d49 starkingdoms/crates/unified/src/particles.rs -rw-r--r-- 4.3 KiB
268047aaghostly_zsh fix: orbit predictor is now actually positioned correctly on the screen 17 days 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
140
141
142
143
144
145
146
147
use bevy::color::{Color, LinearRgba};
use bevy::math::Vec2;
use crate::prelude::*;
use ordered_float::OrderedFloat;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::ops::Bound::{Excluded, Included, Unbounded};

#[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(
            points
                .into_iter()
                .map(|u| (OrderedFloat(u.0), u.1))
                .collect::<BTreeMap<_, _>>(),
        )
    }

    /// 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.
    #[must_use]
    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).abs() < 0.01 {
            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).abs() < 0.01 {
            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.
    #[must_use]
    pub fn clamp_time(&self, time: f32) -> Option<f32> {
        let first = self.0.iter().nth(0)?;
        let last = self.0.iter().last()?;

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

pub trait Lerp {
    #[must_use]
    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.saturating_sub(self.randomness);
        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))
    }
}