glow-control-lib 0.6.0

A library for controlling programmable LED lights
Documentation
use crate::led::led_color::LedColor;
use rand::Rng;
use std::f64::consts::PI;

pub enum MeanderStyle {
    Sphere,
    Cylinder,
    Surface,
}

pub struct ColorMeander {
    step_length: f64,
    noise_level: f64,
    xyz: (f64, f64, f64),
    dir: (f64, f64, f64),
    style: MeanderStyle,
}

impl ColorMeander {
    pub fn new(style: MeanderStyle, speed: f64, noise: f64, start: (f64, f64, f64)) -> Self {
        let mut rng = rand::thread_rng();
        let dir = (
            rng.gen_range(-0.5..0.5),
            rng.gen_range(-0.5..0.5),
            rng.gen_range(-0.5..0.5) - start.2,
        );
        ColorMeander {
            step_length: speed,
            noise_level: noise,
            xyz: start,
            dir,
            style,
        }
    }

    fn normalize(&self, vec: (f64, f64, f64)) -> (f64, f64, f64) {
        let nrm = (vec.0 * vec.0 + vec.1 * vec.1 + vec.2 * vec.2).sqrt();
        if nrm == 0.0 {
            (0.0, 0.0, 0.0)
        } else {
            (vec.0 / nrm, vec.1 / nrm, vec.2 / nrm)
        }
    }

    fn xyz_to_hsl(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) {
        match self.style {
            MeanderStyle::Cylinder => {
                let h = y.atan2(x) / (2.0 * PI) + 0.5;
                let s = (x * x + y * y).sqrt().min(1.0);
                let l = z;
                (h, s, l)
            }
            _ => {
                let h = y.atan2(x) / (2.0 * PI) + 0.5;
                let l = z.asin() * 2.0 / PI;
                let r = (x * x + y * y).sqrt();
                let r0 = (1.0 - z * z).sqrt();
                let s = if r0 > 0.0 { r / r0 } else { 0.0 }.min(1.0);
                (h, s, l)
            }
        }
    }
    fn xyz_color(&self, x: f64, y: f64, z: f64, led_color: &LedColor) -> (u8, u8, u8) {
        let (h, s, l) = self.xyz_to_hsl(x, y, z);
        led_color.hsl_color(h, s, l)
    }

    pub fn get(&self, led_color: &LedColor) -> (u8, u8, u8) {
        self.xyz_color(self.xyz.0, self.xyz.1, self.xyz.2, led_color)
    }

    pub fn get_compl(&self, led_color: &LedColor) -> (u8, u8, u8) {
        self.xyz_color(-self.xyz.0, -self.xyz.1, self.xyz.2, led_color)
    }

    pub fn get_xyz(&self) -> (f64, f64, f64) {
        self.xyz
    }

    pub fn get_hsl(&self) -> (f64, f64, f64) {
        self.xyz_to_hsl(self.xyz.0, self.xyz.1, self.xyz.2)
    }

    pub fn step(&mut self) {
        let mut rng = rand::thread_rng();
        let (mut nx, mut ny, mut nz) = (
            self.xyz.0 + self.dir.0 * self.step_length,
            self.xyz.1 + self.dir.1 * self.step_length,
            self.xyz.2 + self.dir.2 * self.step_length,
        );

        let (mut ndir_x, mut ndir_y, mut ndir_z) = self.dir;
        match self.style {
            MeanderStyle::Cylinder => {
                nz = nz.clamp(-1.0, 1.0);
                let nrm = (nx * nx + ny * ny).sqrt();
                if nrm > 1.0 {
                    nx /= nrm;
                    ny /= nrm;
                    self.dir = self.normalize((nx - self.xyz.0, ny - self.xyz.1, nz - self.xyz.2));
                }
                ndir_x += rng.gen_range(-self.noise_level..self.noise_level);
                ndir_y += rng.gen_range(-self.noise_level..self.noise_level);
                ndir_z += rng.gen_range(-self.noise_level..self.noise_level);
                if (nz + ndir_z).abs() > 1.0 {
                    let sgn = if nz + ndir_z > 0.0 { 1.0 } else { -1.0 };
                    let delta = (1.0 - (sgn - nz).powi(2)).sqrt();
                    let nrm = (ndir_x * ndir_x + ndir_y * ndir_y).sqrt();
                    ndir_x = ndir_x * delta / nrm;
                    ndir_y = ndir_y * delta / nrm;
                    ndir_z = sgn - nz;
                }
            }
            MeanderStyle::Surface => {
                let nrm = (nx * nx + ny * ny + nz * nz).sqrt();
                nx /= nrm;
                ny /= nrm;
                nz /= nrm;
                self.dir = self.normalize((nx - self.xyz.0, ny - self.xyz.1, nz - self.xyz.2));
                ndir_x += rng.gen_range(-self.noise_level..self.noise_level);
                ndir_y += rng.gen_range(-self.noise_level..self.noise_level);
                ndir_z += rng.gen_range(-self.noise_level..self.noise_level);
            }
            _ => {
                let nrm = (nx * nx + ny * ny + nz * nz).sqrt();
                if nrm > 1.0 {
                    nx /= nrm * nrm;
                    ny /= nrm * nrm;
                    nz /= nrm * nrm;
                    self.dir = self.normalize((nx - self.xyz.0, ny - self.xyz.1, nz - self.xyz.2));
                }
                ndir_x += rng.gen_range(-self.noise_level..self.noise_level);
                ndir_y += rng.gen_range(-self.noise_level..self.noise_level);
                ndir_z += rng.gen_range(-self.noise_level..self.noise_level);
            }
        }

        let nrm = (ndir_x * ndir_x + ndir_y * ndir_y + ndir_z * ndir_z).sqrt();
        if nrm != 0.0 {
            self.dir = (ndir_x / nrm, ndir_y / nrm, ndir_z / nrm);
        }

        self.xyz = (nx, ny, nz);
    }
}