smart_led_effects 0.1.8

A collection of effects for LED strips
Documentation
use crate::strip::EffectIterator;
use palette::{Darken, FromColor, Hsv, Mix, Srgb};
use rand::{thread_rng, Rng};

#[derive(Debug, Clone, Copy)]
pub struct Particle {
    position: i32,
    colour: Srgb,
    reverse: bool,
    speed: usize,
    size: usize,
}

impl Particle {
    pub fn new(position: i32, reverse: bool) -> Self {
        let mut rng = thread_rng();

        Particle {
            position,
            colour: Srgb::from_color(Hsv::new(rng.gen_range(0.0..360.0), 1.0, 1.0)),
            reverse,
            speed: 1,
            size: rng.gen_range(1..4),
        }
    }

    pub fn collide(&self, other: &Particle) -> Option<(Particle, Particle)> {
        if (self.position - other.position).abs() > 1 || self.position - other.position < -1 {
            return None;
        }
        let mut rhs = *other;
        let mut lhs = *self;
        lhs.reverse = !lhs.reverse;
        rhs.reverse = !rhs.reverse;

        let scaling_factor = 1.0 - lhs.size as f32 / (lhs.size + rhs.size) as f32;
        let mix = lhs.colour.mix(rhs.colour, scaling_factor / 2.0);
        lhs.colour = mix;
        rhs.colour = mix;

        Some((lhs, rhs))
    }
}

pub struct Collision {
    particles: Vec<Particle>,
    count: usize,
    shatter: bool,
    shattered: bool,
    current: Vec<Srgb>,
}

impl Collision {
    pub fn new(count: usize, shatter: Option<bool>) -> Self {
        let p1 = Particle::new(0, false);
        let p2 = Particle::new(count as i32, true);

        Collision {
            count,
            particles: vec![p1, p2],
            shatter: shatter.unwrap_or(true),
            shattered: false,
            current: vec![Srgb::new(0.0, 0.0, 0.0); count],
        }
    }

    pub fn reset(&mut self) {
        let p1 = Particle::new(0, false);
        let p2 = Particle::new(self.count as i32 - 1, true);

        self.particles = vec![p1, p2];
        self.shattered = false;
    }

    pub fn check_for_collision(&mut self) -> bool {
        if let Some((lhs, rhs)) = self.particles[0].collide(&self.particles[1]) {
            self.particles[0] = lhs;
            self.particles[1] = rhs;

            true
        } else {
            false
        }
    }

    pub fn shatter(&mut self) {
        if !self.shatter {
            return;
        }
        self.shattered = true;

        self.current[self.count / 2] = Srgb::new(1.0, 1.0, 1.0);

        let mut hsv = Hsv::from_color(self.particles[0].colour);
        hsv.value = 1.0;
        let normalize = 1.0 / self.count as f32;

        let mut rng = thread_rng();

        for i in 0..(self.count / 2) {
            if rng.gen_range(0.0..1.0) < 0.5 {
                let hsv = hsv.darken(1.0 - normalize * i as f32);
                self.current[i] = Srgb::from_color(hsv);
            }
        }
        for i in (self.count / 2)..(self.count) {
            if rng.gen_range(0.0..1.0) < 0.5 {
                let hsv = hsv.darken(normalize * i as f32);
                self.current[i] = Srgb::from_color(hsv);
            }
        }
    }

    pub fn move_particles(&mut self) -> Vec<Srgb<u8>> {
        let mut out = vec![Srgb::<u8>::new(0, 0, 0); self.count];
        for particle in self.particles.iter_mut() {
            if particle.position >= 0 && particle.position < self.count as i32 {
                for i in 0..particle.size {
                    if particle.reverse {
                        if particle.position + i as i32 >= 0
                            && i as i32 + particle.position < self.count as i32
                        {
                            out[(particle.position + i as i32) as usize] =
                                particle.colour.into_format();
                        }
                    } else if particle.position - i as i32 >= 0
                        && (particle.position - i as i32) < self.count as i32
                    {
                        out[(particle.position - i as i32) as usize] =
                            particle.colour.into_format();
                    }
                }
                out[particle.position as usize] = particle.colour.into_format()
            }
        }
        out
    }
}

impl EffectIterator for Collision {
    fn name(&self) -> &'static str {
        "Collision"
    }

    fn next(&mut self) -> Option<Vec<Srgb<u8>>> {
        if !self.shattered {
            for particle in self.particles.iter_mut() {
                if particle.reverse {
                    particle.position -= particle.speed as i32;
                } else {
                    particle.position += particle.speed as i32;
                }
            }

            if self.check_for_collision() {
                self.shatter();
            }

            if self.particles[0].position < 0 && self.particles[1].position >= self.count as i32 {
                self.reset();
            }

            Some(self.move_particles())
        } else {
            let mut rng = thread_rng();
            for pixel in self.current.iter_mut() {
                if rng.gen_range(0.0..1.0) < 0.5 {
                    *pixel = pixel.darken(0.1);
                }
            }

            const RESET_VAL: f32 = 0.01;

            for pixel in self.current.iter() {
                if pixel.red > RESET_VAL || pixel.green > RESET_VAL || pixel.blue > RESET_VAL {
                    return Some(self.current.iter().map(|x| x.into_format()).collect());
                }
            }
            self.reset();
            Some(vec![Srgb::<u8>::new(0, 0, 0); self.count])
        }
    }
}