Meh Belly Lint Collection

That awful moment when you realize,
THIS is YOUR circus and THOSE are YOUR monkeys.

User Tools

Site Tools


arduino_gfx_helpers

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
arduino_gfx_helpers [2025/03/27 02:18] kensonarduino_gfx_helpers [2025/03/28 03:08] (current) kenson
Line 241: Line 241:
 </code> </code>
  
 +===== Put in practice as =====
  
-In practice +<code cpp>
-<code>+
 #include <Adafruit_GFX.h> #include <Adafruit_GFX.h>
 #include <Adafruit_ST7796S.h> #include <Adafruit_ST7796S.h>
 +#include <math.h>
 +#include <vector>
 +
 +using std::vector;
  
 #define TFT_CS         2 #define TFT_CS         2
Line 252: Line 256:
 Adafruit_ST7796S tft = Adafruit_ST7796S(TFT_CS, TFT_DC, TFT_RST); Adafruit_ST7796S tft = Adafruit_ST7796S(TFT_CS, TFT_DC, TFT_RST);
  
-#define SCREEN_WIDTH  480 +#define SCREEN_WIDTH  360 
-#define SCREEN_HEIGHT 360+#define SCREEN_HEIGHT 480
  
 GFXcanvas16 canvas(SCREEN_WIDTH, SCREEN_HEIGHT);  // Off-screen framebuffer GFXcanvas16 canvas(SCREEN_WIDTH, SCREEN_HEIGHT);  // Off-screen framebuffer
- 
-uint8_t rotate = 0; 
  
 const float PI_F     = 3.14159265; const float PI_F     = 3.14159265;
Line 263: Line 265:
 const float Z_SCALE  = 0.5; const float Z_SCALE  = 0.5;
 const float Z_OFFSET = 2.0; const float Z_OFFSET = 2.0;
-const int   NUM_SEGMENTS = 72;+const int   NUM_SEGMENTS = 60;
  
 +float baseThickness = 24.0;
 const uint16_t blackish = 0x0000; const uint16_t blackish = 0x0000;
  
 float rotation = 0.0; float rotation = 0.0;
 +
 +struct Vec3 {
 +  float x, y, z;
 +};
  
 struct Segment3D { struct Segment3D {
Line 274: Line 281:
   int thickness;   int thickness;
 }; };
-Segment3D segments[NUM_SEGMENTS]; 
  
-struct Vec3 { +// Rotate a 3D point around the X-axis. 
-  float x, y, z; +Vec3 rotateX(const Vec3& v, float angleDeg) 
-};+  float a = angleDeg * PI_F / 180.0; 
 +  float cosA = cos(a); 
 +  float sinA = sin(a); 
 +  return {  
 +    v.x,  
 +    cosA * v.- sinA * v.z 
 +    sinA * v.y + cosA * v.z  
 +  }; 
 +}
  
 +// Rotate a 3D point around the Y-axis.
 Vec3 rotateY(const Vec3& v, float angleDeg) { Vec3 rotateY(const Vec3& v, float angleDeg) {
   float a = angleDeg * PI_F / 180.0;   float a = angleDeg * PI_F / 180.0;
Line 291: Line 306:
 } }
  
-void project(const Vec3& pt, int &px, int &py) { +// Rotate a 3D point around the Z-axis. 
-  float f = 1.0 / (Z_OFFSET - pt.z); +Vec3 rotateZ(const Vec3& v, float angleDeg) { 
-  float x_proj = pt.x * f; +  float a = angleDeg * PI_F / 180.0; 
-  float y_proj = pt.y * f; +  float cosA = cos(a); 
-  px = (int)((x_proj + 1.0) * SCREEN_WIDTH / 2.0); +  float sinA = sin(a); 
-  py = (int)((1.0 - y_proj) * SCREEN_HEIGHT / 2.0);+  return { 
 +    cosA * v.x - sinA * v.y, 
 +    sinA * v.x + cosA * v.y, 
 +    v.z 
 +  }; 
 +
 + 
 +// Project a 3D point into 2D canvas coordinates. 
 +void project3D(const Vec3& pt, int centerX, int centerY, float size, int &px, int &py) { 
 +  baseThickness = size / 5; 
 +  float target_size = size - baseThickness; 
 +  float f = 1.0 / (1.15 - pt.z); 
 +  float x_proj = pt.x * f * target_size
 +  float y_proj = pt.y * f * target_size
 +  px = static_cast<int>(centerX + x_proj); 
 +  py = static_cast<int>(centerY - y_proj);
 } }
  
Line 306: Line 336:
 } }
  
