Skip to main content

glow_effects/effects/
shine.rs

1use std::collections::HashSet;
2
3use rand::prelude::{IteratorRandom, SliceRandom};
4use rand::thread_rng;
5use thiserror::Error;
6
7use crate::util::color::{RgbContainer, RGB};
8use crate::util::color_point::ColorPointContainer;
9use crate::util::effect::Effect;
10
11#[derive(Error, Debug)]
12pub enum ShineError {
13    #[error(
14        "num_start_simultaneous must be between 1 up to and including the total number of points"
15    )]
16    InvalidNumStartSimultaneous,
17    #[error("colors set must not be empty")]
18    ColorSetIsEmpty,
19}
20
21pub struct Shine<U: RgbContainer, T: ColorPointContainer<U>> {
22    points: Vec<T>,
23    colors: HashSet<U>,
24    frames_between_glow_start: u32,
25    frames_to_max_glow: u32,
26    frames_to_fade: u32,
27    num_start_simultaneous: usize,
28    current_frame: u64,
29    glow_start_times: Vec<u64>,
30    frames_since_last_glow: u32,
31    current_glow_colors: Vec<U>,
32}
33
34impl<U: RgbContainer, T: ColorPointContainer<U>> Shine<U, T> {
35    pub fn new(
36        points: Vec<T>,
37        colors: HashSet<U>,
38        frames_between_glow_start: u32,
39        frames_to_max_glow: u32,
40        frames_to_fade: u32,
41        num_start_simultaneous: usize,
42    ) -> Result<Self, ShineError> {
43        if num_start_simultaneous == 0 || num_start_simultaneous > points.len() {
44            return Err(ShineError::InvalidNumStartSimultaneous);
45        }
46        if colors.is_empty() {
47            return Err(ShineError::ColorSetIsEmpty);
48        }
49        let current_frame = ((frames_to_max_glow + frames_to_fade) * 2) as u64;
50
51        let glow_start_times = vec![0_u64; points.len()];
52
53        let frames_since_last_glow = frames_between_glow_start;
54        let current_glow_colors = vec![*colors.iter().nth(0).unwrap(); points.len()];
55
56        Ok(Shine {
57            points,
58            colors,
59            frames_between_glow_start,
60            frames_to_max_glow,
61            frames_to_fade,
62            num_start_simultaneous,
63            current_frame,
64            glow_start_times,
65            frames_since_last_glow,
66            current_glow_colors,
67        })
68    }
69}
70
71impl<U: RgbContainer, T: ColorPointContainer<U>> Effect<U, T> for Shine<U, T> {
72    fn get_frame(&mut self) -> Vec<T> {
73        let num_points = self.points.len();
74        if self.frames_since_last_glow >= self.frames_between_glow_start {
75            let mut rng = thread_rng();
76            let mut available_points: Vec<usize> = (0..num_points)
77                .filter(|&i| {
78                    self.current_frame - self.glow_start_times[i]
79                        >= (self.frames_to_max_glow + self.frames_to_fade) as u64
80                })
81                .collect();
82            available_points.shuffle(&mut rng);
83            for &led_index in available_points.iter().take(self.num_start_simultaneous) {
84                self.glow_start_times[led_index] = self.current_frame;
85                // Randomly select a color for the LED
86                self.points[led_index] = self
87                    .colors
88                    .iter()
89                    .map(|color| self.points[led_index].copy_with_new_color_value(*color))
90                    .choose(&mut rng)
91                    .expect("colors set is empty");
92            }
93            self.frames_since_last_glow = 0;
94        } else {
95            self.frames_since_last_glow += 1;
96        }
97
98        for i in 0..num_points {
99            let elapsed = self.current_frame - self.glow_start_times[i];
100
101            let glow_color = if self.points[i].get_color_value().is_black() {
102                self.current_glow_colors[i] =
103                    *self.colors.iter().choose(&mut thread_rng()).unwrap();
104                self.current_glow_colors[i]
105            } else {
106                self.current_glow_colors[i]
107            };
108
109            let brightness = if elapsed < self.frames_to_max_glow as u64 {
110                elapsed as f64 / self.frames_to_max_glow as f64
111            } else if elapsed < (self.frames_to_max_glow + self.frames_to_fade) as u64 {
112                1.0 - (elapsed - self.frames_to_max_glow as u64) as f64 / self.frames_to_fade as f64
113            } else {
114                0.0
115            };
116
117            self.points[i] = self.points[i].copy_with_new_color_value(
118                self.points[i].get_color_value().copy_with_new_rgb(RGB {
119                    red: (glow_color.get_rgb().red as f64 * brightness) as u8,
120                    green: (glow_color.get_rgb().green as f64 * brightness) as u8,
121                    blue: (glow_color.get_rgb().blue as f64 * brightness) as u8,
122                }),
123            );
124        }
125        self.current_frame += 1;
126        self.points.clone()
127    }
128}
129
130#[cfg(test)]
131mod shine_tests {
132    use std::collections::HashSet;
133    use std::iter::FromIterator;
134
135    use crate::util::color_point::RgbPoint;
136    use crate::util::point::Point;
137
138    use super::*;
139
140    // Assuming this creates a Shine instance for testing with RGB and RgbPoint
141    fn create_test_shine() -> Shine<RGB, RgbPoint<RGB>> {
142        let points = vec![
143            RgbPoint {
144                point: Point {
145                    x: 0.0,
146                    y: 0.0,
147                    z: 0.0,
148                },
149                color: RGB {
150                    red: 0,
151                    green: 0,
152                    blue: 0,
153                },
154            },
155            RgbPoint {
156                point: Point {
157                    x: 1.0,
158                    y: 1.0,
159                    z: 1.0,
160                },
161                color: RGB {
162                    red: 0,
163                    green: 0,
164                    blue: 0,
165                },
166            },
167        ];
168        let colors = HashSet::from_iter(vec![RGB {
169            red: 255,
170            green: 0,
171            blue: 0,
172        }]);
173        Shine::new(points, colors, 1, 2, 2, 1).expect("Failed to create Shine")
174    }
175
176    #[test]
177    fn test_shade_of_red_with_no_green_or_blue() {
178        let mut shine = create_test_shine();
179
180        // Run several frames to get past initial states and into a cyclic glow state
181        for _ in 0..5 {
182            shine.get_frame();
183        }
184
185        // Check after several frames that at least one point's color is a shade of red
186        // with no green or blue components (indicating it's indeed glowing red).
187        let points = shine.get_frame(); // Assume this gets the current state of all points
188
189        let mut found_shade_of_red = false;
190        for point in points.iter() {
191            let color = point.get_color_value();
192            if color.red > 0 && color.green == 0 && color.blue == 0 {
193                found_shade_of_red = true;
194                break;
195            }
196        }
197
198        assert!(found_shade_of_red, "Failed to find at least one point that is a shade of red with no green or blue elements.");
199    }
200}