Added new 2D effects:

- 2D Floating Blobs
- 2D Ghost Rider
Crash fix for 2D Game of Life.
Added support function fill_circle() & wu_pixel().
Removed obsolete debug strings.
This commit is contained in:
Blaz Kristan 2022-05-20 19:35:22 +02:00
parent ba3555a66f
commit b4d6525899
8 changed files with 242 additions and 14 deletions

View File

@ -4562,11 +4562,11 @@ uint16_t WS2812FX::mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired
if (!SEGENV.allocateData(dataSize*2)) return mode_static(); //allocation failed
CRGB *leds = reinterpret_cast<CRGB*>(SEGENV.data);
CRGB *prevLeds = reinterpret_cast<CRGB*>(SEGENV.data) + dataSize;
CRGB *prevLeds = reinterpret_cast<CRGB*>(SEGENV.data + dataSize);
CRGB backgroundColor = SEGCOLOR(1);
if (SEGENV.call == 0 || now - resetMillis > 20000) {
if (SEGENV.call == 0 || now - resetMillis > 5000) {
resetMillis = now;
random16_set_seed(now); //seed the random generator
@ -4632,7 +4632,8 @@ uint16_t WS2812FX::mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired
} //x,y
// calculate CRC16 of leds[]
uint16_t crc = crc16((const unsigned char*)leds, dataSize);
uint16_t crc = crc16((const unsigned char*)leds, dataSize-1);
// check if we had same CRC and reset if needed
if (!(crc == SEGENV.aux0 || crc == SEGENV.aux1)) resetMillis = now; //if no repetition avoid reset
// remeber last two
@ -5533,3 +5534,193 @@ uint16_t WS2812FX::mode_2Dcrazybees(void) {
setPixels(leds);
return FRAMETIME;
}
/////////////////////////
// 2D Ghost Rider //
/////////////////////////
//// Ghost Rider by stepko (c)2021 [https://editor.soulmatelights.com/gallery/716-ghost-rider], adapted by Blaz Kristan
#define LIGHTERS_AM 64 // max lighters (adequate for 32x32 matrix)
uint16_t WS2812FX::mode_2Dghostrider(void) {
if (!isMatrix) return mode_static(); // not a 2D set-up
uint16_t width = SEGMENT.virtualWidth();
uint16_t height = SEGMENT.virtualHeight();
uint16_t dataSize = sizeof(CRGB) * width * height;
typedef struct Lighter {
int gPosX;
int gPosY;
uint16_t gAngle;
int8_t angleSpeed;
uint16_t lightersPosX[LIGHTERS_AM];
uint16_t lightersPosY[LIGHTERS_AM];
uint16_t Angle[LIGHTERS_AM];
uint16_t time[LIGHTERS_AM];
bool reg[LIGHTERS_AM];
int8_t Vspeed;
} lighter_t;
if (!SEGENV.allocateData(dataSize + sizeof(lighter_t))) return mode_static(); //allocation failed
CRGB *leds = reinterpret_cast<CRGB*>(SEGENV.data);
lighter_t *lighter = reinterpret_cast<lighter_t*>(SEGENV.data + dataSize);
if (SEGENV.call == 0) {
fill_solid(leds, CRGB::Black);
randomSeed(now);
lighter->angleSpeed = random(-10, 10);
lighter->Vspeed = 5;
lighter->gPosX = (width/2) * 10;
lighter->gPosY = (height/2) * 10;
for (byte i = 0; i < LIGHTERS_AM; i++) {
lighter->lightersPosX[i] = lighter->gPosX;
lighter->lightersPosY[i] = lighter->gPosY + i;
lighter->time[i] = i * 2;
}
}
fadeToBlackBy(leds, SEGMENT.speed>>2);
CRGB color = CRGB::White;
wu_pixel(leds, lighter->gPosX * 256 / 10, lighter->gPosY * 256 / 10, color);
lighter->gPosX += lighter->Vspeed * sin_t(radians(lighter->gAngle));
lighter->gPosY += lighter->Vspeed * cos_t(radians(lighter->gAngle));
lighter->gAngle += lighter->angleSpeed;
if (lighter->gPosX < 0) lighter->gPosX = (width - 1) * 10;
if (lighter->gPosX > (width - 1) * 10) lighter->gPosX = 0;
if (lighter->gPosY < 0) lighter->gPosY = (height - 1) * 10;
if (lighter->gPosY > (height - 1) * 10) lighter->gPosY = 0;
for (byte i = 0; i < LIGHTERS_AM; i++) {
lighter->time[i] += random8(5, 20);
if (lighter->time[i] >= 255 ||
(lighter->lightersPosX[i] <= 0) ||
(lighter->lightersPosX[i] >= (width - 1) * 10) ||
(lighter->lightersPosY[i] <= 0) ||
(lighter->lightersPosY[i] >= (height - 1) * 10)) {
lighter->reg[i] = true;
}
if (lighter->reg[i]) {
lighter->lightersPosY[i] = lighter->gPosY;
lighter->lightersPosX[i] = lighter->gPosX;
lighter->Angle[i] = lighter->gAngle + random(-10, 10);
lighter->time[i] = 0;
lighter->reg[i] = false;
} else {
lighter->lightersPosX[i] += -7 * sin_t(radians(lighter->Angle[i]));
lighter->lightersPosY[i] += -7 * cos_t(radians(lighter->Angle[i]));
}
wu_pixel(leds, lighter->lightersPosX[i] * 256 / 10, lighter->lightersPosY[i] * 256 / 10, ColorFromPalette(currentPalette, (256 - lighter->time[i])));
}
blur2d(leds, SEGMENT.intensity>>3);
setPixels(leds);
return FRAMETIME;
}
////////////////////////////
// 2D Floating Blobs //
////////////////////////////
//// Floating Blobs by stepko (c)2021 [https://editor.soulmatelights.com/gallery/573-blobs], adapted by Blaz Kristan
#define MAX_BLOBS 8
uint16_t WS2812FX::mode_2Dfloatingblobs(void) {
if (!isMatrix) return mode_static(); // not a 2D set-up
uint16_t width = SEGMENT.virtualWidth();
uint16_t height = SEGMENT.virtualHeight();
uint16_t dataSize = sizeof(CRGB) * width * height;
typedef struct Blob {
float x[MAX_BLOBS], y[MAX_BLOBS];
float sX[MAX_BLOBS], sY[MAX_BLOBS]; // speed
float r[MAX_BLOBS];
bool grow[MAX_BLOBS];
byte color[MAX_BLOBS];
} blob_t;
uint8_t Amount = (SEGMENT.intensity>>5) + 1; // NOTE: be sure to update MAX_BLOBS if you change this
if (!SEGENV.allocateData(dataSize + sizeof(blob_t))) return mode_static(); //allocation failed
CRGB *leds = reinterpret_cast<CRGB*>(SEGENV.data);
blob_t *blob = reinterpret_cast<blob_t*>(SEGENV.data + dataSize);
if (SEGENV.call == 0) {
fill_solid(leds, CRGB::Black);
DEBUG_PRINT(F("sizeof blob "));
DEBUG_PRINTLN(sizeof(blob_t));
DEBUG_PRINTLN(F("Init begin."));
for (byte i = 0; i < MAX_BLOBS; i++) {
blob->r[i] = random8(1, width/4);
blob->sX[i] = (float) random8(5, 11) / (float)(257 - SEGMENT.speed) / 4.0; // speed x
blob->sY[i] = (float) random8(5, 11) / (float)(257 - SEGMENT.speed) / 4.0; // speed y
blob->x[i] = random8(0, width-1);
blob->y[i] = random8(0, height-1);
blob->color[i] = random8();
blob->grow[i] = (blob->r[i] < 1.);
if (blob->sX[i] == 0) blob->sX[i] = 1;
if (blob->sY[i] == 0) blob->sY[i] = 1;
}
DEBUG_PRINTLN(F("Init done."));
}
fadeToBlackBy(leds, 20);
// Bounce balls around
for (byte i = 0; i < Amount; i++) {
// change radius if needed
if (blob->grow[i]) {
// enlarge radius until it is >= 4
blob->r[i] += (fabs(blob->sX[i]) > fabs(blob->sY[i]) ? fabs(blob->sX[i]) : fabs(blob->sY[i])) * 0.05;
if (blob->r[i] >= MIN(width/4,4.)) {
blob->grow[i] = false;
}
} else {
// reduce radius until it is < 1
blob->r[i] -= (fabs(blob->sX[i]) > fabs(blob->sY[i]) ? fabs(blob->sX[i]) : fabs(blob->sY[i])) * 0.05;
if (blob->r[i] < 1.) {
blob->grow[i] = true;
blob->color[i] = random(0, 255);
}
}
if (blob->r[i] > 1.)
fill_circle(leds, blob->y[i], blob->x[i], blob->r[i], ColorFromPalette(currentPalette, blob->color[i]));
else
leds[XY(blob->y[i], blob->x[i])] += ColorFromPalette(currentPalette, blob->color[i]);
//----------------------
if (blob->x[i] + blob->r[i] >= width - 1)
blob->x[i] += (blob->sX[i] * ((width - 1 - blob->x[i]) / blob->r[i] + 0.005));
else if (blob->x[i] - blob->r[i] <= 0)
blob->x[i] += (blob->sX[i] * (blob->x[i] / blob->r[i] + 0.005));
else
blob->x[i] += blob->sX[i];
//-----------------------
if (blob->y[i] + blob->r[i] >= height - 1)
blob->y[i] += (blob->sY[i] * ((height - 1 - blob->y[i]) / blob->r[i] + 0.005));
else if (blob->y[i] - blob->r[i] <= 0)
blob->y[i] += (blob->sY[i] * (blob->y[i] / blob->r[i] + 0.005));
else
blob->y[i] += blob->sY[i];
//------------------------
if (blob->x[i] < 0.01) {
blob->sX[i] = (float) random8(5, 11) / (257 - SEGMENT.speed) / 4.0;
blob->x[i] = 0.01;
} else if (blob->x[i] > width - 1.01) {
blob->sX[i] = (float) random8(5, 11) / (257 - SEGMENT.speed) / 4.0;
blob->sX[i] = -blob->sX[i];
blob->x[i] = width - 1.01;
}
//----------------------
if (blob->y[i] < 0.01) {
blob->sY[i] = (float) random8(5, 11) / (257 - SEGMENT.speed) / 4.0;
blob->y[i] = 0.01;
} else if (blob->y[i] > height - 1.01) {
blob->sY[i] = (float) random8(5, 11) / (257 - SEGMENT.speed) / 4.0;
blob->sY[i] = -blob->sY[i];
blob->y[i] = height - 1.01;
}
}
blur2d(leds, 16);
setPixels(leds);
return FRAMETIME;
}
#undef MAX_BLOBS

View File

@ -263,8 +263,10 @@
#define FX_MODE_WAVERLY 140
#define FX_MODE_SPACESHIPS 141
#define FX_MODE_CRAZYBEES 142
#define FX_MODE_GHOST_RIDER 143
#define FX_MODE_BLOBS 144
#define MODE_COUNT 143
#define MODE_COUNT 145
class WS2812FX {
@ -654,6 +656,8 @@ class WS2812FX {
_mode[FX_MODE_AKEMI] = &WS2812FX::mode_2DAkemi;
_mode[FX_MODE_SPACESHIPS] = &WS2812FX::mode_2Dspaceships;
_mode[FX_MODE_CRAZYBEES] = &WS2812FX::mode_2Dcrazybees;
_mode[FX_MODE_GHOST_RIDER] = &WS2812FX::mode_2Dghostrider;
_mode[FX_MODE_BLOBS] = &WS2812FX::mode_2Dfloatingblobs;
_brightness = DEFAULT_BRIGHTNESS;
currentPalette = CRGBPalette16(CRGB::Black);
@ -916,11 +920,13 @@ class WS2812FX {
blurCol(uint16_t col, fract8 blur_amount, CRGB* leds=nullptr),
moveX(CRGB *leds, int8_t delta),
moveY(CRGB *leds, int8_t delta),
fill_solid(CRGB* leds, const struct CRGB& color),
fill_solid(CRGB* leds, CRGB c),
fill_circle(CRGB* leds, uint16_t cx, uint16_t cy, uint8_t radius, CRGB c),
fadeToBlackBy(CRGB* leds, uint8_t fadeBy),
nscale8(CRGB* leds, uint8_t scale),
setPixels(CRGB* leds),
drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, CRGB *leds = nullptr);
drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, CRGB *leds = nullptr),
wu_pixel(CRGB *leds, uint32_t x, uint32_t y, CRGB c);
inline void setPixelColorXY(uint16_t x, uint16_t y, uint32_t c) { setPixelColorXY(x, y, byte(c>>16), byte(c>>8), byte(c), byte(c>>24)); }
inline void setPixelColorXY(uint16_t x, uint16_t y, CRGB c) { setPixelColorXY(x, y, c.red, c.green, c.blue); }
@ -959,7 +965,9 @@ class WS2812FX {
mode_2DWaverly(void),
mode_2DAkemi(void),
mode_2Dspaceships(void),
mode_2Dcrazybees(void);
mode_2Dcrazybees(void),
mode_2Dghostrider(void),
mode_2Dfloatingblobs(void);
// end 2D support

View File

@ -305,7 +305,7 @@ void WS2812FX::moveY(CRGB *leds, int8_t delta) {
//ewowi20210628: new functions moved from colorutils: add segment awareness
void WS2812FX::fill_solid(CRGB* leds, const struct CRGB& color) {
void WS2812FX::fill_solid(CRGB* leds, CRGB color) {
uint16_t w = SEGMENT.virtualWidth();
uint16_t h = SEGMENT.virtualHeight();
for(uint16_t y = 0; y < h; y++) for (uint16_t x = 0; x < w; x++) {
@ -314,6 +314,16 @@ void WS2812FX::fill_solid(CRGB* leds, const struct CRGB& color) {
}
}
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
void WS2812FX::fill_circle(CRGB* leds, uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
for (int16_t y = -radius; y <= radius; y++) {
for (int16_t x = -radius; x <= radius; x++) {
if (x * x + y * y <= radius * radius)
leds[XY(cx + x, cy + y)] += col;
}
}
}
void WS2812FX::fadeToBlackBy(CRGB* leds, uint8_t fadeBy) {
nscale8(leds, 255 - fadeBy);
}
@ -347,3 +357,20 @@ void WS2812FX::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB
if (e2 < dy) { err += dx; y0 += sy; }
}
}
#define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8))
void WS2812FX::wu_pixel(CRGB *leds, uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu
// extract the fractional parts and derive their inverses
uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
// calculate the intensities for each affected pixel
uint8_t wu[4] = {WU_WEIGHT(ix, iy), WU_WEIGHT(xx, iy),
WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)};
// multiply the intensities by the colour, and saturating-add them to the pixels
for (uint8_t i = 0; i < 4; i++) {
uint16_t xy = XY((x >> 8) + (i & 1), (y >> 8) + ((i >> 1) & 1));
leds[xy].r = qadd8(leds[xy].r, c.r * wu[i] >> 8);
leds[xy].g = qadd8(leds[xy].g, c.g * wu[i] >> 8);
leds[xy].b = qadd8(leds[xy].b, c.b * wu[i] >> 8);
}
}
#undef WU_WEIGHT

View File

@ -1456,7 +1456,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"2D Tartan@X scale,Y scale;;!",
"2D Waverly@Fade rate,Sensitivity;;!",
"2D Spaceships@Fade rate,Blur;!,!,!;!",
"2D Crazy Bees@Fade rate,Blur;;"
"2D Crazy Bees@Fade rate,Blur;;",
"2D Ghost Rider@Fade rate,Blur;;",
"2D Blobs@!,# blobs;!,!,!;!"
])=====";
const char JSON_palette_names[] PROGMEM = R"=====([

View File

@ -90,7 +90,6 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
colorFromDecOrHexString(col, (char*)payloadStr);
colorUpdated(CALL_MODE_DIRECT_CHANGE);
} else if (strcmp_P(topic, PSTR("/api")) == 0) {
DEBUG_PRINTLN(F("MQTT JSON buffer requested."));
if (!requestJSONBufferLock(15)) { delete[] payloadStr; return; }
if (payload[0] == '{') { //JSON API
deserializeJson(doc, payloadStr);

View File

@ -58,7 +58,6 @@ void handlePresets(bool force)
if (force) return; // something went wrong with force option (most likely WS request), quit and wait for async load
// allocate buffer
DEBUG_PRINTLN(F("Apply preset JSON buffer requested."));
if (!requestJSONBufferLock(9)) return; // will also assign fileDoc
#ifdef ARDUINO_ARCH_ESP32
@ -117,7 +116,6 @@ void savePreset(byte index, const char* pname, JsonObject saveobj)
if (!fileDoc) {
// called from handleSet() HTTP API
DEBUG_PRINTLN(F("Save preset JSON buffer requested."));
if (!requestJSONBufferLock(10)) return;
sObj = fileDoc->to<JsonObject>();
bufferAllocated = true;

View File

@ -211,6 +211,9 @@ bool requestJSONBufferLock(uint8_t module)
}
jsonBufferLock = module ? module : 255;
DEBUG_PRINT(F("JSON buffer locked. ("));
DEBUG_PRINT(jsonBufferLock);
DEBUG_PRINTLN(")");
fileDoc = &doc; // used for applying presets (presets.cpp)
doc.clear();
return true;

View File

@ -36,7 +36,6 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
}
bool verboseResponse = false;
DEBUG_PRINTLN(F("WS JSON receive buffer requested."));
if (!requestJSONBufferLock(11)) return;
DeserializationError error = deserializeJson(doc, data, len);
@ -97,7 +96,8 @@ void sendDataWs(AsyncWebSocketClient * client)
if (!ws.count()) return;
AsyncWebSocketMessageBuffer * buffer;
DEBUG_PRINTLN(F("WS JSON send buffer requested."));
while (strip.isUpdating()) yield();
if (!requestJSONBufferLock(12)) return;
JsonObject state = doc.createNestedObject("state");