//------------ Эффект "За окном идет дождь"
// (c) ldir (Soulmate)
class EffectCRain : public EffectCalc
{
private:
byte rain[NUM_LEDS];
byte counter = 1;
int8_t _speed = 1;
float hue;
bool storm = false;
bool clouds = false;
void setDynCtrl(UIControl*_val) override;
void changepattern()
{
int rand1 = random16(NUM_LEDS);
int rand2 = random16(NUM_LEDS);
if ((rain[rand1] == 1) && (rain[rand2] == 0))
{ //simple get two random dot 1 and 0 and swap it, this will not change total number of dots
rain[rand1] = 0;
rain[rand2] = 1;
}
}
void raininit(byte rain[NUM_LEDS])
{ //init array of dots. run once
for (int i = 0; i < NUM_LEDS; i++)
{
if (random8(18) == 0)
{
rain[i] = 1;
}
else
{
rain[i] = 0;
} //random8(20) number of dots. decrease for more dots
}
}
void updaterain(CRGB *leds, float speedFactor);
bool crainRoutine(CRGB *leds, EffectWorker *param);
//void setDynCtrl(UIControl*_val) override;
public:
bool run(CRGB *ledarr, EffectWorker *opt = nullptr) override;
};
//------------ Эффект "За окном идет дождь..."
// (c) ldir (Soulmate) переделан kostyamat
void EffectCRain::setDynCtrl(UIControl*_val)
{
EffectCalc::setDynCtrl(_val); // сначала дергаем базовый, чтобы была обработка палитр/микрофона (если такая обработка точно не нужна, то можно не вызывать базовый метод)
if(_val->getId()==3) // Тучка
clouds = _val->getVal() == "true";
if(_val->getId()==4) // Молния
storm = _val->getVal() == "true";
}
bool EffectCRain::run(CRGB *ledarr, EffectWorker *opt ) {
return crainRoutine(*&ledarr, &*opt);
}
void EffectCRain::updaterain(CRGB *leds, float speedFactor)
{
byte sat = beatsin8(30, 150, 255);
for (byte i = 0; i < WIDTH; i++)
{
for (float j = 0.; j < ((float)HEIGHT - (clouds ? 4.5 : 1.)); j += speedFactor)
{
//byte layer = rain[XY(i, (((uint8_t)j + _speed + random8(2)) % HEIGHT))]; //fake scroll based on shift coordinate
byte layer = rain[myLamp.getPixelNumber(i, (((uint8_t)j + _speed + random8(2)) % HEIGHT))]; //fake scroll based on shift coordinate
if (layer)
{
myLamp.drawPixelXYF_Y(i, j, CHSV(scale == 255 ? 144 : hue, scale == 255 ? 96 : sat, scale ==255 ? sat-50: 220));
//leds[XY(i, j)] = CHSV(100, 255, BRIGHTNESS);
} //random8(2) add glitchy effect
}
}
_speed++;
fadeToBlackBy(leds, NUM_LEDS, scale < 255 ? 35: 20);
}
bool EffectCRain::crainRoutine(CRGB *leds, EffectWorker *param) {
float speedfactor = EffectMath::fmap((float)speed, 1., 255., 0.1, 0.9);
EVERY_N_MILLISECONDS(5) {
changepattern();
}
if (counter)
{
raininit(rain);
counter = 0;
} //init array of dots. run once
if (millis() - lastrun > 40) {
if (scale != 1)
hue = scale;
else
hue += 0.5;
updaterain(*&leds, speedfactor);
lastrun = millis();
}
if (clouds)
EffectMath::Clouds(2, storm ? EffectMath::Lightning(CHSV(30,90,255), 255U) : false);
else if (storm) EffectMath::Lightning(CHSV(30,90,255), 255U);
return true;
}
// Зависимости
bool EffectMath::Lightning(CRGB lightningColor, uint8_t chanse)
{
//uint8_t lightning[WIDTH][HEIGHT];
// ESP32 does not like static arrays https://github.com/espressif/arduino-esp32/issues/2567
if (random16() < chanse)
{
uint8_t *lightning = (uint8_t *)malloc(WIDTH * HEIGHT); // Odds of a lightning bolt
lightning[scale8(random8(), WIDTH - 1) + (HEIGHT - 1) * WIDTH] = 255; // Random starting location
for (uint8_t ly = HEIGHT - 1; ly > 1; ly--)
{
for (uint8_t lx = 1; lx < WIDTH - 1; lx++)
{
if (lightning[lx + ly * WIDTH] == 255)
{
lightning[lx + ly * WIDTH] = 0;
uint8_t dir = random8(4);
switch (dir)
{
case 0:
EffectMath::setLed(myLamp.getPixelNumber(lx + 1, ly - 1), lightningColor);
lightning[(lx + 1) + (ly - 1) * WIDTH] = 255; // move down and right
break;
case 1:
EffectMath::setLed(myLamp.getPixelNumber(lx, ly - 1), CRGB(128, 128, 128)); // я без понятия, почему у верхней молнии один оттенок, а у остальных - другой
lightning[lx + (ly - 1) * WIDTH] = 255; // move down
break;
case 2:
EffectMath::setLed(myLamp.getPixelNumber(lx - 1, ly - 1), CRGB(128, 128, 128));
lightning[(lx - 1) + (ly - 1) * WIDTH] = 255; // move down and left
break;
case 3:
EffectMath::setLed(myLamp.getPixelNumber(lx - 1, ly - 1), CRGB(128, 128, 128));
lightning[(lx - 1) + (ly - 1) * WIDTH] = 255; // fork down and left
EffectMath::setLed(myLamp.getPixelNumber(lx - 1, ly - 1), CRGB(128, 128, 128));
lightning[(lx + 1) + (ly - 1) * WIDTH] = 255; // fork down and right
break;
}
}
}
}
free(lightning);
return true;
}
return false;
}
void EffectMath::Clouds(uint8_t rhue, bool flash)
{
#ifdef SMARTMATRIX
const CRGBPalette16 rainClouds_p(CRGB::Black, CRGB(75, 84, 84), CRGB(49, 75, 75), CRGB::Black);
#else
const CRGBPalette16 rainClouds_p(CRGB::Black, CRGB(35, 44, 44), CRGB(29, 35, 35), CRGB::Black);
#endif
//uint32_t random = millis();
uint8_t dataSmoothing = 50; //196
uint16_t noiseX = beatsin16(1, 10, 4000, 0, 150);
uint16_t noiseY = beatsin16(1, 1000, 10000, 0, 50);
uint16_t noiseZ = beatsin16(1, 10, 4000, 0, 100);
uint16_t noiseScale = 50; // A value of 1 will be so zoomed in, you'll mostly see solid colors. A value of 4011 will be very zoomed out and shimmery
const uint16_t cloudHeight = (HEIGHT * 0.2) + 1;
// This is the array that we keep our computed noise values in
//static uint8_t noise[WIDTH][cloudHeight];
static uint8_t *noise = (uint8_t *)malloc(WIDTH * cloudHeight);
for (uint8_t x = 0; x < WIDTH; x++)
{
int xoffset = noiseScale * x + rhue;
for (int z = 0; z < cloudHeight; z++)
{
int yoffset = noiseScale * z - rhue;
uint8_t noiseData = qsub8(inoise8(noiseX + xoffset, noiseY + yoffset, noiseZ), 16);
noiseData = qadd8(noiseData, scale8(noiseData, 39));
noise[x * cloudHeight + z] = scale8(noise[x * cloudHeight + z], dataSmoothing) + scale8(noiseData, 256 - dataSmoothing);
if (flash)
EffectMath::drawPixelXY(x, HEIGHT - z - 1, CHSV(random8(20,30), 250, random8(64, 100)));
else
nblend(myLamp.getUnsafeLedsArray()[myLamp.getPixelNumber(x, HEIGHT - z - 1)], ColorFromPalette(rainClouds_p, noise[x * cloudHeight + z]), (250 / cloudHeight));
}
noiseZ++;
}
if (flash) {
for (uint16_t i = 0; i < WIDTH; i++)
{
for (byte z = 0; z < 10; z++)
EffectMath::drawPixelXYF(i, EffectMath::randomf((float)HEIGHT - 4., (float)HEIGHT - 1.), CHSV(0, 250, random8(96, 120)));
}
EffectMath::blur2d(100);
}
}
void EffectMath::drawPixelXYF_Y(uint16_t x, float y, const CRGB &color, uint8_t darklevel)
{
if (x<0 || y<0 || x>((float)WIDTH) || y>((float)HEIGHT)) return;
// extract the fractional parts and derive their inverses
uint8_t yy = (y - (int)y) * 255, iy = 255 - yy;
// calculate the intensities for each affected pixel
uint8_t wu[2] = {iy, yy};
// multiply the intensities by the colour, and saturating-add them to the pixels
for (int8_t i = 1; i >= 0; i--) {
int16_t yn = y + (i & 1);
CRGB clr = EffectMath::getPixColorXY(x, yn);
if(yn>0 && yn<(int)HEIGHT-1){
clr.r = qadd8(clr.r, (color.r * wu[i]) >> 8);
clr.g = qadd8(clr.g, (color.g * wu[i]) >> 8);
clr.b = qadd8(clr.b, (color.b * wu[i]) >> 8);
} else if(yn==0 || yn==(int)HEIGHT-1) {
clr.r = qadd8(clr.r, (color.r * 85) >> 8);
clr.g = qadd8(clr.g, (color.g * 85) >> 8);
clr.b = qadd8(clr.b, (color.b * 85) >> 8);
}
EffectMath::drawPixelXY(x, yn, EffectMath::makeDarker(clr, darklevel));
}
}