Skip to content
Open
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
103 changes: 103 additions & 0 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,108 @@ uint16_t mode_copy_segment(void) {
}
static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,Axis(2D),FullStack(last frame);;;12;ix=0,c1=0,c2=0,c3=0";

uint16_t mode_rotate_segment(void) {

uint32_t sourceid = SEGMENT.custom3;
Segment& sourcesegment = strip.getSegment(sourceid);

if (!sourcesegment.isActive() ||
!sourcesegment.is2D() ||
sourceid >= strip.getSegmentsNum() ||
sourceid == strip.getCurrSegmentId()) {
SEGMENT.fadeToBlackBy(5); // fade out
return FRAMETIME;
}

const int cols = SEG_W, rows = SEG_H;
const int midX = cols / 2;
const int midY = rows / 2;
int angleInc = map(SEGMENT.speed, 0, 255, 1, 16);
SEGENV.aux0 = (SEGENV.aux0 + angleInc) % 360;

int16_t shearX;
int16_t shearY;
const uint8_t Scale_Shift = 10;
const int Fixed_Scale = (1 << Scale_Shift);
const int RoundVal = (1 << (Scale_Shift - 1));

int zoomOffset = SEGMENT.intensity - 128; // -128 - 127
const int zoomRange = (Fixed_Scale * 3) / 4; // 768
int zoomScale = Fixed_Scale + (zoomOffset * zoomRange) / 128;
if (zoomScale <= 0) zoomScale = 1; // avoid divide-by-zero and negative zoom

int shearAngle = SEGENV.aux0;

bool flip = (shearAngle > 90 && shearAngle < 270); // Flip to avoid instability near 180°
shearAngle = flip ? (shearAngle + 180) % 360 : shearAngle;

// Calculate shearX and shearY
float angleRadians = radians(shearAngle);
shearX = -tanf(angleRadians / 2) * Fixed_Scale;
shearY = sinf(angleRadians) * Fixed_Scale;

SEGMENT.fill(0); // clear segment before drawing rotated copy

const int srcWidth = sourcesegment.width();
const int srcHeight = sourcesegment.height();
const int WRAP_PAD_X = srcWidth << 5; // ×32
const int WRAP_PAD_Y = srcHeight << 5; // Ensures wrap works with large negative coordinates when zoomed out

// Use inverse mapping: iterate destination pixels, find source coordinates
for (int destY = 0; destY < rows; destY++) {
for (int destX = 0; destX < cols; destX++) {
// Translate destination to origin
int dx = destX - midX;
int dy = destY - midY;

// Inverse shear transformations (reverse order)
int x1 = dx - ((shearX * dy + RoundVal) >> Scale_Shift);
int y0 = dy - ((shearY * x1 + RoundVal) >> Scale_Shift);
int x0 = x1 - ((shearX * y0 + RoundVal) >> Scale_Shift);

// Apply zoom to source coordinates
x0 = (x0 * Fixed_Scale) / zoomScale;
y0 = (y0 * Fixed_Scale) / zoomScale;

// Handle flip
int srcX = flip ? (midX - x0) : (midX + x0);
int srcY = flip ? (midY - y0) : (midY + y0);

// Bounds check or wrap
if (SEGMENT.check1) { // Wrap around
srcX = (srcX + WRAP_PAD_X) % srcWidth;
srcY = (srcY + WRAP_PAD_Y) % srcHeight;
}
else if (SEGMENT.check2) { // Wrap plus mirror
int tileX = (srcX + WRAP_PAD_X) / srcWidth;
int tileY = (srcY + WRAP_PAD_Y) / srcHeight;

// Wrap src
srcX = (srcX + WRAP_PAD_X) % srcWidth;
srcY = (srcY + WRAP_PAD_Y) % srcHeight;

// Flip on odd tiles
if (tileX & 1) srcX = srcWidth - 1 - srcX;
if (tileY & 1) srcY = srcHeight - 1 - srcY;
}
else if (srcX < 0 || srcX >= srcWidth || srcY < 0 || srcY >= srcHeight) continue;

// Sample from source
sourcesegment.setDrawDimensions();
uint32_t sourcecolor = sourcesegment.getPixelColorXY(srcX, srcY);

if (sourcecolor == 0) continue; // skip black pixels already filled to black

// Write to destination
SEGMENT.setDrawDimensions();
SEGMENT.setPixelColorXY(destX, destY, sourcecolor);
}
}

return FRAMETIME;
}
static const char _data_FX_MODE_ROTATE[] PROGMEM = "Rotate Segment@!,Zoom,,,ID,Wrap,Mirror Wrap;;;2;sx=0,ix=128,c1=0,c2=0,c3=0";


/*
* Blink/strobe function
Expand Down Expand Up @@ -11201,6 +11303,7 @@ void WS2812FX::setupEffectData() {
addEffect(FX_MODE_2DOCTOPUS, &mode_2Doctopus, _data_FX_MODE_2DOCTOPUS);
addEffect(FX_MODE_2DWAVINGCELL, &mode_2Dwavingcell, _data_FX_MODE_2DWAVINGCELL);
addEffect(FX_MODE_2DAKEMI, &mode_2DAkemi, _data_FX_MODE_2DAKEMI); // audio
addEffect(FX_MODE_ROTATE, &mode_rotate_segment, _data_FX_MODE_ROTATE);

#ifndef WLED_DISABLE_PARTICLESYSTEM2D
addEffect(FX_MODE_PARTICLEVOLCANO, &mode_particlevolcano, _data_FX_MODE_PARTICLEVOLCANO);
Expand Down
5 changes: 4 additions & 1 deletion wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,10 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define FX_MODE_PS1DSONICBOOM 215
#define FX_MODE_PS1DSPRINGY 216
#define FX_MODE_PARTICLEGALAXY 217
#define MODE_COUNT 218

#define FX_MODE_ROTATE 218

#define MODE_COUNT 219
Comment on lines +382 to +385
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mode ID and count are correctly assigned.

The new FX_MODE_ROTATE constant (218) correctly follows the previous highest mode ID (217), and MODE_COUNT is properly updated to 219, following the established convention where MODE_COUNT represents the highest mode ID + 1.

Verify that FX.cpp includes the implementation:

  • mode_rotate_segment() function
  • _data_FX_MODE_ROTATE descriptor
  • Mode registration referencing ID 218
🤖 Prompt for AI Agents
In wled00/FX.h around lines 382 to 385, MODE_COUNT and FX_MODE_ROTATE are added
but FX.cpp must be updated accordingly: implement a mode_rotate_segment()
function, add a _data_FX_MODE_ROTATE descriptor object for the mode, and
register the mode using ID 218 in the mode registration table/list; ensure the
function name, descriptor variable name, and registration entry match the
FX_MODE_ROTATE constant (218) exactly and follow the same patterns and parameter
ordering used by other modes in FX.cpp.



#define BLEND_STYLE_FADE 0x00 // universal
Expand Down