acidportal- 😈 Worlds smallest Evil Portal on a LilyGo T-QT |
git clone git://git.acid.vegas/acidportal.git |
Log | Files | Refs | Archive | README | LICENSE |
SpriteRotatingCube.ino (11451B)
1 // TFT_eSPI library demo, principally for STM32F processors with DMA: 2 // https://en.wikipedia.org/wiki/Direct_memory_access 3 4 // Tested with ESP32, Nucleo 64 STM32F446RE and Nucleo 144 STM32F767ZI 5 // TFT's with SPI can use DMA, the sketch also works with 8 bit 6 // parallel TFT's (tested with ILI9341 and ILI9481) 7 8 // The sketch will run on processors without DMA and also parallel 9 // interface TFT's. Comment out line 29 for no DMA. 10 11 // Library here: 12 // https://github.com/Bodmer/TFT_eSPI 13 14 // Adapted by Bodmer 18/12/19 from "RotatingCube" by Daniel Eichhorn. 15 // See MIT License at end of sketch. 16 17 // The rotating cube is drawn into a 128 x 128 Sprite and then this is 18 // rendered to screen. The Sprite need 32Kbytes of RAM and DMA buffer the same 19 // so processors with at least >64Kbytes RAM free will be required. 20 21 // Define to use DMA for Sprite transfer to SPI TFT, comment out to use no DMA 22 // (Tested with Nucleo 64 STM32F446RE and Nucleo 144 STM32F767ZI) 23 // STM32F767 27MHz SPI 50% processor load: Non DMA 52 fps, with DMA 101 fps 24 // STM32F767 27MHz SPI 0% processor load: Non DMA 97 fps, with DMA 102 fps 25 26 // ESP32 27MHz SPI 0% processor load: Non DMA 90 fps, with DMA 101 fps 27 // ESP32 40MHz SPI 0% processor load: Non DMA 127 fps, with DMA 145 fps 28 // NOTE: FOR SPI DISPLAYS ONLY 29 #define USE_DMA_TO_TFT 30 31 // Show a processing load does not impact rendering performance 32 // Processing load is simple algorithm to calculate prime numbers 33 //#define PRIME_NUMBER_PROCESSOR_LOAD 491 // 241 = 50% CPU load for 128 * 128 and STM32F466 Nucleo 64 34 // 491 = 50% CPU load for 128 * 128 and STM32F767 Nucleo 144 35 36 // Color depth has to be 16 bits if DMA is used to render image 37 #define COLOR_DEPTH 16 38 39 // 128x128 for a 16 bit colour Sprite (32Kbytes RAM) 40 // Maximum is 181x181 (64Kbytes) for DMA - restricted by processor design 41 #define IWIDTH 128 42 #define IHEIGHT 128 43 44 // Size of cube image 45 // 358 is max for 128x128 sprite, too big and pixel trails are drawn... 46 #define CUBE_SIZE 358 47 48 #include <TFT_eSPI.h> 49 50 // Library instance 51 TFT_eSPI tft = TFT_eSPI(); // Declare object "tft" 52 53 // Create two sprites for a DMA toggle buffer 54 TFT_eSprite spr[2] = {TFT_eSprite(&tft), TFT_eSprite(&tft) }; 55 56 // Toggle buffer selection 57 bool sprSel = 0; 58 59 // Pointers to start of Sprites in RAM 60 uint16_t* sprPtr[2]; 61 62 // Define the cube face colors 63 uint16_t palette[] = {TFT_WHITE, // 1 64 TFT_GREENYELLOW, // 2 65 TFT_YELLOW, // 3 66 TFT_PINK, // 4 67 TFT_MAGENTA, // 5 68 TFT_CYAN // 6 69 }; 70 71 // Used for fps measuring 72 uint16_t counter = 0; 73 long startMillis = millis(); 74 uint16_t interval = 100; 75 76 // size / 2 of cube edge 77 float d = 15; 78 float px[] = { 79 -d, d, d, -d, -d, d, d, -d 80 }; 81 float py[] = { 82 -d, -d, d, d, -d, -d, d, d 83 }; 84 float pz[] = { 85 -d, -d, -d, -d, d, d, d, d 86 }; 87 88 // Define the triangles 89 // The order of the vertices MUST be CCW or the 90 // shoelace method won't work to detect visible edges 91 int faces[12][3] = { 92 {0, 1, 4}, 93 {1, 5, 4}, 94 {1, 2, 5}, 95 {2, 6, 5}, 96 {5, 7, 4}, 97 {6, 7, 5}, 98 {3, 4, 7}, 99 {4, 3, 0}, 100 {0, 3, 1}, 101 {1, 3, 2}, 102 {2, 3, 6}, 103 {6, 3, 7} 104 }; 105 106 // mapped coordinates on screen 107 float p2x[] = { 108 0, 0, 0, 0, 0, 0, 0, 0 109 }; 110 float p2y[] = { 111 0, 0, 0, 0, 0, 0, 0, 0 112 }; 113 114 // rotation angle in radians 115 float r[] = { 116 0, 0, 0 117 }; 118 119 // Frames per second 120 String fps = "0fps"; 121 122 // Sprite draw position 123 int16_t xpos = 0; 124 int16_t ypos = 0; 125 126 // Prime number initial value 127 int prime_max = 2; 128 129 // 3 axis spin control 130 bool spinX = true; 131 bool spinY = true; 132 bool spinZ = true; 133 134 // Min and max of cube edges, "int" type used for compatibility with original sketch min() function 135 int xmin,ymin,xmax,ymax; 136 137 /////////////////////////////////////////////////////////// setup /////////////////////////////////////////////////// 138 void setup() { 139 140 Serial.begin(115200); 141 142 tft.init(); 143 144 tft.fillScreen(TFT_BLACK); 145 146 xpos = 0; 147 ypos = (tft.height() - IHEIGHT) / 2; 148 149 // Define cprite colour depth 150 spr[0].setColorDepth(COLOR_DEPTH); 151 spr[1].setColorDepth(COLOR_DEPTH); 152 153 // Create the 2 sprites 154 sprPtr[0] = (uint16_t*)spr[0].createSprite(IWIDTH, IHEIGHT); 155 sprPtr[1] = (uint16_t*)spr[1].createSprite(IWIDTH, IHEIGHT); 156 157 // Define text datum and text colour for Sprites 158 spr[0].setTextColor(TFT_BLACK); 159 spr[0].setTextDatum(MC_DATUM); 160 spr[1].setTextColor(TFT_BLACK); 161 spr[1].setTextDatum(MC_DATUM); 162 163 #ifdef USE_DMA_TO_TFT 164 // DMA - should work with ESP32, STM32F2xx/F4xx/F7xx processors 165 // NOTE: >>>>>> DMA IS FOR SPI DISPLAYS ONLY <<<<<< 166 tft.initDMA(); // Initialise the DMA engine (tested with STM32F446 and STM32F767) 167 #endif 168 169 // Animation control timer 170 startMillis = millis(); 171 172 } 173 174 /////////////////////////////////////////////////////////// loop /////////////////////////////////////////////////// 175 void loop() { 176 uint32_t updateTime = 0; // time for next update 177 bool bounce = false; 178 int wait = 0; //random (20); 179 180 // Random movement direction 181 int dx = 1; if (random(2)) dx = -1; 182 int dy = 1; if (random(2)) dy = -1; 183 184 // Grab exclusive use of the SPI bus 185 tft.startWrite(); 186 187 // Loop forever 188 while (1) { 189 190 // Pull it back onto screen if it wanders off 191 if (xpos < -xmin) { 192 dx = 1; 193 bounce = true; 194 } 195 if (xpos >= tft.width() - xmax) { 196 dx = -1; 197 bounce = true; 198 } 199 if (ypos < -ymin) { 200 dy = 1; 201 bounce = true; 202 } 203 if (ypos >= tft.height() - ymax) { 204 dy = -1; 205 bounce = true; 206 } 207 208 if (bounce) { 209 // Randomise spin 210 if (random(2)) spinX = true; 211 else spinX = false; 212 if (random(2)) spinY = true; 213 else spinY = false; 214 if (random(2)) spinZ = true; 215 else spinZ = false; 216 bounce = false; 217 //wait = random (20); 218 } 219 220 if (updateTime <= millis()) 221 { 222 // Use time delay so sprtie does not move fast when not all on screen 223 updateTime = millis() + wait; 224 xmin = IWIDTH / 2; xmax = IWIDTH / 2; ymin = IHEIGHT / 2; ymax = IHEIGHT / 2; 225 drawCube(); 226 227 #ifdef USE_DMA_TO_TFT 228 if (tft.dmaBusy()) prime_max++; // Increase processing load until just not busy 229 tft.pushImageDMA(xpos, ypos, IWIDTH, IHEIGHT, sprPtr[sprSel]); 230 sprSel = !sprSel; 231 #else 232 #ifdef PRIME_NUMBER_PROCESSOR_LOAD 233 prime_max = PRIME_NUMBER_PROCESSOR_LOAD; 234 #endif 235 spr[sprSel].pushSprite(xpos, ypos); // Blocking write (no DMA) 115fps 236 #endif 237 238 counter++; 239 // only calculate the fps every <interval> iterations. 240 if (counter % interval == 0) { 241 long millisSinceUpdate = millis() - startMillis; 242 fps = String((int)(interval * 1000.0 / (millisSinceUpdate))) + " fps"; 243 Serial.println(fps); 244 startMillis = millis(); 245 } 246 #ifdef PRIME_NUMBER_PROCESSOR_LOAD 247 // Add a processor task 248 uint32_t pr = computePrimeNumbers(prime_max); 249 Serial.print("Biggest = "); Serial.println(pr); 250 #endif 251 // Change coord for next loop 252 xpos += dx; 253 ypos += dy; 254 } 255 } // End of forever loop 256 257 // Release exclusive use of SPI bus ( here as a reminder... forever loop prevents execution) 258 tft.endWrite(); 259 } 260 261 /** 262 Detected visible triangles. If calculated area > 0 the triangle 263 is rendered facing towards the viewer, since the vertices are CCW. 264 If the area is negative the triangle is CW and thus facing away from us. 265 */ 266 int shoelace(int x1, int y1, int x2, int y2, int x3, int y3) { 267 // (x1y2 - y1x2) + (x2y3 - y2x3) 268 return x1 * y2 - y1 * x2 + x2 * y3 - y2 * x3 + x3 * y1 - y3 * x1; 269 } 270 271 /** 272 Rotates and renders the cube. 273 **/ 274 void drawCube() 275 { 276 double speed = 90; 277 if (spinX) r[0] = r[0] + PI / speed; // Add a degree 278 if (spinY) r[1] = r[1] + PI / speed; // Add a degree 279 if (spinZ) r[2] = r[2] + PI / speed; // Add a degree 280 281 if (r[0] >= 360.0 * PI / 90.0) r[0] = 0; 282 if (r[1] >= 360.0 * PI / 90.0) r[1] = 0; 283 if (r[2] >= 360.0 * PI / 90.0) r[2] = 0; 284 285 float ax[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 286 float ay[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 287 float az[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 288 289 // Calculate all vertices of the cube 290 for (int i = 0; i < 8; i++) 291 { 292 float px2 = px[i]; 293 float py2 = cos(r[0]) * py[i] - sin(r[0]) * pz[i]; 294 float pz2 = sin(r[0]) * py[i] + cos(r[0]) * pz[i]; 295 296 float px3 = cos(r[1]) * px2 + sin(r[1]) * pz2; 297 float py3 = py2; 298 float pz3 = -sin(r[1]) * px2 + cos(r[1]) * pz2; 299 300 ax[i] = cos(r[2]) * px3 - sin(r[2]) * py3; 301 ay[i] = sin(r[2]) * px3 + cos(r[2]) * py3; 302 az[i] = pz3 - 150; 303 304 p2x[i] = IWIDTH / 2 + ax[i] * CUBE_SIZE / az[i]; 305 p2y[i] = IHEIGHT / 2 + ay[i] * CUBE_SIZE / az[i]; 306 } 307 308 // Fill the buffer with colour 0 (Black) 309 spr[sprSel].fillSprite(TFT_BLACK); 310 311 for (int i = 0; i < 12; i++) { 312 313 if (shoelace(p2x[faces[i][0]], p2y[faces[i][0]], p2x[faces[i][1]], p2y[faces[i][1]], p2x[faces[i][2]], p2y[faces[i][2]]) > 0) { 314 int x0 = p2x[faces[i][0]]; 315 int y0 = p2y[faces[i][0]]; 316 int x1 = p2x[faces[i][1]]; 317 int y1 = p2y[faces[i][1]]; 318 int x2 = p2x[faces[i][2]]; 319 int y2 = p2y[faces[i][2]]; 320 321 xmin = min(xmin, x0); 322 ymin = min(ymin, y0); 323 xmin = min(xmin, x1); 324 ymin = min(ymin, y1); 325 xmin = min(xmin, x2); 326 ymin = min(ymin, y2); 327 xmax = max(xmax, x0); 328 ymax = max(ymax, y0); 329 xmax = max(xmax, x1); 330 ymax = max(ymax, y1); 331 xmax = max(xmax, x2); 332 ymax = max(ymax, y2); 333 334 spr[sprSel].fillTriangle(x0, y0, x1, y1, x2, y2, palette[i / 2]); 335 if (i % 2) { 336 int avX = 0; 337 int avY = 0; 338 for (int v = 0; v < 3; v++) { 339 avX += p2x[faces[i][v]]; 340 avY += p2y[faces[i][v]]; 341 } 342 avX = avX / 3; 343 avY = avY / 3; 344 } 345 } 346 } 347 348 //spr[sprSel].drawString(fps, IWIDTH / 2, IHEIGHT / 2, 4); 349 //delay(100); 350 } 351 352 // This is to provide a processing load to see the improvement DMA gives 353 uint32_t computePrimeNumbers(int32_t n) { 354 if (n<2) return 1; 355 356 int32_t i, fact, j, p = 0; 357 358 //Serial.print("\nPrime Numbers are: \n"); 359 for (i = 1; i <= n; i++) 360 { 361 fact = 0; 362 for (j = 1; j <= n; j++) 363 { 364 if (i % j == 0) 365 fact++; 366 } 367 if (fact == 2) { 368 p = i;//Serial.print(i); Serial.print(", "); 369 } 370 } 371 //Serial.println(); 372 return p; // Biggest 373 } 374 375 /* 376 Original licence: 377 The MIT License (MIT) 378 Copyright (c) 2017 by Daniel Eichhorn 379 Permission is hereby granted, free of charge, to any person obtaining a copy 380 of this software and associated documentation files (the "Software"), to deal 381 in the Software without restriction, including without limitation the rights 382 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 383 copies of the Software, and to permit persons to whom the Software is 384 furnished to do so, subject to the following conditions: 385 The above copyright notice and this permission notice shall be included in all 386 copies or substantial portions of the Software. 387 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 388 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 389 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 390 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 391 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 392 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 393 SOFTWARE. 394 */