From aa56e36f3fd3d573ab0fe70a2c2c6ba03fd91d0b Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Sun, 15 Dec 2019 21:39:03 +0100 Subject: [PATCH] Added snowfall animation --- include/Animation/AllAnimations.h | 1 + include/Animation/AnimationController.h | 4 +- include/Animation/SnowfallAnimation.h | 58 +++++++ src/Animation/SnowfallAnimation.cpp | 191 ++++++++++++++++++++++++ 4 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 include/Animation/SnowfallAnimation.h create mode 100644 src/Animation/SnowfallAnimation.cpp diff --git a/include/Animation/AllAnimations.h b/include/Animation/AllAnimations.h index b207c28..6d67280 100644 --- a/include/Animation/AllAnimations.h +++ b/include/Animation/AllAnimations.h @@ -3,3 +3,4 @@ #include "ConnectingAnimation.h" #include "ConnectionEstablishedAnimation.h" #include "FireAnimation.h" +#include "SnowfallAnimation.h" diff --git a/include/Animation/AnimationController.h b/include/Animation/AnimationController.h index cb52c1b..c03eaad 100644 --- a/include/Animation/AnimationController.h +++ b/include/Animation/AnimationController.h @@ -14,13 +14,15 @@ class AnimationController enum DefaultAnimation { FIRE_HOT = 0, FIRE_COLD = 1, + SNOWFALL = 2, NUM_DEFAULT_ANIMATIONS }; static const constexpr std::array AnimationNames{ "Hot Fire", - "Cold Fire" + "Cold Fire", + "Snowfall" }; AnimationController(Fader *fader); diff --git a/include/Animation/SnowfallAnimation.h b/include/Animation/SnowfallAnimation.h new file mode 100644 index 0000000..d6d3c72 --- /dev/null +++ b/include/Animation/SnowfallAnimation.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include "Animation.h" + +#include "fasttrigon.h" + +class SnowfallAnimation : public Animation +{ + public: + SnowfallAnimation(Fader *fader); + + void loop(uint64_t frame) override; + + void stop(void) override + { + m_stopping = true; + } + + void reset(void) override; + + private: + class SnowFlake + { + public: + SnowFlake(Fader *fader, int32_t phase, int32_t vertSpeed, int32_t baseStrip); + + void move(void); + bool has_fallen(void) const; + + void render(void) const; + + int32_t cur_strip(void) const; + + private: + Fader *m_fader; + + int32_t m_phase; + int32_t m_phaseIncPerFrame; + int32_t m_vertSpeed; // in 1/256 LEDs + + int32_t m_radius; // in 1/256 LEDs + int32_t m_radiusSq; // in 1/256 LEDs + + int32_t m_basePosStrip; // in 1/256 strips (for speed only) + int32_t m_posStrip; // in 1/256 strips + int32_t m_posLED; // in 1/256 LEDs + }; + + std::list m_snowFlakes; + std::vector m_snowLevel; // level for each strip in 1/256 LEDs + + std::default_random_engine m_gen; + + bool m_stopping; +}; diff --git a/src/Animation/SnowfallAnimation.cpp b/src/Animation/SnowfallAnimation.cpp new file mode 100644 index 0000000..f23fbfa --- /dev/null +++ b/src/Animation/SnowfallAnimation.cpp @@ -0,0 +1,191 @@ +#include + +#include "Animation/SnowfallAnimation.h" + +/********* SnowFlake *********/ + +SnowfallAnimation::SnowFlake::SnowFlake(Fader *fader, int32_t phase, int32_t vertSpeed, int32_t baseStrip) + : m_fader(fader), + m_phase(phase), + m_phaseIncPerFrame(fasttrigon::LUT_SIZE/60), + m_vertSpeed(vertSpeed), + m_radius(282), // 1.1 * 256 + m_radiusSq(310), + m_basePosStrip(baseStrip*256), + m_posStrip(baseStrip*256), + m_posLED(fader->modules_per_strip()*256 + m_radius*2) +{} + +void SnowfallAnimation::SnowFlake::move(void) +{ + m_phase += m_phaseIncPerFrame; + if(m_phase > fasttrigon::LUT_SIZE) { + m_phase -= fasttrigon::LUT_SIZE; + } + + m_posStrip = m_basePosStrip + 2*fasttrigon::fastsin(m_phase); + m_posLED -= m_vertSpeed; +} + +bool SnowfallAnimation::SnowFlake::has_fallen(void) const +{ + return m_posLED < -(2*m_radius); +} + +int32_t SnowfallAnimation::SnowFlake::cur_strip(void) const +{ + int32_t strip = m_posStrip / 256; + + uint32_t nstrip = m_fader->strips(); + + while(strip < 0) { + strip += nstrip; + } + while(strip >= nstrip) { + strip -= nstrip; + } + + return strip; +} + +void SnowfallAnimation::SnowFlake::render(void) const +{ + int32_t min_strip = (m_posStrip - m_radius) / 256; + int32_t max_strip = (m_posStrip + m_radius + 256) / 256; + int32_t min_led = (m_posLED - m_radius) / 256; + int32_t max_led = (m_posLED + m_radius + 256) / 256; + + for(int32_t strip = min_strip; strip <= max_strip; strip++) { + int32_t finestrip = strip * 256; + + for(int32_t led = min_led; led <= max_led; led++) { + if(led < 0 || led >= m_fader->modules_per_strip()) { + continue; + } + + int32_t fineled = led * 256; + + int32_t diststrip = finestrip - m_posStrip; + int32_t distled = fineled - m_posLED; + + int32_t distsq = (diststrip*diststrip + distled*distled) / 256; + + // brightness target range: 0..255 + int32_t brightness = 256 - 256 * distsq / m_radiusSq; + + if(brightness < 0) { + continue; // this pixel is too far away -> no effect + } + + int32_t normstrip = strip; + int32_t nstrips = m_fader->strips(); + while(normstrip < 0) { + normstrip += nstrips; + } + while(normstrip >= nstrips) { + normstrip -= nstrips; + } + + m_fader->add_color(normstrip, led, + Fader::Color{ + 0, + 0, + static_cast(32*brightness/256), + static_cast(48*brightness/256) + }); + } + } +} + +/********* SnowfallAnimation *********/ + +SnowfallAnimation::SnowfallAnimation(Fader *fader) + : Animation(fader), + m_snowLevel(fader->strips(), 0) +{ + reset(); +} + +void SnowfallAnimation::loop(uint64_t frame) +{ + std::size_t nled = m_fader->modules_per_strip(); + std::size_t nstrip = m_fader->strips(); + + // update snow level on ground from fallen snow flakes + for(auto &flake: m_snowFlakes) { + if(flake.has_fallen()) { + m_snowLevel[flake.cur_strip()] += 65536/5; + } + } + + // remove fallen snow flakes + m_snowFlakes.remove_if( + [](const SnowFlake &flake) { return flake.has_fallen(); }); + + if(!m_stopping) { + // spawn new snowflakes + std::uniform_int_distribution spawnRNG(0, 999); + if(spawnRNG(m_gen) < 50) { + std::uniform_int_distribution phaseRNG(0, fasttrigon::LUT_SIZE-1); + std::uniform_int_distribution vertSpeedRNG(13, 38); // 0.05..0.15 + std::uniform_int_distribution baseStripRNG(0, nstrip-1); + + m_snowFlakes.emplace_back(SnowFlake{ + m_fader, + phaseRNG(m_gen), + vertSpeedRNG(m_gen), + baseStripRNG(m_gen) + }); + } + } + + // clear the frame buffer + m_fader->set_color(Fader::Color{0, 0, 0, 0}); + + // move snow flakes and render + for(auto &flake: m_snowFlakes) { + flake.move(); + flake.render(); + } + + // render snow on the ground and melt it + for(size_t strip = 0; strip < nstrip; strip++) { + size_t led = 0; + size_t level = m_snowLevel[strip]; + size_t brightness; + + while(level > 0 && led < nled) { + if(level >= 65536) { + brightness = 65536; + } else { + brightness = level; + } + + m_fader->add_color(strip, led, + Fader::Color{ + 0, + static_cast(8 * brightness / 65535), + static_cast(32 * brightness / 65535), + static_cast(12 * brightness / 65535) + }); + + level -= brightness; + led++; + } + + // "melt" the snow + m_snowLevel[strip] = m_snowLevel[strip] * 999 / 1000; + } + + m_fader->update(); + + if(m_stopping && m_snowFlakes.empty()) { + m_running = false; + } +} + +void SnowfallAnimation::reset(void) +{ + m_stopping = false; + m_running = true; +}