-void DrawThickLineRoundedHLines(GFXcanvas16& canvas, int x1, int y1, int x2, int y2, int thickness, uint16_t color) {// Draw a thick line with rounded ends using a unified scanline fill approach. +// Draw a thick line with rounded ends using a unified scanline fill approach. 
-  // Half thickness (using integer division is fine for even thicknesses)+void DrawThickLineRoundedHLines(GFXcanvas16& canvas, int x1, int y1, int x2, int y2, int thickness, uint16_t color) {
   int r = thickness >> 1;   int r = thickness >> 1;
   int dxLine = x2 - x1;   int dxLine = x2 - x1;
   int dyLine = y2 - y1;   int dyLine = y2 - y1;
   int d2 = dxLine * dxLine + dyLine * dyLine;   int d2 = dxLine * dxLine + dyLine * dyLine;
-  +
-  // If endpoints coincide, draw a full circle.+
   if (d2 == 0) {   if (d2 == 0) {
     canvas.fillCircle(x1, y1, r, color);     canvas.fillCircle(x1, y1, r, color);
     return;     return;
   }   }
-  +
-  // Compute the length and normalized line direction.+
   float len = sqrt((float)d2);   float len = sqrt((float)d2);
   float lx = dxLine / (float)len;   float lx = dxLine / (float)len;
   float ly = dyLine / (float)len;   float ly = dyLine / (float)len;
-  
-  // Compute perpendicular offset (using (dy, -dx)) scaled to half thickness. 
   float factor = (thickness / 2.0f) / len;   float factor = (thickness / 2.0f) / len;
   float offX = factor * dyLine;   float offX = factor * dyLine;
   float offY = factor * -dxLine;   float offY = factor * -dxLine;
-  +
-  // Compute the four vertices (of the quadr) in clockwise order. +
-  // These define the main body of the line.+
   float Ax = x1 + offX, Ay = y1 + offY;   float Ax = x1 + offX, Ay = y1 + offY;
   float Bx = x1 - offX, By = y1 - offY;   float Bx = x1 - offX, By = y1 - offY;
   float Cx = x2 - offX, Cy = y2 - offY;   float Cx = x2 - offX, Cy = y2 - offY;
   float Dx = x2 + offX, Dy = y2 + offY;   float Dx = x2 + offX, Dy = y2 + offY;
-  +
-  // Determine overall bounding box (including the circular caps).+
   float bboxMinX = min(min(min(Ax, Bx), min(Cx, Dx)), (float)min(x1 - r, x2 - r));   float bboxMinX = min(min(min(Ax, Bx), min(Cx, Dx)), (float)min(x1 - r, x2 - r));
   float bboxMaxX = max(max(max(Ax, Bx), max(Cx, Dx)), (float)max(x1 + r, x2 + r));   float bboxMaxX = max(max(max(Ax, Bx), max(Cx, Dx)), (float)max(x1 + r, x2 + r));
   float bboxMinY = min(min(min(Ay, By), min(Cy, Dy)), (float)min(y1 - r, y2 - r));   float bboxMinY = min(min(min(Ay, By), min(Cy, Dy)), (float)min(y1 - r, y2 - r));
   float bboxMaxY = max(max(max(Ay, By), max(Cy, Dy)), (float)max(y1 + r, y2 + r));   float bboxMaxY = max(max(max(Ay, By), max(Cy, Dy)), (float)max(y1 + r, y2 + r));
- +
   int iMinY = (int)floor(bboxMinY);   int iMinY = (int)floor(bboxMinY);
   int iMaxY = (int)ceil(bboxMaxY);   int iMaxY = (int)ceil(bboxMaxY);
-  +
-  // Lambda to process an edge of the quadr. +
-  // Returns the x coordinate where the horizontal scanline at 'y' intersects the edge, +
-  // or NAN if there is no intersection.+
   auto processEdge = [=](float x0, float y0, float x1, float y1, int y) -> float {   auto processEdge = [=](float x0, float y0, float x1, float y1, int y) -> float {
     if ((y0 <= y && y1 > y) || (y1 <= y && y0 > y)) {     if ((y0 <= y && y1 > y) || (y1 <= y && y0 > y)) {
Line 355: Line 375:
     return NAN;     return NAN;
   };   };
-  +
-  // For each scanline, we compute up to three segments: one from the quadr body, +
-  // one from the start cap, and one from the end cap. +
-  // We then merge overlapping segments and draw them.+
   struct Segment { float x0, x1; };   struct Segment { float x0, x1; };
- +
   for (int y = iMinY; y <= iMaxY; y++) {   for (int y = iMinY; y <= iMaxY; y++) {
     Segment segments[3];     Segment segments[3];
     int segCount = 0;     int segCount = 0;
-  
-    // 1. Quadr segment: process all four edges of the quadr. 
     float inters[4];     float inters[4];
     int count = 0;     int count = 0;
Line 381: Line 396:
       segments[segCount++] = { qx0, qx1 };       segments[segCount++] = { qx0, qx1 };
     }     }
-  +
-    // 2. Start cap (centered at (x1,y1)): if y is within the circle's vertical span.+
     if (y >= y1 - r && y <= y1 + r) {     if (y >= y1 - r && y <= y1 + r) {
       float dyCap = y - y1;       float dyCap = y - y1;
-      float dxCap = sqrt((float)(r*r - dyCap*dyCap));+      float dxCap = sqrt((float)(r * r - dyCap * dyCap));
       float capStart = x1 - dxCap;       float capStart = x1 - dxCap;
       float capEnd   = x1 + dxCap;       float capEnd   = x1 + dxCap;
-      // Clip to the half circle: include only points for which dot((x-x1,y-y1),(lx,ly)) < 0. 
-      // Solve: (x - x1)*lx + (y - y1)*ly < 0  => if lx != 0, x < x1 - ((y-y1)*ly)/lx when lx > 0, 
-      // or x > x1 - ((y-y1)*ly)/lx when lx < 0. 
       if (fabs(lx) > 1e-6) {       if (fabs(lx) > 1e-6) {
         float xBoundary = x1 - ((y - y1) * ly) / lx;         float xBoundary = x1 - ((y - y1) * ly) / lx;
Line 399: Line 410:
         }         }
       } else {       } else {
-        // For near-vertical lines, if the condition isn't met, skip drawing the cap on this scanline. 
         if ((ly > 0 && y >= y1) || (ly < 0 && y <= y1)) {         if ((ly > 0 && y >= y1) || (ly < 0 && y <= y1)) {
-          capStart = 1, capEnd = 0; // empty+          capStart = 1, capEnd = 0;
         }         }
       }       }
Line 407: Line 417:
         segments[segCount++] = { capStart, capEnd };         segments[segCount++] = { capStart, capEnd };
     }     }
-  +
-    // 3. End cap (centered at (x2,y2)): if y is within the circle's vertical span.+
     if (y >= y2 - r && y <= y2 + r) {     if (y >= y2 - r && y <= y2 + r) {
       float dyCap = y - y2;       float dyCap = y - y2;
-      float dxCap = sqrt((float)(r*r - dyCap*dyCap));+      float dxCap = sqrt((float)(r * r - dyCap * dyCap));
       float capStart = x2 - dxCap;       float capStart = x2 - dxCap;
       float capEnd   = x2 + dxCap;       float capEnd   = x2 + dxCap;
-      // Clip to the half circle: include only points for which dot((x-x2,y-y2),(lx,ly)) > 0. 
       if (fabs(lx) > 1e-6) {       if (fabs(lx) > 1e-6) {
         float xBoundary = x2 - ((y - y2) * ly) / lx;         float xBoundary = x2 - ((y - y2) * ly) / lx;
Line 430: Line 438:
         segments[segCount++] = { capStart, capEnd };         segments[segCount++] = { capStart, capEnd };
     }     }
-  +
-    // If no segments were found on this scanline, continue.+
     if (segCount == 0)     if (segCount == 0)
       continue;       continue;
-  +
-    // Merge segments: sort by starting x and then combine overlapping ones. +
-    // (Since there are few segments, a simple bubble sort is sufficient.)+
     for (int i = 0; i < segCount - 1; i++) {     for (int i = 0; i < segCount - 1; i++) {
       for (int j = i + 1; j < segCount; j++) {       for (int j = i + 1; j < segCount; j++) {
Line 446: Line 451:
       }       }
     }     }
-  +
-    // Merge overlapping segments.+
     float mergedStart = segments[0].x0;     float mergedStart = segments[0].x0;
     float mergedEnd = segments[0].x1;     float mergedEnd = segments[0].x1;
     for (int i = 1; i < segCount; i++) {     for (int i = 1; i < segCount; i++) {
-      if (segments[i].x0 <= mergedEnd + 1) {  // overlapping or contiguous+      if (segments[i].x0 <= mergedEnd + 1) {
         if (segments[i].x1 > mergedEnd)         if (segments[i].x1 > mergedEnd)
           mergedEnd = segments[i].x1;           mergedEnd = segments[i].x1;
       } else {       } else {
-        // Draw the current merged segment. 
         int ix0 = (int)ceil(mergedStart);         int ix0 = (int)ceil(mergedStart);
         int ix1 = (int)floor(mergedEnd);         int ix1 = (int)floor(mergedEnd);
Line 464: Line 467:
       }       }
     }     }
-    // Draw the final merged segment. 
     int ix0 = (int)ceil(mergedStart);     int ix0 = (int)ceil(mergedStart);
     int ix1 = (int)floor(mergedEnd);     int ix1 = (int)floor(mergedEnd);
Line 472: Line 474:
 } }
  
