glow_effects/effects/
shine.rs1use 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 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 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 for _ in 0..5 {
182 shine.get_frame();
183 }
184
185 let points = shine.get_frame(); 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}