diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e7429d19f1..1bc0ebdaaf 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1111,7 +1111,7 @@ uint16_t mode_running_random(void) { unsigned z = it % zoneSize; bool nzone = (!z && it != SEGENV.aux1); - for (unsigned i=SEGLEN-1; i > 0; i--) { + for (int i=SEGLEN-1; i > 0; i--) { if (nzone || z >= zoneSize) { unsigned lastrand = PRNG16 >> 8; int16_t diff = 0; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 1bbfa365bd..1ec334661a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -637,37 +637,29 @@ uint16_t IRAM_ATTR_YN Segment::nrOfVStrips() const { // Constants for mapping mode "Pinwheel" #ifndef WLED_DISABLE_2D -constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16 -constexpr int Pinwheel_Size_Small = 16; // larger than this -> use "Medium" -constexpr int Pinwheel_Steps_Medium = 192; // no holes up to 32x32 -constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big" -constexpr int Pinwheel_Steps_Big = 304; // no holes up to 50x50 -constexpr int Pinwheel_Size_Big = 50; // larger than this -> use "XL" -constexpr int Pinwheel_Steps_XL = 368; -constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360) / Pinwheel_Steps_Small; // conversion: from 0...72 to Radians -constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...192 to Radians -constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...304 to Radians -constexpr float Int_to_Rad_XL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XL; // conversion: from 0...368 to Radians - -constexpr int Fixed_Scale = 512; // fixpoint scaling factor (9bit for fraction) - -// Pinwheel helper function: pixel index to radians -static float getPinwheelAngle(int i, int vW, int vH) { - int maxXY = max(vW, vH); - if (maxXY <= Pinwheel_Size_Small) return float(i) * Int_to_Rad_Small; - if (maxXY <= Pinwheel_Size_Medium) return float(i) * Int_to_Rad_Med; - if (maxXY <= Pinwheel_Size_Big) return float(i) * Int_to_Rad_Big; - // else - return float(i) * Int_to_Rad_XL; -} +uint32_t* pinWheelBitMap = NULL; // bit-map to track drawn pixels (managed in service() ) +constexpr int Fixed_Scale = 16384; // fixpoint scaling factor (14bit for fraction) +constexpr float stepFactor = 1.6; // number of angle steps (rays = stepFacor * maxXY) + // Pinwheel helper function: matrix dimensions to number of rays -static int getPinwheelLength(int vW, int vH) { +static int getPinwheelSteps(int vW, int vH) { int maxXY = max(vW, vH); - if (maxXY <= Pinwheel_Size_Small) return Pinwheel_Steps_Small; - if (maxXY <= Pinwheel_Size_Medium) return Pinwheel_Steps_Medium; - if (maxXY <= Pinwheel_Size_Big) return Pinwheel_Steps_Big; - // else - return Pinwheel_Steps_XL; + unsigned stepfactor = unsigned(stepFactor * Fixed_Scale); + return (maxXY * stepfactor) / Fixed_Scale; +} + +static void setPinwheelParameters(int i, int vW, int vH, int& startx, int& starty, int* cosVal, int* sinVal, bool getPixel = false) { + int steps = getPinwheelSteps(vW, vH); + int baseAngle = 0xFFFF / steps; // 360° / steps, in 16 bit scale + int rotate = 0; + if(getPixel) rotate = baseAngle / 2; // rotate by half a ray width when reading pixel color + for(int k = 0; k < 2; k++) // angular steps for two consecutive rays + { + cosVal[k] = (cos16((i + k) * baseAngle + rotate) * Fixed_Scale) >> 15; // step per pixel in fixed point, cos16 output is -0x7FFF to +0x7FFF + sinVal[k] = (sin16((i + k) * baseAngle + rotate) * Fixed_Scale) >> 15; // using explicit bit shifts as dividing negative numbers is not equivalent (rounding error is acceptable) + } + startx = (vW * Fixed_Scale) / 2 + cosVal[0] / 4; // starting position = center + 1/4 pixel (in fixed point) + starty = (vH * Fixed_Scale) / 2 + sinVal[0] / 4; } #endif @@ -689,7 +681,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { vLen = sqrt16(vH*vH + vW*vW); // use diagonal break; case M12_sPinwheel: - vLen = getPinwheelLength(vW, vH); + vLen = getPinwheelSteps(vW, vH); break; default: vLen = vW * vH; // use all pixels from segment @@ -763,51 +755,84 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) for (int y = 0; y < i; y++) setPixelColorXY(i, y, col); break; case M12_sPinwheel: { - // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) - float centerX = roundf((vW-1) / 2.0f); - float centerY = roundf((vH-1) / 2.0f); - float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians - float cosVal = cos_t(angleRad); - float sinVal = sin_t(angleRad); - - // avoid re-painting the same pixel - int lastX = INT_MIN; // impossible position - int lastY = INT_MIN; // impossible position - // draw line at angle, starting at center and ending at the segment edge - // we use fixed point math for better speed. Starting distance is 0.5 for better rounding - // int_fast16_t and int_fast32_t types changed to int, minimum bits commented - int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit - int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit - int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit - int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit - - int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint - int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint - - // Odd rays start further from center if prevRay started at center. - static int prevRay = INT_MIN; // previous ray number - if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) { - int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel - posx += inc_x * jump; - posy += inc_y * jump; + if(pinWheelBitMap == NULL) break; // safety check + int startX, startY, cosVal[2], sinVal[2]; // in fixed point scale + setPinwheelParameters(i, vW, vH, startX, startY, cosVal, sinVal); + + unsigned maxLineLength = max(vW, vH) + 2; // pixels drawn is always smaller than dx or dy, +1 pair for rounding errors + uint16_t lineCoords[2][maxLineLength]; // uint16_t to save ram + int lineLength[2] = {0}; + + // draw two lines starting at at x0/y0, save the coordinates. based on idea by @Brandon502 + for (int lineNr = 0; lineNr < 2; lineNr++) { + int x0 = startX; // x / y coordinates in fixed scale + int y0 = startY; + int x1 = (startX + (cosVal[lineNr] * vW)); // outside of grid + int y1 = (startY + (sinVal[lineNr] * vH)); + const int dx = abs(x1-x0), sx = x0= vW || unsigned(y0) >= vH) break; // stop if outside of grid (exploit unsigned int overflow) + coordinates[idx++] = x0; + coordinates[idx++] = y0; + (*length)++; + // note: since endpoint is out of grid, no need to check if endpoint is reached + int e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } } - prevRay = i; - - // draw ray until we hit any edge - while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { - // scale down to integer (compiler will replace division with appropriate bitshift) - int x = posx / Fixed_Scale; - int y = posy / Fixed_Scale; - // set pixel - if (x != lastX || y != lastY) setPixelColorXY(x, y, col); // only paint if pixel position is different - lastX = x; - lastY = y; - // advance to next position - posx += inc_x; - posy += inc_y; + + // fill up the shorter line with missing coordinates, so block filling works correctly and efficiently + int diff = lineLength[0] - lineLength[1]; + int longLineIdx = (diff > 0) ? 0 : 1; + int shortLineIdx = longLineIdx ? 0 : 1; + if (diff != 0) { + int idx = (lineLength[shortLineIdx] - 1) * 2; // last valid coordinate index + int lastX = lineCoords[shortLineIdx][idx++]; + int lastY = lineCoords[shortLineIdx][idx++]; + bool keepX = lastX == 0 || lastX == vW - 1; + for (int d = 0; d < abs(diff); d++) { + lineCoords[shortLineIdx][idx] = keepX ? lastX :lineCoords[longLineIdx][idx]; + idx++; + lineCoords[shortLineIdx][idx] = keepX ? lineCoords[longLineIdx][idx] : lastY; + idx++; + } + } + + // draw and block-fill the line oordinates. Note: block filling only efficient if angle between lines is small + for (int idx = 0; idx < lineLength[longLineIdx] * 2;) { //!! should be long line idx! + int x1 = lineCoords[0][idx]; + int x2 = lineCoords[1][idx++]; + int y1 = lineCoords[0][idx]; + int y2 = lineCoords[1][idx++]; + int minX, maxX, minY, maxY; + (x1 < x2) ? (minX = x1, maxX = x2) : (minX = x2, maxX = x1); + (y1 < y2) ? (minY = y1, maxY = y2) : (minY = y2, maxY = y1); + // fill the block between the two x,y points + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + int index = y * vW + x; // calculate bitmap index, int_index, and bitmask for the current (x, y) + uint32_t int_index = index >> 5; // equivalent to index / 32 + uint32_t bitmask = 1U << (index & 31); // equivalent to index % 32 + // Check if the pixel has already been drawn + if (!(pinWheelBitMap[int_index] & bitmask)) { + pinWheelBitMap[int_index] |= bitmask; // set bit in map + setPixelColorXY(x, y, col); + } + } + } } - break; } + break; } return; } else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) { @@ -929,31 +954,17 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const break; case M12_sPinwheel: // not 100% accurate, returns pixel at outer edge - // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) - float centerX = roundf((vW-1) / 2.0f); - float centerY = roundf((vH-1) / 2.0f); - float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians - float cosVal = cos_t(angleRad); - float sinVal = sin_t(angleRad); - - int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit - int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit - int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit - int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit - int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint - int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint - - // trace ray from center until we hit any edge - to avoid rounding problems, we use the same method as in setPixelColor - int x = INT_MIN; - int y = INT_MIN; - while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { - // scale down to integer (compiler will replace division with appropriate bitshift) - x = posx / Fixed_Scale; - y = posy / Fixed_Scale; - // advance to next position - posx += inc_x; - posy += inc_y; + int x, y, cosVal[2], sinVal[2]; + setPinwheelParameters(i, vW, vH, x, y, cosVal, sinVal, true); + int maxX = (vW-1) * Fixed_Scale; + int maxY = (vH-1) * Fixed_Scale; + // trace ray from center until we hit any edge - to avoid rounding problems, we use fixed point coordinates + while ((x < maxX) && (y < maxY) && (x > Fixed_Scale) && (y > Fixed_Scale)) { + x += cosVal[0]; // advance to next position + y += sinVal[0]; } + x /= Fixed_Scale; + y /= Fixed_Scale; return getPixelColorXY(x, y); break; } @@ -1364,6 +1375,15 @@ void WS2812FX::service() { // when cctFromRgb is true we implicitly calculate WW and CW from RGB values if (cctFromRgb) BusManager::setSegmentCCT(-1); else BusManager::setSegmentCCT(seg.currentBri(true), correctWB); + + #ifndef WLED_DISABLE_2D + if (seg.is2D() && seg.map1D2D == M12_sPinwheel) { + int bitArraySize = (seg.virtualHeight() * seg.virtualWidth() + 31) / 32; // 1 bit per pixel + //uint32_t tempmap[bitArraySize] = {0}; // put the bit-map for overdraw tracking on stack + //pinWheelBitMap = tempmap; // assign global pointer Note: using stack leads to crashes on larger sizes (write access error) + pinWheelBitMap = new uint32_t[bitArraySize](); // allocate the bit-map for overdraw tracking on heap and initialize to zero + } + #endif // Effect blending // When two effects are being blended, each may have different segment data, this // data needs to be saved first and then restored before running previous mode. @@ -1392,6 +1412,10 @@ void WS2812FX::service() { seg.next_time = nowUp + delay; } _segment_index++; + #ifndef WLED_DISABLE_2D + if(pinWheelBitMap != NULL) delete[] pinWheelBitMap; // free the memory + pinWheelBitMap = NULL; // invalidate pointer + #endif } _virtualSegmentLength = 0; _isServicing = false;