glow_control_lib/led/
color_meander.rs

1use crate::led::led_color::LedColor;
2use rand::Rng;
3use std::f64::consts::PI;
4
5pub enum MeanderStyle {
6    Sphere,
7    Cylinder,
8    Surface,
9}
10
11pub struct ColorMeander {
12    step_length: f64,
13    noise_level: f64,
14    xyz: (f64, f64, f64),
15    dir: (f64, f64, f64),
16    style: MeanderStyle,
17}
18
19impl ColorMeander {
20    pub fn new(style: MeanderStyle, speed: f64, noise: f64, start: (f64, f64, f64)) -> Self {
21        let mut rng = rand::thread_rng();
22        let dir = (
23            rng.gen_range(-0.5..0.5),
24            rng.gen_range(-0.5..0.5),
25            rng.gen_range(-0.5..0.5) - start.2,
26        );
27        ColorMeander {
28            step_length: speed,
29            noise_level: noise,
30            xyz: start,
31            dir,
32            style,
33        }
34    }
35
36    fn normalize(&self, vec: (f64, f64, f64)) -> (f64, f64, f64) {
37        let nrm = (vec.0 * vec.0 + vec.1 * vec.1 + vec.2 * vec.2).sqrt();
38        if nrm == 0.0 {
39            (0.0, 0.0, 0.0)
40        } else {
41            (vec.0 / nrm, vec.1 / nrm, vec.2 / nrm)
42        }
43    }
44
45    fn xyz_to_hsl(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) {
46        match self.style {
47            MeanderStyle::Cylinder => {
48                let h = y.atan2(x) / (2.0 * PI) + 0.5;
49                let s = (x * x + y * y).sqrt().min(1.0);
50                let l = z;
51                (h, s, l)
52            }
53            _ => {
54                let h = y.atan2(x) / (2.0 * PI) + 0.5;
55                let l = z.asin() * 2.0 / PI;
56                let r = (x * x + y * y).sqrt();
57                let r0 = (1.0 - z * z).sqrt();
58                let s = if r0 > 0.0 { r / r0 } else { 0.0 }.min(1.0);
59                (h, s, l)
60            }
61        }
62    }
63    fn xyz_color(&self, x: f64, y: f64, z: f64, led_color: &LedColor) -> (u8, u8, u8) {
64        let (h, s, l) = self.xyz_to_hsl(x, y, z);
65        led_color.hsl_color(h, s, l)
66    }
67
68    pub fn get(&self, led_color: &LedColor) -> (u8, u8, u8) {
69        self.xyz_color(self.xyz.0, self.xyz.1, self.xyz.2, led_color)
70    }
71
72    pub fn get_compl(&self, led_color: &LedColor) -> (u8, u8, u8) {
73        self.xyz_color(-self.xyz.0, -self.xyz.1, self.xyz.2, led_color)
74    }
75
76    pub fn get_xyz(&self) -> (f64, f64, f64) {
77        self.xyz
78    }
79
80    pub fn get_hsl(&self) -> (f64, f64, f64) {
81        self.xyz_to_hsl(self.xyz.0, self.xyz.1, self.xyz.2)
82    }
83
84    pub fn step(&mut self) {
85        let mut rng = rand::thread_rng();
86        let (mut nx, mut ny, mut nz) = (
87            self.xyz.0 + self.dir.0 * self.step_length,
88            self.xyz.1 + self.dir.1 * self.step_length,
89            self.xyz.2 + self.dir.2 * self.step_length,
90        );
91
92        let (mut ndir_x, mut ndir_y, mut ndir_z) = self.dir;
93        match self.style {
94            MeanderStyle::Cylinder => {
95                nz = nz.clamp(-1.0, 1.0);
96                let nrm = (nx * nx + ny * ny).sqrt();
97                if nrm > 1.0 {
98                    nx /= nrm;
99                    ny /= nrm;
100                    self.dir = self.normalize((nx - self.xyz.0, ny - self.xyz.1, nz - self.xyz.2));
101                }
102                ndir_x += rng.gen_range(-self.noise_level..self.noise_level);
103                ndir_y += rng.gen_range(-self.noise_level..self.noise_level);
104                ndir_z += rng.gen_range(-self.noise_level..self.noise_level);
105                if (nz + ndir_z).abs() > 1.0 {
106                    let sgn = if nz + ndir_z > 0.0 { 1.0 } else { -1.0 };
107                    let delta = (1.0 - (sgn - nz).powi(2)).sqrt();
108                    let nrm = (ndir_x * ndir_x + ndir_y * ndir_y).sqrt();
109                    ndir_x = ndir_x * delta / nrm;
110                    ndir_y = ndir_y * delta / nrm;
111                    ndir_z = sgn - nz;
112                }
113            }
114            MeanderStyle::Surface => {
115                let nrm = (nx * nx + ny * ny + nz * nz).sqrt();
116                nx /= nrm;
117                ny /= nrm;
118                nz /= nrm;
119                self.dir = self.normalize((nx - self.xyz.0, ny - self.xyz.1, nz - self.xyz.2));
120                ndir_x += rng.gen_range(-self.noise_level..self.noise_level);
121                ndir_y += rng.gen_range(-self.noise_level..self.noise_level);
122                ndir_z += rng.gen_range(-self.noise_level..self.noise_level);
123            }
124            _ => {
125                let nrm = (nx * nx + ny * ny + nz * nz).sqrt();
126                if nrm > 1.0 {
127                    nx /= nrm * nrm;
128                    ny /= nrm * nrm;
129                    nz /= nrm * nrm;
130                    self.dir = self.normalize((nx - self.xyz.0, ny - self.xyz.1, nz - self.xyz.2));
131                }
132                ndir_x += rng.gen_range(-self.noise_level..self.noise_level);
133                ndir_y += rng.gen_range(-self.noise_level..self.noise_level);
134                ndir_z += rng.gen_range(-self.noise_level..self.noise_level);
135            }
136        }
137
138        let nrm = (ndir_x * ndir_x + ndir_y * ndir_y + ndir_z * ndir_z).sqrt();
139        if nrm != 0.0 {
140            self.dir = (ndir_x / nrm, ndir_y / nrm, ndir_z / nrm);
141        }
142
143        self.xyz = (nx, ny, nz);
144    }
145}