diff --git a/wled00/FX.cpp b/wled00/FX.cpp index cb337a12e6..1b95086d5c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1929,6 +1929,7 @@ static const char _data_FX_MODE_JUGGLE[] PROGMEM = "Juggle@!,Trail;;!;;sx=64,ix= uint16_t mode_palette() { + // 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; @@ -1961,35 +1962,57 @@ uint16_t mode_palette() { const bool inputAnimateRotation = SEGMENT.check2; const bool inputAssumeSquare = SEGMENT.check3; - const int paletteOffset = (!inputAnimateShift) ? (inputShift-128) : (((strip.now * ((inputShift >> 3) +1)) & 0xFFFF) >> 8); - 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); + 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(); - const int yTo = isMatrix ? maxY : yFrom; + 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 + // 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) {