From 368c5bec7243e3d67da009ac52266fb270993ad5 Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Thu, 5 Dec 2019 01:11:37 +0100 Subject: [PATCH] Added fire animation; better transitions --- include/Animation/AllAnimations.h | 1 + include/Animation/AnimationController.h | 5 + include/Animation/FireAnimation.h | 49 ++++++ include/Fader.h | 10 +- .../ConnectionEstablishedAnimation.cpp | 6 +- src/Animation/FireAnimation.cpp | 165 ++++++++++++++++++ src/main.cpp | 8 +- 7 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 include/Animation/FireAnimation.h create mode 100644 src/Animation/FireAnimation.cpp diff --git a/include/Animation/AllAnimations.h b/include/Animation/AllAnimations.h index 36ac683..b207c28 100644 --- a/include/Animation/AllAnimations.h +++ b/include/Animation/AllAnimations.h @@ -2,3 +2,4 @@ #include "ConnectingAnimation.h" #include "ConnectionEstablishedAnimation.h" +#include "FireAnimation.h" diff --git a/include/Animation/AnimationController.h b/include/Animation/AnimationController.h index 91ef412..27f1c81 100644 --- a/include/Animation/AnimationController.h +++ b/include/Animation/AnimationController.h @@ -14,6 +14,11 @@ class AnimationController void restart(void); + bool isIdle(void) + { + return m_animation->finished() && !m_nextAnimation; + } + private: Fader *m_fader; std::unique_ptr m_animation; diff --git a/include/Animation/FireAnimation.h b/include/Animation/FireAnimation.h new file mode 100644 index 0000000..c526122 --- /dev/null +++ b/include/Animation/FireAnimation.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include "Animation.h" + +class FireAnimation : public Animation +{ + public: + FireAnimation(Fader *fader, bool cold); + + void loop(uint64_t frame) override; + + void stop(void) override + { + m_stopping = true; + } + + void reset(void) override + { + m_stopping = false; + m_running = true; + } + + private: + static const constexpr unsigned MAX_NEW_ENERGY = 160; + static const constexpr unsigned RM_ENERGY = 7; + static const constexpr unsigned MAX_PULL_ENERGY_PCT = 100; + + std::vector m_energy; + std::vector m_energySmooth; + + std::vector m_colorMapIndices; + std::vector m_colorMapColors; + + std::default_random_engine m_gen; + + bool m_stopping; + uint32_t m_intensity; + + uint8_t interpolate(uint32_t energy, std::size_t start_x, std::size_t end_x, uint8_t start_c, uint8_t end_c); + void colormap(uint32_t energy, Fader::Color *color); + + uint32_t idx(uint32_t strip, uint32_t module) + { + return strip * m_fader->modules_per_strip() + module; + } +}; diff --git a/include/Fader.h b/include/Fader.h index 9a496dd..3c6ddd5 100644 --- a/include/Fader.h +++ b/include/Fader.h @@ -25,6 +25,14 @@ class Fader if(b > 255) { b = 255; } else if (b < 0) { b = 0; }; if(w > 255) { w = 255; } else if (w < 0) { w = 0; }; } + + void gamma_correct(void) + { + r = r * r / 255; + g = g * g / 255; + b = b * b / 255; + w = w * w / 255; + } }; @@ -73,4 +81,4 @@ class Fader std::vector m_curColor; std::vector m_targetColor; -}; \ No newline at end of file +}; diff --git a/src/Animation/ConnectionEstablishedAnimation.cpp b/src/Animation/ConnectionEstablishedAnimation.cpp index 7c1d3a3..caf9be5 100644 --- a/src/Animation/ConnectionEstablishedAnimation.cpp +++ b/src/Animation/ConnectionEstablishedAnimation.cpp @@ -37,7 +37,11 @@ void ConnectionEstablishedAnimation::loop(uint64_t frame) m_fader->fade_color(strip, led, Fader::Color{0,0,0,0}); } } - } else { + } + + m_fader->update(); + + if(led >= nled) { // stop the animation if everything is dark m_running = m_fader->something_changed(); } diff --git a/src/Animation/FireAnimation.cpp b/src/Animation/FireAnimation.cpp new file mode 100644 index 0000000..82129a5 --- /dev/null +++ b/src/Animation/FireAnimation.cpp @@ -0,0 +1,165 @@ +#include "Animation/FireAnimation.h" + +FireAnimation::FireAnimation(Fader *fader, bool cold) + : Animation(fader), + m_energy(fader->modules_per_strip() * fader->strips()), + m_energySmooth(fader->modules_per_strip() * fader->strips()), + m_stopping(false) +{ + m_colorMapIndices.assign({0, 70, 120, 200, 225, 255}); + + if(cold) { + m_colorMapColors.emplace_back(Fader::Color{ 0, 0, 0, 0}); + m_colorMapColors.emplace_back(Fader::Color{ 0, 0, 128, 0}); + m_colorMapColors.emplace_back(Fader::Color{ 0, 64, 192, 0}); + m_colorMapColors.emplace_back(Fader::Color{ 0, 128, 255, 0}); + m_colorMapColors.emplace_back(Fader::Color{ 0, 128, 255, 20}); + m_colorMapColors.emplace_back(Fader::Color{ 0, 128, 255, 40}); + } else { + m_colorMapColors.emplace_back(Fader::Color{ 0, 0, 0, 0}); + m_colorMapColors.emplace_back(Fader::Color{128, 0, 0, 0}); + m_colorMapColors.emplace_back(Fader::Color{192, 64, 0, 0}); + m_colorMapColors.emplace_back(Fader::Color{255, 128, 0, 0}); + m_colorMapColors.emplace_back(Fader::Color{255, 128, 0, 20}); + m_colorMapColors.emplace_back(Fader::Color{255, 128, 0, 40}); + } + + m_fader->set_fadestep(10); +} + +uint8_t FireAnimation::interpolate(uint32_t energy, std::size_t start_x, std::size_t end_x, uint8_t start_c, uint8_t end_c) +{ + int ien = static_cast(energy); + int isx = static_cast(start_x); + int iex = static_cast(end_x); + int isc = static_cast(start_c); + int iec = static_cast(end_c); + + int result = (ien - isx) * 128 / (iex - isx) * (iec - isc) / 128 + isc; + + return static_cast(result); +} + +void FireAnimation::colormap(uint32_t energy, Fader::Color *color) +{ + std::size_t i = 0; + + // find the index on the left side of the interpolation range + while((i < m_colorMapIndices.size()-1) && (m_colorMapIndices[i+1] < energy)) { + i++; + } + + // if the index found is the last index of the list, no interpolation is + // possible -> return the last color + if(i >= m_colorMapIndices.size()-1) { + *color = *(m_colorMapColors.end()-1); + return; + } + + // interpolate the color map + std::size_t start_idx = m_colorMapIndices[i]; + std::size_t end_idx = m_colorMapIndices[i+1]; + + Fader::Color &start_color = m_colorMapColors[i]; + Fader::Color &end_color = m_colorMapColors[i+1]; + + color->r = interpolate(energy, start_idx, end_idx, start_color.r, end_color.r); + color->g = interpolate(energy, start_idx, end_idx, start_color.g, end_color.g); + color->b = interpolate(energy, start_idx, end_idx, start_color.b, end_color.b); + color->w = interpolate(energy, start_idx, end_idx, start_color.w, end_color.w); + + color->gamma_correct(); +} + +void FireAnimation::loop(uint64_t frame) +{ + std::size_t nled = m_fader->modules_per_strip(); + std::size_t nstrip = m_fader->strips(); + + if(!m_stopping && (frame % 10 == 0)) { + std::uniform_int_distribution m_intensityRNG(MAX_NEW_ENERGY*3/4, MAX_NEW_ENERGY*5/4); + m_intensity = m_intensityRNG(m_gen); + } else if(m_stopping) { + m_intensity = 0; + } + + if((frame % 2) == 0) { + // inject random amount of energy in bottom row + std::uniform_int_distribution m_newEnergyRNG(0, m_intensity); + + for(std::size_t strip = 0; strip < nstrip; strip++) { + m_energy[idx(strip, 0)] += m_newEnergyRNG(m_gen); + } + + // pull energy from cell below + std::uniform_int_distribution m_pullEnergyPctRNG(0, MAX_PULL_ENERGY_PCT); + + for(std::size_t strip = 0; strip < nstrip; strip++) { + for(std::size_t led = nled-1; led > 0; led--) { + uint32_t &energy = m_energy[idx(strip,led)]; + uint32_t &energy_below = m_energy[idx(strip,led-1)]; + + uint32_t pulled_energy = m_pullEnergyPctRNG(m_gen) * energy_below / 100; + + energy += pulled_energy; + energy_below -= pulled_energy; + } + } + + // decrease global amount of energy + for(auto &energy: m_energy) { + if(energy >= RM_ENERGY) { + energy -= RM_ENERGY; + } else { + energy = 0; + } + } + + // smooth the energy distribution and update the colors + for(std::size_t strip = 0; strip < nstrip; strip++) { + for(std::size_t led = 0; led < nled; led++) { + uint32_t strip_left = (strip + nstrip - 1) % nstrip; + uint32_t strip_right = (strip + nstrip + 1) % nstrip; + + int32_t led_above = led + 1; + int32_t led_below = led - 1; + + uint32_t &energy_smooth = m_energySmooth[idx(strip, led)]; + + energy_smooth = 100 * m_energy[idx(strip, led)]; + energy_smooth += 30 * m_energy[idx(strip_left, led)]; + energy_smooth += 30 * m_energy[idx(strip_right, led)]; + + uint32_t gain = 160; + + if(led_above < nled) { + energy_smooth += 10 * m_energy[idx(strip, led_above)]; + gain += 10; + } + + if(led_below >= 0) { + energy_smooth += 10 * m_energy[idx(strip, led_below)]; + gain += 10; + } + + energy_smooth /= gain; + } + } + + // update colors + Fader::Color color; + + for(std::size_t strip = 0; strip < nstrip; strip++) { + for(std::size_t led = 0; led < nled; led++) { + colormap(m_energySmooth[idx(strip, led)], &color); + m_fader->fade_color(strip, led, color); + } + } + } + + m_fader->update(); + + if(m_stopping && !m_fader->something_changed()) { + m_running = false; + } +} diff --git a/src/main.cpp b/src/main.cpp index 41ceaa3..5c39a94 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,6 +96,10 @@ static void ledTask( void * parameter ) animController.loop(); + if(animController.isIdle()) { + animController.changeAnimation(std::unique_ptr(new FireAnimation(&ledFader, false))); + } + if((WiFi.status() == WL_CONNECTED) || (WiFi.getMode() == WIFI_MODE_AP)) { udpProto.loop(); } @@ -111,7 +115,9 @@ static void ledTask( void * parameter ) strands[i] = &STRANDS[i]; } - ledFader.update(); + if(animController.isIdle()) { + ledFader.update(); + } if(ledFader.something_changed()) { const std::vector &colors = ledFader.get_color_values();