rdefeo 1 год назад
Родитель
Сommit
a3005318c5
3 измененных файлов с 473 добавлено и 16 удалено
  1. 445 0
      graphics.cxx
  2. 3 0
      graphics.h
  3. 25 16
      objects.cxx

+ 445 - 0
graphics.cxx

@@ -2,6 +2,438 @@
 
 
 #define SCALE 10
 #define SCALE 10
 
 
+namespace {
+
+// Another algo - https://www.research-collection.ethz.ch/handle/20.500.11850/68976
+
+/*
+ * Thick line methods courtesy
+ * https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp 
+ */
+const int LOCAL_DISPLAY_WIDTH = 64;
+const int LOCAL_DISPLAY_HEIGHT = 128;
+/*
+ * Overlap means drawing additional pixel when changing minor direction
+ * Needed for drawThickLine, otherwise some pixels will be missing in the thick line
+ */
+const int LINE_OVERLAP_NONE = 0; // No line overlap, like in standard Bresenham
+const int LINE_OVERLAP_MAJOR =
+    0x01; // Overlap - first go major then minor direction. Pixel is drawn as extension after actual line
+const int LINE_OVERLAP_MINOR =
+    0x02; // Overlap - first go minor then major direction. Pixel is drawn as extension before next line
+const int LINE_OVERLAP_BOTH = 0x03; // Overlap - both
+
+const int LINE_THICKNESS_MIDDLE = 0; // Start point is on the line at center of the thick line
+const int LINE_THICKNESS_DRAW_CLOCKWISE = 1; // Start point is on the counter clockwise border line
+const int LINE_THICKNESS_DRAW_COUNTERCLOCKWISE = 2; // Start point is on the clockwise border line
+
+/**
+ * Draws a line from aXStart/aYStart to aXEnd/aYEnd including both ends
+ * @param aOverlap One of LINE_OVERLAP_NONE, LINE_OVERLAP_MAJOR, LINE_OVERLAP_MINOR, LINE_OVERLAP_BOTH
+ */
+void drawLineOverlap(
+    Canvas* canvas,
+    unsigned int aXStart,
+    unsigned int aYStart,
+    unsigned int aXEnd,
+    unsigned int aYEnd,
+    uint8_t aOverlap) {
+    int16_t tDeltaX, tDeltaY, tDeltaXTimes2, tDeltaYTimes2, tError, tStepX, tStepY;
+
+    /*
+     * Clip to display size
+     */
+    if(aXStart >= LOCAL_DISPLAY_WIDTH) {
+        aXStart = LOCAL_DISPLAY_WIDTH - 1;
+    }
+
+    if(aXEnd >= LOCAL_DISPLAY_WIDTH) {
+        aXEnd = LOCAL_DISPLAY_WIDTH - 1;
+    }
+
+    if(aYStart >= LOCAL_DISPLAY_HEIGHT) {
+        aYStart = LOCAL_DISPLAY_HEIGHT - 1;
+    }
+
+    if(aYEnd >= LOCAL_DISPLAY_HEIGHT) {
+        aYEnd = LOCAL_DISPLAY_HEIGHT - 1;
+    }
+
+    if((aXStart == aXEnd) || (aYStart == aYEnd)) {
+        // horizontal or vertical line -> fillRect() is faster than drawLine()
+        // fillRect(
+        //     aXStart,
+        //     aYStart,
+        //     aXEnd,
+        //     aYEnd,
+        //     aColor); // you can remove the check and this line if you have no fillRect() or drawLine() available.
+        canvas_draw_box(canvas, aXStart, aYStart, aXEnd - aXStart, aYEnd - aYStart);
+    } else {
+        // calculate direction
+        tDeltaX = aXEnd - aXStart;
+        tDeltaY = aYEnd - aYStart;
+        if(tDeltaX < 0) {
+            tDeltaX = -tDeltaX;
+            tStepX = -1;
+        } else {
+            tStepX = +1;
+        }
+        if(tDeltaY < 0) {
+            tDeltaY = -tDeltaY;
+            tStepY = -1;
+        } else {
+            tStepY = +1;
+        }
+        tDeltaXTimes2 = tDeltaX << 1;
+        tDeltaYTimes2 = tDeltaY << 1;
+        // draw start pixel
+        // drawPixel(aXStart, aYStart, aColor);
+        canvas_draw_dot(canvas, aXStart, aYStart);
+        if(tDeltaX > tDeltaY) {
+            // start value represents a half step in Y direction
+            tError = tDeltaYTimes2 - tDeltaX;
+            while(aXStart != aXEnd) {
+                // step in main direction
+                aXStart += tStepX;
+                if(tError >= 0) {
+                    if(aOverlap & LINE_OVERLAP_MAJOR) {
+                        // draw pixel in main direction before changing
+                        // drawPixel(aXStart, aYStart, aColor);
+                        canvas_draw_dot(canvas, aXStart, aYStart);
+                    }
+                    // change Y
+                    aYStart += tStepY;
+                    if(aOverlap & LINE_OVERLAP_MINOR) {
+                        // draw pixel in minor direction before changing
+                        // drawPixel(aXStart - tStepX, aYStart, aColor);
+                        canvas_draw_dot(canvas, aXStart - tStepX, aYStart);
+                    }
+                    tError -= tDeltaXTimes2;
+                }
+                tError += tDeltaYTimes2;
+                // drawPixel(aXStart, aYStart, aColor);
+                canvas_draw_dot(canvas, aXStart, aYStart);
+            }
+        } else {
+            tError = tDeltaXTimes2 - tDeltaY;
+            while(aYStart != aYEnd) {
+                aYStart += tStepY;
+                if(tError >= 0) {
+                    if(aOverlap & LINE_OVERLAP_MAJOR) {
+                        // draw pixel in main direction before changing
+                        // drawPixel(aXStart, aYStart, aColor);
+                        canvas_draw_dot(canvas, aXStart, aYStart);
+                    }
+                    aXStart += tStepX;
+                    if(aOverlap & LINE_OVERLAP_MINOR) {
+                        // draw pixel in minor direction before changing
+                        // drawPixel(aXStart, aYStart - tStepY, aColor);
+                        canvas_draw_dot(canvas, aXStart, aYStart - tStepY);
+                    }
+                    tError -= tDeltaYTimes2;
+                }
+                tError += tDeltaXTimes2;
+                // drawPixel(aXStart, aYStart, aColor);
+                canvas_draw_dot(canvas, aXStart, aYStart);
+            }
+        }
+    }
+}
+
+/**
+ * Bresenham with thickness
+ * No pixel missed and every pixel only drawn once!
+ * The code is bigger and more complicated than drawThickLineSimple() but it tends to be faster, since drawing a pixel is often a slow operation.
+ * aThicknessMode can be one of LINE_THICKNESS_MIDDLE, LINE_THICKNESS_DRAW_CLOCKWISE, LINE_THICKNESS_DRAW_COUNTERCLOCKWISE
+ */
+void drawThickLine(
+    Canvas* canvas,
+    unsigned int aXStart,
+    unsigned int aYStart,
+    unsigned int aXEnd,
+    unsigned int aYEnd,
+    unsigned int aThickness,
+    uint8_t aThicknessMode) {
+    int16_t i, tDeltaX, tDeltaY, tDeltaXTimes2, tDeltaYTimes2, tError, tStepX, tStepY;
+
+    if(aThickness <= 1) {
+        drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE);
+    }
+    /*
+     * Clip to display size
+     */
+    if(aXStart >= LOCAL_DISPLAY_WIDTH) {
+        aXStart = LOCAL_DISPLAY_WIDTH - 1;
+    }
+
+    if(aXEnd >= LOCAL_DISPLAY_WIDTH) {
+        aXEnd = LOCAL_DISPLAY_WIDTH - 1;
+    }
+
+    if(aYStart >= LOCAL_DISPLAY_HEIGHT) {
+        aYStart = LOCAL_DISPLAY_HEIGHT - 1;
+    }
+
+    if(aYEnd >= LOCAL_DISPLAY_HEIGHT) {
+        aYEnd = LOCAL_DISPLAY_HEIGHT - 1;
+    }
+
+    /**
+     * For coordinate system with 0.0 top left
+     * Swap X and Y delta and calculate clockwise (new delta X inverted)
+     * or counterclockwise (new delta Y inverted) rectangular direction.
+     * The right rectangular direction for LINE_OVERLAP_MAJOR toggles with each octant
+     */
+    tDeltaY = aXEnd - aXStart;
+    tDeltaX = aYEnd - aYStart;
+    // mirror 4 quadrants to one and adjust deltas and stepping direction
+    bool tSwap = true; // count effective mirroring
+    if(tDeltaX < 0) {
+        tDeltaX = -tDeltaX;
+        tStepX = -1;
+        tSwap = !tSwap;
+    } else {
+        tStepX = +1;
+    }
+    if(tDeltaY < 0) {
+        tDeltaY = -tDeltaY;
+        tStepY = -1;
+        tSwap = !tSwap;
+    } else {
+        tStepY = +1;
+    }
+    tDeltaXTimes2 = tDeltaX << 1;
+    tDeltaYTimes2 = tDeltaY << 1;
+    bool tOverlap;
+    // adjust for right direction of thickness from line origin
+    int tDrawStartAdjustCount = aThickness / 2;
+    if(aThicknessMode == LINE_THICKNESS_DRAW_COUNTERCLOCKWISE) {
+        tDrawStartAdjustCount = aThickness - 1;
+    } else if(aThicknessMode == LINE_THICKNESS_DRAW_CLOCKWISE) {
+        tDrawStartAdjustCount = 0;
+    }
+
+    /*
+     * Now tDelta* are positive and tStep* define the direction
+     * tSwap is false if we mirrored only once
+     */
+    // which octant are we now
+    if(tDeltaX >= tDeltaY) {
+        // Octant 1, 3, 5, 7 (between 0 and 45, 90 and 135, ... degree)
+        if(tSwap) {
+            tDrawStartAdjustCount = (aThickness - 1) - tDrawStartAdjustCount;
+            tStepY = -tStepY;
+        } else {
+            tStepX = -tStepX;
+        }
+        /*
+         * Vector for draw direction of the starting points of lines is rectangular and counterclockwise to main line direction
+         * Therefore no pixel will be missed if LINE_OVERLAP_MAJOR is used on change in minor rectangular direction
+         */
+        // adjust draw start point
+        tError = tDeltaYTimes2 - tDeltaX;
+        for(i = tDrawStartAdjustCount; i > 0; i--) {
+            // change X (main direction here)
+            aXStart -= tStepX;
+            aXEnd -= tStepX;
+            if(tError >= 0) {
+                // change Y
+                aYStart -= tStepY;
+                aYEnd -= tStepY;
+                tError -= tDeltaXTimes2;
+            }
+            tError += tDeltaYTimes2;
+        }
+        // draw start line. We can alternatively use drawLineOverlap(aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE, aColor) here.
+        // drawLine(aXStart, aYStart, aXEnd, aYEnd);
+        // canvas_draw_line(canvas, aXStart, aYStart, aXEnd, aYEnd);
+        drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE);
+        // draw aThickness number of lines
+        tError = tDeltaYTimes2 - tDeltaX;
+        for(i = aThickness; i > 1; i--) {
+            // change X (main direction here)
+            aXStart += tStepX;
+            aXEnd += tStepX;
+            tOverlap = LINE_OVERLAP_NONE;
+            if(tError >= 0) {
+                // change Y
+                aYStart += tStepY;
+                aYEnd += tStepY;
+                tError -= tDeltaXTimes2;
+                /*
+                 * Change minor direction reverse to line (main) direction
+                 * because of choosing the right (counter)clockwise draw vector
+                 * Use LINE_OVERLAP_MAJOR to fill all pixel
+                 *
+                 * EXAMPLE:
+                 * 1,2 = Pixel of first 2 lines
+                 * 3 = Pixel of third line in normal line mode
+                 * - = Pixel which will additionally be drawn in LINE_OVERLAP_MAJOR mode
+                 *           33
+                 *       3333-22
+                 *   3333-222211
+                 * 33-22221111
+                 *  221111                     ^
+                 *  11                          Main direction of start of lines draw vector
+                 *  -> Line main direction
+                 *  <- Minor direction of counterclockwise of start of lines draw vector
+                 */
+                tOverlap = LINE_OVERLAP_MAJOR;
+            }
+            tError += tDeltaYTimes2;
+            drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, tOverlap);
+        }
+    } else {
+        // the other octant 2, 4, 6, 8 (between 45 and 90, 135 and 180, ... degree)
+        if(tSwap) {
+            tStepX = -tStepX;
+        } else {
+            tDrawStartAdjustCount = (aThickness - 1) - tDrawStartAdjustCount;
+            tStepY = -tStepY;
+        }
+        // adjust draw start point
+        tError = tDeltaXTimes2 - tDeltaY;
+        for(i = tDrawStartAdjustCount; i > 0; i--) {
+            aYStart -= tStepY;
+            aYEnd -= tStepY;
+            if(tError >= 0) {
+                aXStart -= tStepX;
+                aXEnd -= tStepX;
+                tError -= tDeltaYTimes2;
+            }
+            tError += tDeltaXTimes2;
+        }
+        //draw start line
+        // drawLine(aXStart, aYStart, aXEnd, aYEnd);
+        canvas_draw_line(canvas, aXStart, aYStart, aXEnd, aYEnd);
+        // draw aThickness number of lines
+        tError = tDeltaXTimes2 - tDeltaY;
+        for(i = aThickness; i > 1; i--) {
+            aYStart += tStepY;
+            aYEnd += tStepY;
+            tOverlap = LINE_OVERLAP_NONE;
+            if(tError >= 0) {
+                aXStart += tStepX;
+                aXEnd += tStepX;
+                tError -= tDeltaYTimes2;
+                tOverlap = LINE_OVERLAP_MAJOR;
+            }
+            tError += tDeltaXTimes2;
+            drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, tOverlap);
+        }
+    }
+}
+/**
+ * The same as before, but no clipping to display range, some pixel are drawn twice (because of using LINE_OVERLAP_BOTH)
+ * and direction of thickness changes for each octant (except for LINE_THICKNESS_MIDDLE and aThickness value is odd)
+ * aThicknessMode can be LINE_THICKNESS_MIDDLE or any other value
+ *
+ */
+/*
+void drawThickLineSimple(
+    Canvas* canvas,
+    unsigned int aXStart,
+    unsigned int aYStart,
+    unsigned int aXEnd,
+    unsigned int aYEnd,
+    unsigned int aThickness,
+    uint8_t aThicknessMode) {
+    int16_t i, tDeltaX, tDeltaY, tDeltaXTimes2, tDeltaYTimes2, tError, tStepX, tStepY;
+
+    tDeltaY = aXStart - aXEnd;
+    tDeltaX = aYEnd - aYStart;
+    // mirror 4 quadrants to one and adjust deltas and stepping direction
+    if(tDeltaX < 0) {
+        tDeltaX = -tDeltaX;
+        tStepX = -1;
+    } else {
+        tStepX = +1;
+    }
+    if(tDeltaY < 0) {
+        tDeltaY = -tDeltaY;
+        tStepY = -1;
+    } else {
+        tStepY = +1;
+    }
+    tDeltaXTimes2 = tDeltaX << 1;
+    tDeltaYTimes2 = tDeltaY << 1;
+    bool tOverlap;
+    // which octant are we now
+    if(tDeltaX > tDeltaY) {
+        if(aThicknessMode == LINE_THICKNESS_MIDDLE) {
+            // adjust draw start point
+            tError = tDeltaYTimes2 - tDeltaX;
+            for(i = aThickness / 2; i > 0; i--) {
+                // change X (main direction here)
+                aXStart -= tStepX;
+                aXEnd -= tStepX;
+                if(tError >= 0) {
+                    // change Y
+                    aYStart -= tStepY;
+                    aYEnd -= tStepY;
+                    tError -= tDeltaXTimes2;
+                }
+                tError += tDeltaYTimes2;
+            }
+        }
+        // draw start line. We can alternatively use drawLineOverlap(aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE, aColor) here.
+        // drawLine(aXStart, aYStart, aXEnd, aYEnd, aColor);
+        canvas_draw_line(canvas, aXStart, aYStart, aXEnd, aYEnd);
+        // draw aThickness lines
+        tError = tDeltaYTimes2 - tDeltaX;
+        for(i = aThickness; i > 1; i--) {
+            // change X (main direction here)
+            aXStart += tStepX;
+            aXEnd += tStepX;
+            tOverlap = LINE_OVERLAP_NONE;
+            if(tError >= 0) {
+                // change Y
+                aYStart += tStepY;
+                aYEnd += tStepY;
+                tError -= tDeltaXTimes2;
+                tOverlap = LINE_OVERLAP_BOTH;
+            }
+            tError += tDeltaYTimes2;
+            drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, tOverlap);
+        }
+    } else {
+        // adjust draw start point
+        if(aThicknessMode == LINE_THICKNESS_MIDDLE) {
+            tError = tDeltaXTimes2 - tDeltaY;
+            for(i = aThickness / 2; i > 0; i--) {
+                aYStart -= tStepY;
+                aYEnd -= tStepY;
+                if(tError >= 0) {
+                    aXStart -= tStepX;
+                    aXEnd -= tStepX;
+                    tError -= tDeltaYTimes2;
+                }
+                tError += tDeltaXTimes2;
+            }
+        }
+        // draw start line. We can alternatively use drawLineOverlap(aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE, aColor) here.
+        // drawLine(aXStart, aYStart, aXEnd, aYEnd, aColor);
+        canvas_draw_line(canvas, aXStart, aYStart, aXEnd, aYEnd);
+        tError = tDeltaXTimes2 - tDeltaY;
+        for(i = aThickness; i > 1; i--) {
+            aYStart += tStepY;
+            aYEnd += tStepY;
+            tOverlap = LINE_OVERLAP_NONE;
+            if(tError >= 0) {
+                aXStart += tStepX;
+                aXEnd += tStepX;
+                tError -= tDeltaYTimes2;
+                tOverlap = LINE_OVERLAP_BOTH;
+            }
+            tError += tDeltaXTimes2;
+            drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, tOverlap);
+        }
+    }
+}
+*/
+
+}; // namespace
+
 /*
 /*
   Fontname: micro
   Fontname: micro
   Copyright: Public domain font.  Share and enjoy.
   Copyright: Public domain font.  Share and enjoy.
@@ -26,6 +458,19 @@ void gfx_draw_line(Canvas* canvas, const Vec2& p1, const Vec2& p2) {
     gfx_draw_line(canvas, p1.x, p1.y, p2.x, p2.y);
     gfx_draw_line(canvas, p1.x, p1.y, p2.x, p2.y);
 }
 }
 
 
+void gfx_draw_line_thick(Canvas* canvas, float x1, float y1, float x2, float y2, int thickness) {
+    x1 = roundf(x1 / SCALE);
+    y1 = roundf(y1 / SCALE);
+    x2 = roundf(x2 / SCALE);
+    y2 = roundf(y2 / SCALE);
+
+    drawThickLine(canvas, x1, y1, x2, y2, thickness, LINE_THICKNESS_MIDDLE);
+}
+
+void gfx_draw_line_thick(Canvas* canvas, const Vec2& p1, const Vec2& p2, int thickness) {
+    gfx_draw_line_thick(canvas, p1.x, p1.y, p2.x, p2.y, thickness);
+}
+
 void gfx_draw_disc(Canvas* canvas, float x, float y, float r) {
 void gfx_draw_disc(Canvas* canvas, float x, float y, float r) {
     canvas_draw_disc(canvas, roundf(x / SCALE), roundf(y / SCALE), roundf(r / SCALE));
     canvas_draw_disc(canvas, roundf(x / SCALE), roundf(y / SCALE), roundf(r / SCALE));
 }
 }

+ 3 - 0
graphics.h

@@ -11,6 +11,9 @@
 void gfx_draw_line(Canvas* canvas, float x1, float y1, float x2, float y2);
 void gfx_draw_line(Canvas* canvas, float x1, float y1, float x2, float y2);
 void gfx_draw_line(Canvas* canvas, const Vec2& p1, const Vec2& p2);
 void gfx_draw_line(Canvas* canvas, const Vec2& p1, const Vec2& p2);
 
 
+void gfx_draw_line_thick(Canvas* canvas, float x1, float y1, float x2, float y2, int thickness);
+void gfx_draw_line_thick(Canvas* canvas, const Vec2& p1, const Vec2& p2, int thickness);
+
 void gfx_draw_disc(Canvas* canvas, float x, float y, float r);
 void gfx_draw_disc(Canvas* canvas, float x, float y, float r);
 void gfx_draw_disc(Canvas* canvas, const Vec2& p, float r);
 void gfx_draw_disc(Canvas* canvas, const Vec2& p, float r);
 
 

+ 25 - 16
objects.cxx

@@ -53,28 +53,37 @@ Flipper::Flipper(const Vec2& p_, Side side_, size_t size_)
 }
 }
 
 
 void Flipper::draw(Canvas* canvas) {
 void Flipper::draw(Canvas* canvas) {
-    // base / pivot
-    gfx_draw_circle(canvas, p, r);
-
     // tip
     // tip
     float angle = rest_angle + sign * rotation;
     float angle = rest_angle + sign * rotation;
     Vec2 dir(cos(angle), -sin(angle));
     Vec2 dir(cos(angle), -sin(angle));
 
 
     // draw the tip
     // draw the tip
     Vec2 tip = p + dir * size;
     Vec2 tip = p + dir * size;
-    gfx_draw_circle(canvas, tip, r);
-
-    // top and bottom lines
-    Vec2 perp(-dir.y, dir.x);
-    perp.normalize();
-    Vec2 start = p + perp * r;
-    Vec2 end = start + dir * size;
-    gfx_draw_line(canvas, start, end);
-
-    perp *= -1.0f;
-    start = p + perp * r;
-    end = start + dir * size;
-    gfx_draw_line(canvas, start, end);
+    gfx_draw_line_thick(canvas, p, tip, (r * 1.5f) / 10.0f);
+    gfx_draw_disc(canvas, tip, r * 0.6f);
+
+    // // base / pivot
+    // gfx_draw_circle(canvas, p, r);
+
+    // // tip
+    // float angle = rest_angle + sign * rotation;
+    // Vec2 dir(cos(angle), -sin(angle));
+
+    // // draw the tip
+    // Vec2 tip = p + dir * size;
+    // gfx_draw_circle(canvas, tip, r);
+
+    // // top and bottom lines
+    // Vec2 perp(-dir.y, dir.x);
+    // perp.normalize();
+    // Vec2 start = p + perp * r;
+    // Vec2 end = start + dir * size;
+    // gfx_draw_line(canvas, start, end);
+
+    // perp *= -1.0f;
+    // start = p + perp * r;
+    // end = start + dir * size;
+    // gfx_draw_line(canvas, start, end);
 }
 }
 
 
 void Flipper::update(float dt) {
 void Flipper::update(float dt) {