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

Rotating 2D Palette effect #3683

Merged
merged 4 commits into from
Jan 27, 2024
Merged
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
106 changes: 93 additions & 13 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1929,22 +1929,102 @@ static const char _data_FX_MODE_JUGGLE[] PROGMEM = "Juggle@!,Trail;;!;;sx=64,ix=


uint16_t mode_palette() {
uint16_t counter = 0;
if (SEGMENT.speed != 0)
{
counter = (strip.now * ((SEGMENT.speed >> 3) +1)) & 0xFFFF;
counter = counter >> 8;
}

for (int i = 0; i < SEGLEN; i++)
{
uint8_t colorIndex = (i * 255 / SEGLEN) - counter;
SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, PALETTE_MOVING_WRAP, 255));
// Set up some compile time constants so that we can handle integer and float based modes using the same code base.
#ifdef ESP8266
using mathType = int32_t;
using wideMathType = int64_t;
using angleType = uint16_t;
constexpr mathType sInt16Scale = 0x7FFF;
constexpr mathType maxAngle = 0x8000;
constexpr mathType staticRotationScale = 256;
constexpr mathType animatedRotationScale = 1;
constexpr int16_t (*sinFunction)(uint16_t) = &sin16;
constexpr int16_t (*cosFunction)(uint16_t) = &cos16;
#else
using mathType = float;
using wideMathType = float;
using angleType = float;
constexpr mathType sInt16Scale = 1.0f;
constexpr mathType maxAngle = M_PI / 256.0;
constexpr mathType staticRotationScale = 1.0f;
constexpr mathType animatedRotationScale = M_TWOPI / double(0xFFFF);
constexpr float (*sinFunction)(float) = &sin_t;
constexpr float (*cosFunction)(float) = &cos_t;
#endif
const bool isMatrix = strip.isMatrix;
const int cols = SEGMENT.virtualWidth();
const int rows = isMatrix ? SEGMENT.virtualHeight() : strip.getActiveSegmentsNum();

const int inputShift = SEGMENT.speed;
const int inputSize = SEGMENT.intensity;
const int inputRotation = SEGMENT.custom1;
const bool inputAnimateShift = SEGMENT.check1;
const bool inputAnimateRotation = SEGMENT.check2;
const bool inputAssumeSquare = SEGMENT.check3;

const angleType theta = (!inputAnimateRotation) ? (inputRotation * maxAngle / staticRotationScale) : (((strip.now * ((inputRotation >> 4) +1)) & 0xFFFF) * animatedRotationScale);
const mathType sinTheta = sinFunction(theta);
const mathType cosTheta = cosFunction(theta);

const mathType maxX = std::max(1, cols-1);
const mathType maxY = std::max(1, rows-1);
// Set up some parameters according to inputAssumeSquare, so that we can handle anamorphic mode using the same code base.
const mathType maxXIn = inputAssumeSquare ? maxX : mathType(1);
const mathType maxYIn = inputAssumeSquare ? maxY : mathType(1);
const mathType maxXOut = !inputAssumeSquare ? maxX : mathType(1);
const mathType maxYOut = !inputAssumeSquare ? maxY : mathType(1);
const mathType centerX = sInt16Scale * maxXOut / mathType(2);
const mathType centerY = sInt16Scale * maxYOut / mathType(2);
// The basic idea for this effect is to rotate a rectangle that is filled with the palette along one axis, then map our
// display to it, to find what color a pixel should have.
// However, we want a) no areas of solid color (in front of or behind the palette), and b) we want to make use of the full palette.
// So the rectangle needs to have exactly the right size. That size depends on the rotation.
// This scale computation here only considers one dimension. You can think of it like the rectangle is always scaled so that
// the left and right most points always match the left and right side of the display.
const mathType scale = std::abs(sinTheta) + (std::abs(cosTheta) * maxYOut / maxXOut);
// 2D simulation:
// If we are dealing with a 1D setup, we assume that each segment represents one line on a 2-dimensional display.
// The function is called once per segments, so we need to handle one line at a time.
const int yFrom = isMatrix ? 0 : strip.getCurrSegmentId();
blazoncek marked this conversation as resolved.
Show resolved Hide resolved
const int yTo = isMatrix ? maxY : yFrom;
for (int y = yFrom; y <= yTo; ++y) {
// translate, scale, rotate
const mathType ytCosTheta = mathType((wideMathType(cosTheta) * wideMathType(y * sInt16Scale - centerY * maxYIn))/wideMathType(maxYIn * scale));
for (int x = 0; x < cols; ++x) {
// translate, scale, rotate
const mathType xtSinTheta = mathType((wideMathType(sinTheta) * wideMathType(x * sInt16Scale - centerX * maxXIn))/wideMathType(maxXIn * scale));
// Map the pixel coordinate to an imaginary-rectangle-coordinate.
// The y coordinate doesn't actually matter, as our imaginary rectangle is filled with the palette from left to right,
// so all points at a given x-coordinate have the same color.
const mathType sourceX = xtSinTheta + ytCosTheta + centerX;
// The computation was scaled just right so that the result should always be in range [0, maxXOut], but enforce this anyway
// to account for imprecision. Then scale it so that the range is [0, 255], which we can use with the palette.
int colorIndex = (std::min(std::max(sourceX, mathType(0)), maxXOut * sInt16Scale) * 255) / (sInt16Scale * maxXOut);
// inputSize determines by how much we want to scale the palette:
// values < 128 display a fraction of a palette,
// values > 128 display multiple palettes.
if (inputSize <= 128) {
colorIndex = (colorIndex * inputSize) / 128;
} else {
// Linear function that maps colorIndex 128=>1, 256=>9.
// With this function every full palette repetition is exactly 16 configuration steps wide.
// That allows displaying exactly 2 repetitions for example.
colorIndex = ((inputSize - 112) * colorIndex) / 16;
}
// Finally, shift the palette a bit.
const int paletteOffset = (!inputAnimateShift) ? (inputShift-128) : (((strip.now * ((inputShift >> 3) +1)) & 0xFFFF) >> 8);
colorIndex += paletteOffset;
const uint32_t color = SEGMENT.color_wheel((uint8_t)colorIndex);
if (isMatrix) {
blazoncek marked this conversation as resolved.
Show resolved Hide resolved
SEGMENT.setPixelColorXY(x, y, color);
} else {
SEGMENT.setPixelColor(x, color);
}
}
}

return FRAMETIME;
}
static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Cycle speed;;!;;c3=0,o2=0";
static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;c1=128,c2=128,c3=128,o1=1,o2=1,o3=0";


// WLED limitation: Analog Clock overlay will NOT work when Fire2012 is active
Expand Down
Loading