-void setup() { +//======================================================================== 
-  tft.init(320, 480, 00, ST7796S_BGR); +// 1. Create the shape (a list of 3D points) defined by sin/cos functions. 
-  tft.setSPISpeed(40000000); +vector<Vec3> createShape() { 
-  tft.invertDisplay(true); +  vector<Vec3> shape; 
-  tft.setRotation(rotate);+  for (int i = 0; i <= NUM_SEGMENTS; ++i) { 
 +    float t = (360./ NUM_SEGMENTS* i
 +    float rad = t * PI_F / 180.0; 
 +    float y = -SCALE * (cos(rad) - 0.5 * cos(5 * rad)); 
 +    float x =  SCALE * (sin(rad) + 0.5 * sin(5 * rad)); 
 +    float z = Z_SCALE * cos(3 * rad); 
 +    shape.push_back({ x, y, z })
 +  } 
 +  return shape;
 } }
  
-void loop() { +//======================================================================== 
-  canvas.fillScreen(blackish);+// 2. Rotate the shape 
 +vector<Vec3> rotateShape(const vector<Vec3>& shape, float angleX, float angleY, float angleZ) { 
 +  vector<Vec3> rotated; 
 +  for (size_t i = 0; i < shape.size(); ++i) { 
 +    // Apply rotations in order: X, then Y, then Z. 
 +    Vec3 point = rotateX(shape[i], angleX); 
 +    point = rotateY(point, angleY); 
 +    point = rotateZ(point, angleZ); 
 +    rotated.push_back(point); 
 +  } 
 +  return rotated; 
 +}
  
-  const float baseThickness 24.0;+ 
 +//======================================================================== 
 +// 3Render the shape by projecting the rotated 3D points into 2D, building segments, 
 +//    sorting them by depth, and drawing them with thick lines. 
 +void renderShape(const vector<Vec3>& shape, int centerX, int centerY, float size) {
   int segmentIndex = 0;   int segmentIndex = 0;
-  float prev_px -1prev_py -1;+  int prev_px_i 0prev_py_i 0;
   float prev_z = 0;   float prev_z = 0;
-  int prev_px_i 0, prev_py_i = 0; +  bool firstPoint true
- +  Segment3D segmentsLocal[NUM_SEGMENTS];
-  for (int i = 0; i <= NUM_SEGMENTS; ++i) { +
-    float t = (360.0 / NUM_SEGMENTS) * i; +
-    float rad = t * PI_F / 180.0; +
- +
-    float x = SCALE * (cos(rad) - 0.5 * cos(5 * rad)); +
-    float y = SCALE * (sin(rad) + 0.5 * sin(5 * rad)); +
-    float z = Z_SCALE * cos(3 * rad); +
- +
-    Vec3 point = { x, y, z }; +
-    Vec3 rotated = rotateY(point, rotation);+
  
 +  for (size_t i = 0; i < shape.size(); i++) {
     int px, py;     int px, py;
-    project(rotated, px, py); +    project3D(shape[i], centerX, centerY, size, px, py);  
- +     
-    if (i > 0 && px >= 0 && px < SCREEN_WIDTH && py >= 0 && py < SCREEN_HEIGHT) { +    if (!firstPoint && px >= 0 && px < SCREEN_WIDTH && py >= 0 && py < SCREEN_HEIGHT) { 
-      float avgZ = (rotated.z + prev_z) / 2.0;+      float avgZ = (shape[i].z + prev_z) / 2.0;
       float perspectiveScale = 1.0f / (Z_OFFSET - avgZ);       float perspectiveScale = 1.0f / (Z_OFFSET - avgZ);
       int thickness = baseThickness * perspectiveScale;       int thickness = baseThickness * perspectiveScale;
       if (thickness < 1) thickness = 1;       if (thickness < 1) thickness = 1;
       if (thickness > 20) thickness = 20;       if (thickness > 20) thickness = 20;
- +      segmentsLocal[segmentIndex++] = { prev_px_i, prev_py_i, px, py, avgZ, thickness };
-      segments[segmentIndex++] = { +
-        prev_px_i, prev_py_i, px, py, avgZ, thickness +
-      };+
     }     }
 +     
 +    firstPoint = false;
     prev_px_i = px;     prev_px_i = px;
     prev_py_i = py;     prev_py_i = py;
-    prev_z = rotated.z;+    prev_z = shape[i].z;
   }   }
 +   
 +  // Depth sort (back to front)
   for (int i = 0; i < segmentIndex - 1; ++i) {   for (int i = 0; i < segmentIndex - 1; ++i) {
     for (int j = i + 1; j < segmentIndex; ++j) {     for (int j = i + 1; j < segmentIndex; ++j) {
-      if (segments[j].avgZ < segments[i].avgZ) { +      if (segmentsLocal[j].avgZ < segmentsLocal[i].avgZ) { 
-        Segment3D tmp = segments[i]; +        Segment3D tmp = segmentsLocal[i]; 
-        segments[i] = segments[j]; +        segmentsLocal[i] = segmentsLocal[j]; 
-        segments[j] = tmp;+        segmentsLocal[j] = tmp;
       }       }
     }     }
   }   }
 +   
 +  // Draw each segment with color based on depth.
   for (int i = 0; i < segmentIndex; ++i) {   for (int i = 0; i < segmentIndex; ++i) {
-    float depthFactor = (Z_OFFSET - segments[i].avgZ - 1.5f);+    float depthFactor = (Z_OFFSET - segmentsLocal[i].avgZ - 1.5f);
     depthFactor = constrain(depthFactor, 0.0f, 1.0f);     depthFactor = constrain(depthFactor, 0.0f, 1.0f);
-    uint8_t brightness 255 - (uint8_t)(depthFactor * 192); +    uint8_t brightnessg (uint8_t)(99.0 (depthFactor * 99.0 * 0.5)); 
-    uint16_t color = rgb888_to_565(0, brightnessbrightness); +    uint8_t brightnessb = (uint8_t)(168.0 - (depthFactor * 168.0 * 0.5)); 
 +    uint16_t color = rgb888_to_565(0, brightnessgbrightnessb);
     DrawThickLineRoundedHLines(     DrawThickLineRoundedHLines(
       canvas,       canvas,
-      segments[i].x1, segments[i].y1, +      segmentsLocal[i].x1, segmentsLocal[i].y1, 
-      segments[i].x2, segments[i].y2, +      segmentsLocal[i].x2, segmentsLocal[i].y2, 
-      segments[i].thickness,+      segmentsLocal[i].thickness,
       color       color
     );     );
   }   }
 +}
  
 +void setup() {
 +  tft.init(320, 480, 0, 0, ST7796S_BGR);
 +  tft.setSPISpeed(40000000);
 +  tft.invertDisplay(true);
 +  tft.setRotation(0);
 +}
 +
 +void loop() {
 +  canvas.fillScreen(blackish);
 +
 +  // Create the base shape only once (static initialization).
 +  static vector<Vec3> baseShape = createShape();
 +  vector<Vec3> rotatedShape = rotateShape(baseShape, rotation, rotation, rotation);
 +  renderShape(rotatedShape, 30, 30, 50);
 +  canvas.setCursor(5, 60);
 +  canvas.setTextColor(rgb888_to_565(0, 99, 168));
 +  canvas.print("EMBRIENT");
 +  
 +  // Draw the off-screen canvas onto the display.
   tft.drawRGBBitmap(0, 0, canvas.getBuffer(), SCREEN_WIDTH, SCREEN_HEIGHT);   tft.drawRGBBitmap(0, 0, canvas.getBuffer(), SCREEN_WIDTH, SCREEN_HEIGHT);
-  rotation += 2.0;+ 
 +  // Increment rotation for next frame. 
 +  rotation += 4.0;
   if (rotation >= 360.0) rotation -= 360.0;   if (rotation >= 360.0) rotation -= 360.0;
 } }
 +
 </code> </code>
arduino_gfx_helpers.1743041900.txt.gz · Last modified: 2025/03/27 02:18 by kenson

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki