@@ -687,37 +687,25 @@ unsigned Segment::virtualHeight() const {
687687
688688// Constants for mapping mode "Pinwheel"
689689#ifndef WLED_DISABLE_2D
690- constexpr int Pinwheel_Steps_Small = 72 ; // no holes up to 16x16
691- constexpr int Pinwheel_Size_Small = 16 ; // larger than this -> use "Medium"
692- constexpr int Pinwheel_Steps_Medium = 192 ; // no holes up to 32x32
693- constexpr int Pinwheel_Size_Medium = 32 ; // larger than this -> use "Big"
694- constexpr int Pinwheel_Steps_Big = 304 ; // no holes up to 50x50
695- constexpr int Pinwheel_Size_Big = 50 ; // larger than this -> use "XL"
696- constexpr int Pinwheel_Steps_XL = 368 ;
697- constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360 ) / Pinwheel_Steps_Small; // conversion: from 0...72 to Radians
698- constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360 ) / Pinwheel_Steps_Medium; // conversion: from 0...192 to Radians
699- constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360 ) / Pinwheel_Steps_Big; // conversion: from 0...304 to Radians
700- constexpr float Int_to_Rad_XL = (DEG_TO_RAD * 360 ) / Pinwheel_Steps_XL; // conversion: from 0...368 to Radians
701-
702- constexpr int Fixed_Scale = 512 ; // fixpoint scaling factor (9bit for fraction)
703-
704- // Pinwheel helper function: pixel index to radians
705- static float getPinwheelAngle (int i, int vW, int vH) {
706- int maxXY = max (vW, vH);
707- if (maxXY <= Pinwheel_Size_Small) return float (i) * Int_to_Rad_Small;
708- if (maxXY <= Pinwheel_Size_Medium) return float (i) * Int_to_Rad_Med;
709- if (maxXY <= Pinwheel_Size_Big) return float (i) * Int_to_Rad_Big;
710- // else
711- return float (i) * Int_to_Rad_XL;
712- }
690+ constexpr int Fixed_Scale = 16384 ; // fixpoint scaling factor (14bit for fraction)
713691// Pinwheel helper function: matrix dimensions to number of rays
714692static int getPinwheelLength (int vW, int vH) {
715- int maxXY = max (vW, vH);
716- if (maxXY <= Pinwheel_Size_Small) return Pinwheel_Steps_Small;
717- if (maxXY <= Pinwheel_Size_Medium) return Pinwheel_Steps_Medium;
718- if (maxXY <= Pinwheel_Size_Big) return Pinwheel_Steps_Big;
719- // else
720- return Pinwheel_Steps_XL;
693+ // Returns multiple of 8, prevents over drawing
694+ return (max (vW, vH) + 15 ) & ~7 ;
695+ }
696+ static void setPinwheelParameters (int i, int vW, int vH, int & startx, int & starty, int * cosVal, int * sinVal, bool getPixel = false ) {
697+ int steps = getPinwheelLength (vW, vH);
698+ int baseAngle = ((0xFFFF + steps / 2 ) / steps); // 360° / steps, in 16 bit scale round to nearest integer
699+ int rotate = 0 ;
700+ if (getPixel) rotate = baseAngle / 2 ; // rotate by half a ray width when reading pixel color
701+ for (int k = 0 ; k < 2 ; k++) // angular steps for two consecutive rays
702+ {
703+ int angle = (i + k) * baseAngle + rotate;
704+ cosVal[k] = (cos16_t (angle) * Fixed_Scale) >> 15 ; // step per pixel in fixed point, cos16 output is -0x7FFF to +0x7FFF
705+ sinVal[k] = (sin16_t (angle) * Fixed_Scale) >> 15 ; // using explicit bit shifts as dividing negative numbers is not equivalent (rounding error is acceptable)
706+ }
707+ startx = (vW * Fixed_Scale) / 2 ; // + cosVal[0] / 4; // starting position = center + 1/4 pixel (in fixed point)
708+ starty = (vH * Fixed_Scale) / 2 ; // + sinVal[0] / 4;
721709}
722710#endif
723711
@@ -852,55 +840,103 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const
852840 for (int x = 0 ; x <= i; x++) setPixelColorXY (x, i, col);
853841 for (int y = 0 ; y < i; y++) setPixelColorXY (i, y, col);
854842 break ;
855- case M12_sPinwheel: {
856- // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small)
857- float centerX = roundf ((vW-1 ) / 2 .0f );
858- float centerY = roundf ((vH-1 ) / 2 .0f );
859- float angleRad = getPinwheelAngle (i, vW, vH); // angle in radians
860- float cosVal = cos_t (angleRad);
861- float sinVal = sin_t (angleRad);
862-
863- // avoid re-painting the same pixel
864- int lastX = INT_MIN; // impossible position
865- int lastY = INT_MIN; // impossible position
866- // draw line at angle, starting at center and ending at the segment edge
867- // we use fixed point math for better speed. Starting distance is 0.5 for better rounding
868- // int_fast16_t and int_fast32_t types changed to int, minimum bits commented
869- int posx = (centerX + 0 .5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit
870- int posy = (centerY + 0 .5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit
871- int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit
872- int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit
873-
874- int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint
875- int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint
876-
877- // Odd rays start further from center if prevRay started at center.
878- static int prevRay = INT_MIN; // previous ray number
879- if ((i % 2 == 1 ) && (i - 1 == prevRay || i + 1 == prevRay)) {
880- int jump = min (vW/3 , vH/3 ); // can add 2 if using medium pinwheel
881- posx += inc_x * jump;
882- posy += inc_y * jump;
883- }
884- prevRay = i;
885-
886- // draw ray until we hit any edge
887- while ((posx >= 0 ) && (posy >= 0 ) && (posx < maxX) && (posy < maxY)) {
888- // scale down to integer (compiler will replace division with appropriate bitshift)
889- int x = posx / Fixed_Scale;
890- int y = posy / Fixed_Scale;
891- // set pixel
892- if (x != lastX || y != lastY) setPixelColorXY (x, y, col); // only paint if pixel position is different
893- lastX = x;
894- lastY = y;
895- // advance to next position
896- posx += inc_x;
897- posy += inc_y;
843+ case M12_sPinwheel: {
844+ // Uses Bresenham's algorithm to place coordinates of two lines in arrays then draws between them
845+ int startX, startY, cosVal[2 ], sinVal[2 ]; // in fixed point scale
846+ setPinwheelParameters (i, vW, vH, startX, startY, cosVal, sinVal);
847+
848+ unsigned maxLineLength = max (vW, vH) + 2 ; // pixels drawn is always smaller than dx or dy, +1 pair for rounding errors
849+ uint16_t lineCoords[2 ][maxLineLength]; // uint16_t to save ram
850+ int lineLength[2 ] = {0 };
851+
852+ static int prevRays[2 ] = {INT_MAX, INT_MAX}; // previous two ray numbers
853+ int closestEdgeIdx = INT_MAX; // index of the closest edge pixel
854+
855+ for (int lineNr = 0 ; lineNr < 2 ; lineNr++) {
856+ int x0 = startX; // x, y coordinates in fixed scale
857+ int y0 = startY;
858+ int x1 = (startX + (cosVal[lineNr] << 9 )); // outside of grid
859+ int y1 = (startY + (sinVal[lineNr] << 9 )); // outside of grid
860+ const int dx = abs (x1-x0), sx = x0<x1 ? 1 : -1 ; // x distance & step
861+ const int dy = -abs (y1-y0), sy = y0<y1 ? 1 : -1 ; // y distance & step
862+ uint16_t * coordinates = lineCoords[lineNr]; // 1D access is faster
863+ int * length = &lineLength[lineNr]; // faster access
864+ x0 /= Fixed_Scale; // convert to pixel coordinates
865+ y0 /= Fixed_Scale;
866+
867+ // Bresenham's algorithm
868+ int idx = 0 ;
869+ int err = dx + dy;
870+ while (true ) {
871+ if (unsigned (x0) >= vW || unsigned (y0) >= vH) {
872+ closestEdgeIdx = min (closestEdgeIdx, idx-2 );
873+ break ; // stop if outside of grid (exploit unsigned int overflow)
874+ }
875+ coordinates[idx++] = x0;
876+ coordinates[idx++] = y0;
877+ (*length)++;
878+ // note: since endpoint is out of grid, no need to check if endpoint is reached
879+ int e2 = 2 * err;
880+ if (e2 >= dy) { err += dy; x0 += sx; }
881+ if (e2 <= dx) { err += dx; y0 += sy; }
882+ }
883+ }
884+
885+ // fill up the shorter line with missing coordinates, so block filling works correctly and efficiently
886+ int diff = lineLength[0 ] - lineLength[1 ];
887+ int longLineIdx = (diff > 0 ) ? 0 : 1 ;
888+ int shortLineIdx = longLineIdx ? 0 : 1 ;
889+ if (diff != 0 ) {
890+ int idx = (lineLength[shortLineIdx] - 1 ) * 2 ; // last valid coordinate index
891+ int lastX = lineCoords[shortLineIdx][idx++];
892+ int lastY = lineCoords[shortLineIdx][idx++];
893+ bool keepX = lastX == 0 || lastX == vW - 1 ;
894+ for (int d = 0 ; d < abs (diff); d++) {
895+ lineCoords[shortLineIdx][idx] = keepX ? lastX :lineCoords[longLineIdx][idx];
896+ idx++;
897+ lineCoords[shortLineIdx][idx] = keepX ? lineCoords[longLineIdx][idx] : lastY;
898+ idx++;
899+ }
900+ }
901+
902+ // draw and block-fill the line coordinates. Note: block filling only efficient if angle between lines is small
903+ closestEdgeIdx += 2 ;
904+ int max_i = getPinwheelLength (vW, vH) - 1 ;
905+ bool drawFirst = !(prevRays[0 ] == i - 1 || (i == 0 && prevRays[0 ] == max_i)); // draw first line if previous ray was not adjacent including wrap
906+ bool drawLast = !(prevRays[0 ] == i + 1 || (i == max_i && prevRays[0 ] == 0 )); // same as above for last line
907+ for (int idx = 0 ; idx < lineLength[longLineIdx] * 2 ;) { // !! should be long line idx!
908+ int x1 = lineCoords[0 ][idx];
909+ int x2 = lineCoords[1 ][idx++];
910+ int y1 = lineCoords[0 ][idx];
911+ int y2 = lineCoords[1 ][idx++];
912+ int minX, maxX, minY, maxY;
913+ (x1 < x2) ? (minX = x1, maxX = x2) : (minX = x2, maxX = x1);
914+ (y1 < y2) ? (minY = y1, maxY = y2) : (minY = y2, maxY = y1);
915+
916+ // fill the block between the two x,y points
917+ bool alwaysDraw = (drawFirst && drawLast) || // No adjacent rays, draw all pixels
918+ (idx > closestEdgeIdx) || // Edge pixels on uneven lines are always drawn
919+ (i == 0 && idx == 2 ) || // Center pixel special case
920+ (i == prevRays[1 ]); // Effect drawing twice in 1 frame
921+ for (int x = minX; x <= maxX; x++) {
922+ for (int y = minY; y <= maxY; y++) {
923+ bool onLine1 = x == x1 && y == y1;
924+ bool onLine2 = x == x2 && y == y2;
925+ if ((alwaysDraw) ||
926+ (!onLine1 && (!onLine2 || drawLast)) || // Middle pixels and line2 if drawLast
927+ (!onLine2 && (!onLine1 || drawFirst)) // Middle pixels and line1 if drawFirst
928+ ) {
929+ setPixelColorXY (x, y, col);
930+ }
931+ }
932+ }
933+ }
934+ prevRays[1 ] = prevRays[0 ];
935+ prevRays[0 ] = i;
936+ break ;
898937 }
899- break ;
900938 }
901- }
902- _colorScaled = false ;
903- return ;
939+ return ;
904940 } else if (Segment::maxHeight != 1 && (width () == 1 || height () == 1 )) {
905941 if (start < Segment::maxWidth*Segment::maxHeight) {
906942 // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed)
@@ -1032,31 +1068,17 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
10321068 break ;
10331069 case M12_sPinwheel:
10341070 // not 100% accurate, returns pixel at outer edge
1035- // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small)
1036- float centerX = roundf ((vW-1 ) / 2 .0f );
1037- float centerY = roundf ((vH-1 ) / 2 .0f );
1038- float angleRad = getPinwheelAngle (i, vW, vH); // angle in radians
1039- float cosVal = cos_t (angleRad);
1040- float sinVal = sin_t (angleRad);
1041-
1042- int posx = (centerX + 0 .5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit
1043- int posy = (centerY + 0 .5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit
1044- int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit
1045- int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit
1046- int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint
1047- int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint
1048-
1049- // trace ray from center until we hit any edge - to avoid rounding problems, we use the same method as in setPixelColor
1050- int x = INT_MIN;
1051- int y = INT_MIN;
1052- while ((posx >= 0 ) && (posy >= 0 ) && (posx < maxX) && (posy < maxY)) {
1053- // scale down to integer (compiler will replace division with appropriate bitshift)
1054- x = posx / Fixed_Scale;
1055- y = posy / Fixed_Scale;
1056- // advance to next position
1057- posx += inc_x;
1058- posy += inc_y;
1071+ int x, y, cosVal[2 ], sinVal[2 ];
1072+ setPinwheelParameters (i, vW, vH, x, y, cosVal, sinVal, true );
1073+ int maxX = (vW-1 ) * Fixed_Scale;
1074+ int maxY = (vH-1 ) * Fixed_Scale;
1075+ // trace ray from center until we hit any edge - to avoid rounding problems, we use fixed point coordinates
1076+ while ((x < maxX) && (y < maxY) && (x > Fixed_Scale) && (y > Fixed_Scale)) {
1077+ x += cosVal[0 ]; // advance to next position
1078+ y += sinVal[0 ];
10591079 }
1080+ x /= Fixed_Scale;
1081+ y /= Fixed_Scale;
10601082 return getPixelColorXY (x, y);
10611083 break ;
10621084 }
0 commit comments