From ab39623562acf5df40318b16db087adee2cbb1aa Mon Sep 17 00:00:00 2001 From: Ewoud Date: Mon, 12 Aug 2024 10:38:33 +0200 Subject: [PATCH] Update laser GEQ 3D to latest LedEffects: update laserGEQ: - more controls - incl soft and depth LedLayer: drawLine - add soft and depth --- misc/octoSL.sc | 9 --- platformio.ini | 2 +- src/App/LedEffects.h | 176 ++++++++++++++++++++++--------------------- src/App/LedLayer.h | 68 +++++++++++++++-- 4 files changed, 153 insertions(+), 102 deletions(-) diff --git a/misc/octoSL.sc b/misc/octoSL.sc index 40d6eda0..795da90b 100644 --- a/misc/octoSL.sc +++ b/misc/octoSL.sc @@ -48,13 +48,4 @@ void loop() { } t=t+speed; //delay(16); -} - -void main() { - resetStat(); - setup(); - while (2>1) { - loop(); - show(); - } } \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index f87796ba..3cf41535 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,7 +89,7 @@ lib_deps = build_flags = ; -D APP=StarBase -D PIOENV=$PIOENV - -D VERSION=24081011 ; Date and time (GMT!), update at every commit!! + -D VERSION=24081209 ; Date and time (GMT!), update at every commit!! -D LFS_THREADSAFE ; enables use of semaphores in LittleFS driver -D STARBASE_DEVMODE ${ESPAsyncWebServer.build_flags} ;alternatively PsychicHttp diff --git a/src/App/LedEffects.h b/src/App/LedEffects.h index b50e355f..f53b766f 100644 --- a/src/App/LedEffects.h +++ b/src/App/LedEffects.h @@ -2408,9 +2408,10 @@ class GEQEffect: public Effect { } }; //GEQ -//by @Troy +// Author: @TroyHacks +// @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 class LaserGEQEffect: public Effect { - const char * name() {return "laserGEQ";} + const char * name() {return "GEQ 3D";} uint8_t dim() {return _2D;} const char * tags() {return "♫💫";} @@ -2420,130 +2421,137 @@ class LaserGEQEffect: public Effect { void loop(LedsLayer &leds) { //Binding of controls. Keep before binding of vars and keep in same order as in controls() - uint8_t fadeOut = leds.effectData.read(); + uint8_t speed = leds.effectData.read(); + uint8_t frontFill = leds.effectData.read(); + uint8_t horizon = leds.effectData.read() - 1; + uint8_t depth = leds.effectData.read(); + uint8_t numBands = leds.effectData.read() - 1; + bool borders = leds.effectData.read(); + bool soft = leds.effectData.read(); uint16_t *projector = leds.effectData.readWrite(); - int8_t *projector_dir = leds.effectData.readWrite();; + int8_t *projector_dir = leds.effectData.readWrite(); + uint32_t *counter = leds.effectData.readWrite(); - *projector += *projector_dir; - if (*projector > leds.size.x) *projector = leds.size.x; //init - if (*projector == leds.size.x) *projector_dir = -1; - if (*projector == 0) *projector_dir = 1; + const int cols = leds.size.x; + const int rows = leds.size.y; - if (fadeOut > 250) { - leds.fill_solid(CRGB::Black); - } else { - leds.fadeToBlackBy(fadeOut); - } + if ((*counter)++ % (11-speed) == 0) *projector += *projector_dir; + if (*projector >= cols) *projector_dir = -1; + if (*projector <= 0) *projector_dir = 1; - const int NUM_BANDS = 16; // map(SEGMENT.custom1, 0, 255, 1, 16); + leds.fill_solid(CRGB::Black); - uint8_t heights[16] = { 0 }; + CRGB ledColorTemp; + const int NUM_BANDS = numBands; + uint_fast8_t split = map(*projector,0,cols,0,(NUM_BANDS - 1)); - for (int i=0; i<16; i++) { - heights[i] = map(wledAudioMod->fftResults[i],0,255,0,leds.size.y-10); + uint8_t heights[NUM_GEQ_CHANNELS] = { 0 }; + const uint8_t maxHeight = roundf(float(rows) * ((rows<18) ? 0.75f : 0.85f)); // slightly reduce bar height on small panels + for (int i=0; ifftResults[band],0,maxHeight); // cache fftResult[] as data might be updated in parallel by the audioreactive core } - for (int i=0; i<8; i++) { - uint16_t colorIndex = map(leds.size.x/16*i, 0, leds.size.x-1, 0, 255); + for (int i=0; i<=split; i++) { // paint right vertical faces and top - LEFT to RIGHT + uint16_t colorIndex = map(cols/NUM_BANDS*i, 0, cols-1, 0, 255); CRGB ledColor = ColorFromPalette(leds.palette, colorIndex); - - int linex = i*(leds.size.x/16); + int linex = i*(cols/NUM_BANDS); if (heights[i] > 1) { - - if (linex < *projector) { - for (int y = 0; y <= heights[i]; y++) { - leds.drawLine(linex+(leds.size.x/16)-1,leds.size.y-y-1,*projector,0, blend(ledColor, CRGB::Black, 32)); - } - } - - if (linex > *projector) { - for (int y = 0; y <= heights[i]; y++) { - leds.drawLine(linex ,leds.size.y-y-1,*projector,0, blend(ledColor, CRGB::Black, 32)); - } + ledColorTemp = blend(ledColor, CRGB::Black, 255-32); + int pPos = max(0, linex+(cols/NUM_BANDS)-1); + for (int y = (i 0) leds.drawLine(pPos,rows-y-1,*projector,horizon,ledColorTemp,false,depth); // right side perspective + } + + ledColorTemp = blend(ledColor, CRGB::Black, 255-128); + if (heights[i] < rows-horizon && (*projector <=linex || *projector >= pPos)) { // draw if above horizon AND not directly under projector (special case later) + if (rows-heights[i] > 1) { // sanity check - avoid negative Y + for (uint_fast8_t x=linex; x<=pPos;x++) { + bool doSoft = soft && ((x==linex) || (x==pPos)); // only first and last line need AA + leds.drawLine(x,rows-heights[i]-2,*projector,horizon,ledColorTemp,doSoft,depth); // top perspective + } + } } - } - } - for (int i=15; i>7; i--) { - uint16_t colorIndex = map(leds.size.x/16*i, 0, leds.size.x-1, 0, 255); + for (int i=(NUM_BANDS - 1); i>split; i--) { // paint left vertical faces and top - RIGHT to LEFT + uint16_t colorIndex = map(cols/NUM_BANDS*i, 0, cols-1, 0, 255); CRGB ledColor = ColorFromPalette(leds.palette, colorIndex); - - int linex = i*(leds.size.x/16); + int linex = i*(cols/NUM_BANDS); + int pPos = max(0, linex+(cols/NUM_BANDS)-1); if (heights[i] > 1) { - - if (linex < *projector) { - for (int y = 0; y <= heights[i]; y++) { - leds.drawLine(linex+(leds.size.x/16)-1,leds.size.y-y-1,*projector,0, blend(ledColor, CRGB::Black, 32)); - } + ledColorTemp = blend(ledColor, CRGB::Black, 255-32); + for (uint_fast8_t y = (i>0) ? heights[i-1] : 0; y <= heights[i]; y++) { // don't bother drawing what we'll hide anyway + if (rows-y > 0) leds.drawLine(linex,rows-y-1,*projector,horizon,ledColorTemp,false,depth); // left side perspective } - if (linex > *projector) { - for (int y = 0; y <= heights[i]; y++) { - leds.drawLine(linex ,leds.size.y-y-1,*projector,0, blend(ledColor, CRGB::Black, 32)); - } + ledColorTemp = blend(ledColor, CRGB::Black, 255-128); + if (heights[i] < rows-horizon && (*projector <=linex || *projector >= pPos)) { // draw if above horizon AND not directly under projector (special case later) + if (rows-heights[i] > 1) { // sanity check - avoid negative Y + for (uint_fast8_t x=linex; x<=pPos;x++) { + bool doSoft = soft && ((x==linex) || (x==pPos)); // only first and last line need AA + leds.drawLine(x,rows-heights[i]-2,*projector,horizon,ledColorTemp,doSoft,depth); // top perspective + } + } } } } - for (int i=0; i<8; i++) { - uint16_t colorIndex = map(leds.size.x/16*i, 0, leds.size.x-1, 0, 255); + for (int i=0; i 1) { - for (int x=linex; x<=linex+(leds.size.x/16)-1;x++) { - leds.drawLine(x, leds.size.y-heights[i]-2,*projector,0, blend(ledColor, CRGB::Black, 128)); // top perspective + int linex = i*(cols/NUM_BANDS); + int pPos = linex+(cols/NUM_BANDS)-1; + int pPos1 = linex+(cols/NUM_BANDS); + + if (*projector >=linex && *projector <= pPos) { // special case when top perspective is directly under the projector + if ((heights[i] > 1) && (heights[i] < rows-horizon) && (rows-heights[i] > 1)) { + ledColorTemp = blend(ledColor, CRGB::Black, 255-128); + for (uint_fast8_t x=linex; x<=pPos;x++) { + bool doSoft = soft && ((x==linex) || (x==pPos)); // only first and last line need AA + leds.drawLine(x,rows-heights[i]-2,*projector,horizon,ledColorTemp,doSoft,depth); // top perspective + } } } - } - - for (int i=15; i>7; i--) { - - uint16_t colorIndex = map(leds.size.x/16*i, 0, leds.size.x-1, 0, 255); - CRGB ledColor = ColorFromPalette(leds.palette, colorIndex); - - int linex = i*(leds.size.x/16); - - if (heights[i] > 1) { - for (int x=linex; x<=linex+(leds.size.x/16)-1;x++) { - leds.drawLine(x, leds.size.y-heights[i]-2,*projector,0, blend(ledColor, CRGB::Black, 128)); // top perspective + if ((heights[i] > 1) && (rows-heights[i] > 0)) { + ledColorTemp = blend(ledColor, CRGB::Black, 255-frontFill); + for (uint_fast8_t x=linex; x rows-horizon) { + if (frontFill == 0) ledColorTemp = blend(ledColor, CRGB::Black, 255-32); // match side fill if we're in blackout mode + leds.drawLine(linex,rows-heights[i]-1,linex+(cols/NUM_BANDS)-1,rows-heights[i]-1,ledColorTemp); // top line to simulate hidden top fill + } - if (heights[i] > 1) { - for (int x=linex+1; x 1)) { + leds.drawLine(linex, rows-1,linex,rows-heights[i]-1,ledColor); // left side line + leds.drawLine(linex+(cols/NUM_BANDS)-1,rows-1,linex+(cols/NUM_BANDS)-1,rows-heights[i]-1,ledColor); // right side line + leds.drawLine(linex, rows-heights[i]-2,linex+(cols/NUM_BANDS)-1,rows-heights[i]-2,ledColor); // top line + leds.drawLine(linex, rows-1,linex+(cols/NUM_BANDS)-1,rows-1,ledColor); // bottom line } - leds.drawLine(linex, leds.size.y-1,linex,leds.size.y-heights[i]-1,ledColor); // left side - leds.drawLine(linex+(leds.size.x/16)-1,leds.size.y-1,linex+(leds.size.x/16)-1,leds.size.y-heights[i]-1,ledColor); // right side - leds.drawLine(linex, leds.size.y-heights[i]-2,linex+(leds.size.x/16)-1,leds.size.y-heights[i]-2,ledColor); // top - leds.drawLine(linex, leds.size.y-1,linex+(leds.size.x/16)-1,leds.size.y-1,ledColor); // bottom } } } void controls(LedsLayer &leds, JsonObject parentVar) { Effect::controls(leds, parentVar); - ui->initSlider(parentVar, "fadeOut", leds.effectData.write(255)); + ui->initSlider(parentVar, "Speed", leds.effectData.write(5), 1, 10); + ui->initSlider(parentVar, "Front Fill", leds.effectData.write(16)); + ui->initSlider(parentVar, "Horizon", leds.effectData.write(leds.size.x), 1, leds.size.x); + ui->initSlider(parentVar, "Depth", leds.effectData.write(176)); + ui->initSlider(parentVar, "Num bands", leds.effectData.write(8), 2, 16); // constrain NUM_BANDS between 2(for split) and cols (for small width segments) + ui->initCheckBox(parentVar, "Borders", leds.effectData.write(false)); + ui->initCheckBox(parentVar, "Soft hack", leds.effectData.write(true)); } }; //LaserGEQEffect diff --git a/src/App/LedLayer.h b/src/App/LedLayer.h index cadd76f6..200af3f9 100644 --- a/src/App/LedLayer.h +++ b/src/App/LedLayer.h @@ -348,17 +348,69 @@ class LedsLayer { void addPixelColor(unsigned16 indexV, CRGB color) {setPixelColor(indexV, getPixelColor(indexV) + color);} void addPixelColor(Coord3D pixel, CRGB color) {setPixelColor(pixel, getPixelColor(pixel) + color);} - void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB color) { + void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB color, bool soft = false, uint8_t depth = UINT8_MAX) { if (x0 >= size.x || x1 >= size.x || y0 >= size.y || y1 >= size.y) return; + + // WLEDMM shorten line according to depth + if (depth < UINT8_MAX) { + if (depth == 0) return; // nothing to paint + if (depth<2) {x1 = x0; y1=y0; } // single pixel + else { // shorten line + x0 *=2; y0 *=2; // we do everything "*2" for better rounding + int dx1 = ((int(2*x1) - int(x0)) * int(depth)) / 255; // X distance, scaled down by depth + int dy1 = ((int(2*y1) - int(y0)) * int(depth)) / 255; // Y distance, scaled down by depth + x1 = (x0 + dx1 +1) / 2; + y1 = (y0 + dy1 +1) / 2; + x0 /=2; y0 /=2; + } + } + const int16_t dx = abs(x1-x0), sx = x0dy ? dx : -dy)/2, e2; - for (;;) { - setPixelColor(XY(x0,y0), color); - if (x0==x1 && y0==y1) break; - e2 = err; - if (e2 >-dx) { err -= dy; x0 += sx; } - if (e2 < dy) { err += dx; y0 += sy; } + + // single pixel (line length == 0) + if (dx+dy == 0) { + setPixelColor(XY(x0, y0), color); + return; + } + + if (soft) { + // Xiaolin Wu’s algorithm + const bool steep = dy > dx; + if (steep) { + // we need to go along longest dimension + std::swap(x0,y0); + std::swap(x1,y1); + } + if (x0 > x1) { + // we need to go in increasing fashion + std::swap(x0,x1); + std::swap(y0,y1); + } + float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0); + float intersectY = y0; + for (int x = x0; x <= x1; x++) { + unsigned keep = float(0xFFFF) * (intersectY-int(intersectY)); // how much color to keep + unsigned seep = 0xFFFF - keep; // how much background to keep + int y = int(intersectY); + if (steep) std::swap(x,y); // temporarily swap if steep + // pixel coverage is determined by fractional part of y co-ordinate + setPixelColor(XY(x, y), blend(color, getPixelColor(XY(x, y)), keep)); + setPixelColor(XY(x+int(steep), y+int(!steep)), blend(color, getPixelColor(XY(x+int(steep), y+int(!steep))), seep)); + intersectY += gradient; + if (steep) std::swap(x,y); // restore if steep + } + } else { + // Bresenham's algorithm + int err = (dx>dy ? dx : -dy)/2; // error direction + for (;;) { + // if (x0 >= cols || y0 >= rows) break; // WLEDMM we hit the edge - should never happen + setPixelColor(XY(x0, y0), color); + if (x0==x1 && y0==y1) break; + int e2 = err; + if (e2 >-dx) { err -= dy; x0 += sx; } + if (e2 < dy) { err += dx; y0 += sy; } + } } }