diff --git a/src/App/LedEffects.h b/src/App/LedEffects.h index 2d3955d1..4b4a5569 100644 --- a/src/App/LedEffects.h +++ b/src/App/LedEffects.h @@ -1194,7 +1194,7 @@ class OctopusEffect: public Effect { const uint8_t C_Y = leds.size.y / 2 + (offsetY - 128)*leds.size.y/255; for (pos.x = 0; pos.x < leds.size.x; pos.x++) { for (pos.y = 0; pos.y < leds.size.y; pos.y++) { - uint16_t indexV = leds.XYZ(pos); + uint16_t indexV = leds.XYZUnprojected(pos); if (indexV < leds.size.x * leds.size.y) { //excluding UINT16_MAX from XY if out of bounds due to projection rMap[indexV].angle = 40.7436f * atan2f(pos.y - C_Y, pos.x - C_X); // avoid 128*atan2()/PI rMap[indexV].radius = hypotf(pos.x - C_X, pos.y - C_Y) * mapp; //thanks Sutaburosu @@ -2823,6 +2823,52 @@ class PixelMapEffect: public Effect { } }; // PixelMap +class MarioTestEffect: public Effect { + const char * name() {return "MarioTest";} + uint8_t dim() {return _2D;} + const char * tags() {return "💫";} + + void loop(LedsLayer &leds) { + bool background = leds.effectData.read(); + uint8_t offsetX = leds.effectData.read(); + uint8_t offsetY = leds.effectData.read(); + + const uint8_t mario[16][16] = { + {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, + {0, 0, 0, 0, 2, 2, 2, 3, 3, 4, 3, 0, 0, 0, 0, 0}, + {0, 0, 0, 2, 3, 2, 3, 3, 3, 4, 3, 3, 3, 0, 0, 0}, + {0, 0, 0, 2, 3, 2, 2, 3, 3, 3, 4, 3, 3, 3, 0, 0}, + {0, 0, 0, 0, 2, 3, 3, 3, 3, 4, 4, 4, 4, 0, 0, 0}, + {0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 5, 5, 1, 5, 5, 1, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 5, 5, 5, 1, 5, 5, 1, 5, 5, 5, 0, 0, 0}, + {0, 0, 5, 5, 5, 5, 1, 5, 5, 1, 5, 5, 5, 5, 0, 0}, + {0, 0, 3, 3, 5, 5, 1, 1, 1, 1, 5, 5, 3, 3, 0, 0}, + {0, 0, 3, 3, 3, 1, 6, 1, 1, 6, 1, 3, 3, 3, 0, 0}, + {0, 0, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 0, 0}, + {0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0}, + {0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0}, + {0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0} + }; + + CRGB colors[7] = {CRGB::DimGrey, CRGB::Red, CRGB::Brown, CRGB::Tan, CRGB::Black, CRGB::Blue, CRGB::Yellow}; + + if (background) leds.fill_solid(CRGB::DimGrey); + else leds.fill_solid(CRGB::Black); + //draw 16x16 mario + for (int x = 0; x < 16; x++) for (int y = 0; y < 16; y++) { + leds[Coord3D({x + offsetX, y + offsetY, 0})] = colors[mario[y][x]]; + } + } + + void controls(LedsLayer &leds, JsonObject parentVar) { + ui->initCheckBox(parentVar, "Background", leds.effectData.write(false)); + ui->initSlider(parentVar, "OffsetX", leds.effectData.write(leds.size.x/2 - 8), 0, leds.size.x - 16); + ui->initSlider(parentVar, "OffsetY", leds.effectData.write(leds.size.y/2 - 8), 0, leds.size.y - 16); + } +}; // MarioTest + #ifdef STARBASE_USERMOD_LIVE class LiveScriptEffect: public Effect { diff --git a/src/App/LedLayer.h b/src/App/LedLayer.h index f190f76f..5149ed3a 100644 --- a/src/App/LedLayer.h +++ b/src/App/LedLayer.h @@ -45,10 +45,11 @@ enum ProjectionsE p_Grouping, p_Spacing, p_Transpose, - p_Kaleidoscope, + // p_Kaleidoscope, p_Scrolling, p_Acceleration, p_Checkerboard, + p_Rotate, p_count // keep as last entry }; diff --git a/src/App/LedModEffects.h b/src/App/LedModEffects.h index f3954b80..0f2a951e 100644 --- a/src/App/LedModEffects.h +++ b/src/App/LedModEffects.h @@ -117,6 +117,7 @@ class LedModEffects:public SysModule { effects.push_back(new RubiksCubeEffect); effects.push_back(new SphereMoveEffect); effects.push_back(new PixelMapEffect); + effects.push_back(new MarioTestEffect); #ifdef STARBASE_USERMOD_LIVE effects.push_back(new LiveScriptEffect); @@ -136,10 +137,11 @@ class LedModEffects:public SysModule { fixture.projections.push_back(new GroupingProjection); fixture.projections.push_back(new SpacingProjection); fixture.projections.push_back(new TransposeProjection); - fixture.projections.push_back(new KaleidoscopeProjection); + // fixture.projections.push_back(new KaleidoscopeProjection); fixture.projections.push_back(new ScrollingProjection); fixture.projections.push_back(new AccelerationProjection); fixture.projections.push_back(new CheckerboardProjection); + fixture.projections.push_back(new RotateProjection); #ifdef STARLIGHT_CLOCKLESS_LED_DRIVER #if !(CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2) diff --git a/src/App/LedProjections.h b/src/App/LedProjections.h index 4725fb90..0d711cab 100644 --- a/src/App/LedProjections.h +++ b/src/App/LedProjections.h @@ -306,10 +306,22 @@ class TiltPanRollProjection: public Projection { public: void setup(LedsLayer &leds, Coord3D &sizeAdjusted, Coord3D &pixelAdjusted, Coord3D &midPosAdjusted, Coord3D &mapped, uint16_t &indexV) { + // adjustSizeAndPixel(leds, sizeAdjusted, pixelAdjusted, midPosAdjusted); // Uncomment to expand grid to fill corners DefaultProjection dp; dp.setup(leds, sizeAdjusted, pixelAdjusted, midPosAdjusted, mapped, indexV); } + void adjustSizeAndPixel(LedsLayer &leds, Coord3D &sizeAdjusted, Coord3D &pixelAdjusted, Coord3D &midPosAdjusted) { + uint8_t size = max(sizeAdjusted.x, max(sizeAdjusted.y, sizeAdjusted.z)); + size = sqrt(size * size * 2) + 1; + Coord3D offset = {(size - sizeAdjusted.x) / 2, (size - sizeAdjusted.y) / 2, 0}; + sizeAdjusted = Coord3D{size, size, 1}; + + pixelAdjusted.x += offset.x; + pixelAdjusted.y += offset.y; + pixelAdjusted.z += offset.z; + } + void adjustXYZ(LedsLayer &leds, Coord3D &pixel) { #ifdef STARBASE_USERMOD_MPU6050 if (leds.proGyro) { @@ -598,7 +610,7 @@ class GroupingProjection: public Projection { } void controls(LedsLayer &leds, JsonObject parentVar) { - Coord3D *grouping = leds.projectionData.write({1,1,1}); + Coord3D *grouping = leds.projectionData.write({2,2,2}); ui->initCoord3D(parentVar, "Grouping", grouping, 0, 100, false, [&leds](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun case onChange: leds.fixture->layers[rowNr]->triggerMapping(); @@ -734,7 +746,7 @@ class ScrollingProjection: public Projection { MirrorProjection mp; mp.controls(leds, parentVar); - uint8_t *xSpeed = leds.projectionData.write(0); + uint8_t *xSpeed = leds.projectionData.write(128); uint8_t *ySpeed = leds.projectionData.write(0); uint8_t *zSpeed = leds.projectionData.write(0); @@ -853,6 +865,148 @@ class CheckerboardProjection: public Projection { } }; //CheckerboardProjection +class RotateProjection: public Projection { + const char * name() {return "Rotate";} + const char * tags() {return "💫";} + + struct RotateData { // 16 bytes + union { + struct { + bool flip : 1; + bool reverse : 1; + bool alternate : 1; + bool expand : 1; + }; + uint8_t flags; + }; + uint8_t speed; + uint8_t midX; + uint8_t midY; + uint16_t angle; + uint16_t interval; // ms between updates + int16_t shearX; + int16_t shearY; + unsigned long lastUpdate; // last sys->now update + }; + + public: + + void setup(LedsLayer &leds, Coord3D &sizeAdjusted, Coord3D &pixelAdjusted, Coord3D &midPosAdjusted, Coord3D &mapped, uint16_t &indexV) { + adjustSizeAndPixel(leds, sizeAdjusted, pixelAdjusted, midPosAdjusted); + DefaultProjection dp; + dp.setup(leds, sizeAdjusted, pixelAdjusted, midPosAdjusted, mapped, indexV); + } + + void adjustSizeAndPixel(LedsLayer &leds, Coord3D &sizeAdjusted, Coord3D &pixelAdjusted, Coord3D &midPosAdjusted) { + leds.projectionData.begin(); + RotateData *data = leds.projectionData.readWrite(); + if (leds.size == Coord3D{0, 0, 0}) { + data->expand = mdl->getValue("Expand"); + } + if (data->expand) { + uint8_t size = max(sizeAdjusted.x, max(sizeAdjusted.y, sizeAdjusted.z)); + size = sqrt(size * size * 2) + 1; + Coord3D offset = {(size - sizeAdjusted.x) / 2, (size - sizeAdjusted.y) / 2, 0}; + sizeAdjusted = Coord3D{size, size, 1}; + pixelAdjusted.x += offset.x; + pixelAdjusted.y += offset.y; + pixelAdjusted.z += offset.z; + } + if (leds.size == Coord3D{0, 0, 0}) { + data->midX = sizeAdjusted.x / 2; + data->midY = sizeAdjusted.y / 2; + return; + } + } + + void adjustXYZ(LedsLayer &leds, Coord3D &pixel) { + leds.projectionData.begin(); + RotateData *data = leds.projectionData.readWrite(); + + constexpr int Fixed_Scale = 1 << 10; + + if ((sys->now - data->lastUpdate > data->interval) && data->speed) { // Only update if the angle has changed + data->lastUpdate = sys->now; + // Increment the angle + data->angle = data->reverse ? (data->angle <= 0 ? 359 : data->angle - 1) : (data->angle >= 359 ? 0 : data->angle + 1); + + if (data->alternate && (data->angle == 0)) data->reverse = !data->reverse; + + data->flip = (data->angle > 90 && data->angle < 270); + + int newAngle = data->angle; // Flip newAngle if needed. Don't change angle in data + if (data->flip) {newAngle += 180; newAngle %= 360;} + + // Calculate shearX and shearY + float angleRadians = radians(newAngle); + data->shearX = -tan(angleRadians / 2) * Fixed_Scale; + data->shearY = sin(angleRadians) * Fixed_Scale; + } + + int maxX = leds.size.x; + int maxY = leds.size.y; + + if (data->flip) { + // Reverse x and y values + pixel.x = maxX - pixel.x; + pixel.y = maxY - pixel.y; + } + + // Translate pixel to origin + int dx = pixel.x - data->midX; + int dy = pixel.y - data->midY; + + // Apply the 3 shear transformations + int x1 = dx + data->shearX * dy / Fixed_Scale; + int y1 = dy + data->shearY * x1 / Fixed_Scale; + int x2 = x1 + data->shearX * y1 / Fixed_Scale; + + // Translate pixel back and assign + pixel.x = x2 + data->midX; + pixel.y = y1 + data->midY; + pixel.z = 0; + + // Clamp the pixel to the bounds + if (pixel.x < 0) pixel.x = 0; + else if (pixel.x >= maxX) pixel.x = maxX - 1; + if (pixel.y < 0) pixel.y = 0; + else if (pixel.y >= maxY) pixel.y = maxY - 1; + } + + void controls(LedsLayer &leds, JsonObject parentVar) { + RotateData *data = leds.projectionData.readWrite(); + + ui->initSelect(parentVar, "Direction", 0, false, [data](JsonObject var, uint8_t rowNr, uint8_t funType) { switch (funType) { //varFun + case onUI: { + JsonArray options = ui->setOptions(var); + options.add("Clockwise"); + options.add("Counter-Clockwise"); + options.add("Alternate"); + return true; } + case onChange: { + uint8_t val = mdl->getValue(var, rowNr); + if (val == 0) data->reverse = false; + if (val == 1) data->reverse = true; + if (val == 2) data->alternate = true; else data->alternate = false; + return true; } + default: return false; + }}); + ui->initSlider(parentVar, "Rotate Speed", 128, 0, 254, false, [data](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun + case onChange: + data->speed = mdl->getValue(var, rowNr); + data->interval = 1000 / (data->speed + 1); + return true; + default: return false; + }}); + ui->initCheckBox(parentVar, "Expand", false, false, [&leds](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun + case onChange: + leds.fixture->layers[rowNr]->triggerMapping(); + return true; + default: return false; + }}); + } +}; //RotateProjection + class TestProjection: public Projection { const char * name() {return "Test";} const char * tags() {return "💡";}