Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2D Effect Snow Fall #147

Merged
merged 7 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5381,6 +5381,117 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
} // mode_2Dgameoflife()
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay BG ☾,Wrap ☾;!,!;!;2;sx=56,ix=2,c1=128,o1=0,o2=0,o3=1";

/////////////////////////
// 2D SnowFall //
/////////////////////////

uint16_t mode_2DSnowFall(void) { // By: Brandon Butler
// Uses Game of Life style bit array to track snow/particles
if (!strip.isMatrix) return mode_static(); // Not a 2D set-up
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
const size_t dataSize = (SEGMENT.length() + 7) / 8; // Round up to nearest byte

if (!SEGENV.allocateData(dataSize)) return mode_static(); // Allocation failed
byte *grid = reinterpret_cast<byte*>(SEGENV.data);

bool overlay = SEGMENT.check2; // Overlay is inverted. Only draws non-snow. Layer 1 controls snow color
uint32_t bgColor = SEGCOLOR(1);

if (SEGENV.call == 0) {
SEGMENT.setUpLeds();
SEGMENT.fill(bgColor);
SEGENV.aux0 = 0; // Overflow value
memset(grid, 0, dataSize);
}

// Draw non snow for inverted overlay
if (overlay) {
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
if (!getBitValue(grid, y * cols + x)) SEGMENT.setPixelColorXY(x, y, bgColor);
}
}

uint8_t speed = map(SEGMENT.speed, 0, 255, 0, 60); // Updates per second
if (!speed || strip.now - SEGENV.step < 1000 / speed) return FRAMETIME; // Not enough time passed

uint8_t blur = map(SEGMENT.custom2, 0, 255, 255, 0);
uint8_t sway = SEGMENT.custom3;

// Despawn snow
bool overflow = SEGENV.aux0 && SEGMENT.check3;
int despawnChance = SEGMENT.custom1 == 255 ? 256 : map(SEGMENT.custom1, 0, 255, 0, 100); // 255 goes to 256, allows always despawn
int lastY = rows - 1;
for (int x = 0; x < cols; x++) {
if (overflow || random8() < despawnChance) setBitValue(grid, lastY * cols + x, 0);
if (overlay || getBitValue(grid, lastY * cols + x)) continue; // Skip drawing if inverted overlay or snow
SEGMENT.blendPixelColorXY(x, lastY, bgColor, blur);
}
if (SEGENV.aux0) --SEGENV.aux0; // Decrease overflow

// Precompute shuffled indices, helps randomize snow movement
uint16_t shuffledIndices[cols];
for (int i = 0; i < cols; i++) shuffledIndices[i] = i;
std::random_shuffle(shuffledIndices, shuffledIndices + cols);

// Update snow, loop from 2nd bottom row to top with precomputed random order
for (int y = rows - 2; y >= 0; y--) {
for (int i = 0; i < cols; i++) {
int x = shuffledIndices[i];

int pos = XY(x, y);

uint32_t xyColor = SEGMENT.getPixelColorXY(x, y); // Limit getPixelColorXY calls
if (!getBitValue(grid, pos)) { // No snow, fade if needed and skip
if (!overlay && blur) SEGMENT.setPixelColorXY(x, y, color_blend(xyColor, bgColor, blur));
continue;
}

int newX = x, newY = y + 1;
int newPos = XY(newX, newY);
// Open Position Booleans
bool down = !getBitValue(grid, newPos);
bool downLeft = x > 0 && !getBitValue(grid, newPos - 1);
bool downRight = x < cols - 1 && !getBitValue(grid, newPos + 1);

if (!down) {
if (downLeft && downRight) newX = random8(2) ? x - 1 : x + 1;
else if (downLeft) newX = x - 1;
else if (downRight) newX = x + 1;
else newY = y; // Snow is stuck
}
else if (sway && random8(30) < sway) { // Sway falling snow if horizontal and diagonal directions are open
if (x % 2 == 1 && downLeft && !getBitValue(grid, pos - 1)) newX = x - 1; // Odd Columns Move Left
else if (x % 2 == 0 && downRight && !getBitValue(grid, pos + 1)) newX = x + 1; // Even Columns Move Right
}

if (newY != y || newX != x) { // Snow moved
setBitValue(grid, pos, 0); // Clear old
setBitValue(grid, XY(newX, newY), 1); // Set new
if (!overlay) SEGMENT.setPixelColorXY(x, y, color_blend(xyColor, bgColor, blur)); // Fade old
}
if (!overlay) SEGMENT.setPixelColorXY(newX, newY, xyColor); // Draw new / redraw stuck
}
}

// Spawn snow
int spawnChance = map(SEGMENT.intensity, 0, 255, 0, 100);
for (int x = 0; x < cols; x++) { // y = 0
if (random8() >= spawnChance) continue;
if (getBitValue(grid, x)) {SEGENV.aux0 = rows; continue;} // Snow exists, overflowing

setBitValue(grid, x, 1); // Spawn snow
if (overlay) continue; // Skip drawing if inverted overlay

if (SEGMENT.check1) SEGMENT.setPixelColorXY(x, 0, ColorFromPalette(SEGPALETTE, random8())); // Use palette
else {int c = random8(120,200); SEGMENT.setPixelColorXY(x, 0, c, c, c);} // Use snow color
}

SEGENV.step = strip.now;
return FRAMETIME;
} // mode_2DSnowFall()
static const char _data_FX_MODE_2DSNOWFALL[] PROGMEM = "Snow Fall ☾@!,Spawn Rate,Despawn Rate,Blur,Sway Chance,Use Palette,Inverted Overlay,Prevent Overflow,;!,!;!;2;sx=128,ix=16,c1=17,c2=0,c3=0,o1=0,o2=0,o3=1";

/////////////////////////
// 2D Hiphotic //
/////////////////////////
Expand Down Expand Up @@ -8777,6 +8888,7 @@ void WS2812FX::setupEffectData() {
addEffect(FX_MODE_2DSOAP, &mode_2Dsoap, _data_FX_MODE_2DSOAP);
addEffect(FX_MODE_2DOCTOPUS, &mode_2Doctopus, _data_FX_MODE_2DOCTOPUS);
addEffect(FX_MODE_2DWAVINGCELL, &mode_2Dwavingcell, _data_FX_MODE_2DWAVINGCELL);
addEffect(FX_MODE_2DSNOWFALL, &mode_2DSnowFall, _data_FX_MODE_2DSNOWFALL);

addEffect(FX_MODE_2DAKEMI, &mode_2DAkemi, _data_FX_MODE_2DAKEMI); // audio

Expand Down
5 changes: 3 additions & 2 deletions wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,9 @@ bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented
// #define FX_MODE_PALETTE_AR 193 // WLED-SR audioreactive palette
#define FX_MODE_FIREWORKS_AR 194 // WLED-SR audioreactive fireworks
#define FX_MODE_GEQLASER 195 // WLED-MM GEQ Laser
#define FX_MODE_2DPAINTBRUSH 196 // WLED-MM GEQ Laser
#define MODE_COUNT 197
#define FX_MODE_2DPAINTBRUSH 196 // WLED-MM Paintbrush
#define FX_MODE_2DSNOWFALL 197 // WLED-MM Snowfall
#define MODE_COUNT 198

typedef enum mapping1D2D {
M12_Pixels = 0,
Expand Down