esp32-sk6812/src/Animation/SnowfallAnimation.cpp

192 lines
5.1 KiB
C++

#include <algorithm>
#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 + FASTTRIGON_8BIT(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<int16_t>(32*brightness/256),
static_cast<int16_t>(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<uint32_t> spawnRNG(0, 999);
if(spawnRNG(m_gen) < 50) {
std::uniform_int_distribution<int32_t> phaseRNG(0, fasttrigon::LUT_SIZE-1);
std::uniform_int_distribution<int32_t> vertSpeedRNG(13, 38); // 0.05..0.15
std::uniform_int_distribution<int32_t> 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<int16_t>(8 * brightness / 65535),
static_cast<int16_t>(32 * brightness / 65535),
static_cast<int16_t>(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;
}