diff --git a/include/Animation/AllAnimations.h b/include/Animation/AllAnimations.h index bd0e38e..3bf2f13 100644 --- a/include/Animation/AllAnimations.h +++ b/include/Animation/AllAnimations.h @@ -7,3 +7,4 @@ #include "FadeToColorAnimation.h" #include "ImageScrollerAnimation.h" #include "MatrixCodeAnimation.h" +#include "FireworkAnimation.h" diff --git a/include/Animation/AnimationController.h b/include/Animation/AnimationController.h index 1380d32..0f481fb 100644 --- a/include/Animation/AnimationController.h +++ b/include/Animation/AnimationController.h @@ -17,6 +17,7 @@ class AnimationController SNOWFALL = 2, FONT_TEST = 3, MATRIX_CODE = 4, + FIREWORK = 5, NUM_DEFAULT_ANIMATIONS }; @@ -26,7 +27,8 @@ class AnimationController "Cold Fire", "Snowfall", "Font Test", - "Matrix Code" + "Matrix Code", + "Fireworks" }; AnimationController(Fader *fader); diff --git a/include/Animation/FireworkAnimation.h b/include/Animation/FireworkAnimation.h new file mode 100644 index 0000000..ca0e9bf --- /dev/null +++ b/include/Animation/FireworkAnimation.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include + +#include "Animation.h" + +class FireworkRocket +{ + private: + enum LaunchPhase { + PHASE_RISING, + PHASE_SPREADING, + PHASE_DONE + }; + + std::array m_flareXPos; + std::array m_flareYPos; + + LaunchPhase m_phase; + + int m_rocketXPos; + int m_rocketYPos; + + int m_targetHeight; + int m_riseSpeed; + + uint64_t m_spreadStartFrame; + + Fader::Color m_color; + + public: + FireworkRocket(int start_x, int target_height, int rise_speed, const Fader::Color &color); + + void move(uint64_t frame); + void render(Fader *fader); + + bool done(void) const { return m_phase == PHASE_DONE; } +}; + +class FireworkAnimation : public Animation +{ + public: + FireworkAnimation(Fader *fader, int vspeed = 128, int spawn_rate = 8); + + void loop(uint64_t frame) override; + + void stop(void) override + { + m_stopping = true; + } + + void reset(void) override; + + private: + std::default_random_engine m_gen; + + std::list m_rockets; + int m_vspeed; + int m_spawnRate; // average pixels per second + + bool m_stopping; +}; diff --git a/src/Animation/AnimationController.cpp b/src/Animation/AnimationController.cpp index 784fa44..21831e1 100644 --- a/src/Animation/AnimationController.cpp +++ b/src/Animation/AnimationController.cpp @@ -53,6 +53,10 @@ void AnimationController::changeAnimation(AnimationController::DefaultAnimation changeAnimation(std::unique_ptr(new MatrixCodeAnimation(m_fader)), transition); break; + case FIREWORK: + changeAnimation(std::unique_ptr(new FireworkAnimation(m_fader, 64, 1)), transition); + break; + case FONT_TEST: { Bitmap bmp(0, 0); diff --git a/src/Animation/FireworkAnimation.cpp b/src/Animation/FireworkAnimation.cpp new file mode 100644 index 0000000..a4b457b --- /dev/null +++ b/src/Animation/FireworkAnimation.cpp @@ -0,0 +1,138 @@ +#include "Animation/FireworkAnimation.h" + +#include + +#include "fasttrigon.h" + +FireworkRocket::FireworkRocket(int start_x, int target_height, int rise_speed, const Fader::Color &color) + : m_phase(PHASE_RISING), m_rocketXPos(start_x*256), m_rocketYPos(0*256), m_targetHeight(target_height*256), m_riseSpeed(rise_speed), m_color(color) +{ +} + +void FireworkRocket::move(uint64_t frame) +{ + switch(m_phase) { + case PHASE_RISING: + if(m_rocketYPos < m_targetHeight) { + m_rocketYPos += m_riseSpeed; + } else { + for(size_t i = 0; i < m_flareXPos.size(); i++) { + m_flareXPos[i] = m_rocketXPos; + m_flareYPos[i] = m_rocketYPos; + } + + m_spreadStartFrame = frame; + m_phase = PHASE_SPREADING; + } + break; + + case PHASE_SPREADING: + for(size_t i = 0; i < m_flareXPos.size(); i++) { + int speedX = fasttrigon::fastcos(fasttrigon::LUT_SIZE / 12 + i * fasttrigon::LUT_SIZE / m_flareXPos.size()) / 2; + int speedY = fasttrigon::fastsin(fasttrigon::LUT_SIZE / 12 + i * fasttrigon::LUT_SIZE / m_flareXPos.size()) / 2; + + m_flareXPos[i] += speedX; + m_flareYPos[i] += speedY; + } + + if((frame - m_spreadStartFrame) >= 20) { + m_phase = PHASE_DONE; + } + break; + + case PHASE_DONE: + break; + + } +} + +void FireworkRocket::render(Fader *fader) +{ + uint32_t nled = fader->modules_per_strip(); + uint32_t nstrip = fader->strips(); + + switch(m_phase) { + case PHASE_RISING: + fader->add_color(m_rocketXPos/256, m_rocketYPos/256, Fader::Color{32, 16, 0, 0}); + break; + + case PHASE_SPREADING: + for(size_t i = 0; i < m_flareXPos.size(); i++) { + int x = (m_flareXPos[i]/256 + nstrip) % nstrip; + int y = (m_flareYPos[i]/256); + + if(y >= nled || y < 0) { + continue; + } + + fader->add_color(x, y, m_color); + } + break; + + case PHASE_DONE: + // nothing more + break; + } +} + +FireworkAnimation::FireworkAnimation(Fader *fader, int vspeed, int spawn_rate) + : Animation(fader), + m_vspeed(vspeed), + m_spawnRate(spawn_rate) +{ + reset(); +} + +void FireworkAnimation::loop(uint64_t frame) +{ + //int nled = m_fader->modules_per_strip(); + int nstrip = m_fader->strips(); + + if(frame == 0) { + m_fader->set_fadestep(16); + } + + m_fader->fade_color(Fader::Color{0,0,0,0}); + + // create new pixels + std::uniform_int_distribution spawnRNG(0, (60*nstrip / m_spawnRate) - 1); + std::uniform_int_distribution vspeedRNG(m_vspeed - 16, m_vspeed + 16); + + for(int i = 0; i < nstrip; i++) { + if(spawnRNG(m_gen) == 0) { + std::uniform_int_distribution stripRNG(0, nstrip-1); + std::uniform_int_distribution heightRNG(5, 13); + std::uniform_int_distribution colorRNG(0, 15); + + int16_t colorMask = colorRNG(m_gen); + + m_rockets.emplace_back(FireworkRocket{stripRNG(m_gen), heightRNG(m_gen), vspeedRNG(m_gen), + Fader::Color{ + 255 * ((colorMask >> 3) & 0x01), + 255 * ((colorMask >> 2) & 0x01), + 255 * ((colorMask >> 1) & 0x01), + 128 * ((colorMask >> 0) & 0x01) + }}); + } + } + + // move all pixels down and render them + for(auto &rocket: m_rockets) { + rocket.move(frame); + rocket.render(m_fader); + } + + m_rockets.remove_if( + [](const FireworkRocket &r) { return r.done(); }); + + m_fader->update(); + m_running = !m_stopping || m_fader->something_changed(); +} + +void FireworkAnimation::reset(void) +{ + m_rockets.clear(); + + m_stopping = false; + m_running = true; +}