tablegen.cpp
1 // SPDX-FileCopyrightText: 2019 Phillip Burgess for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 //34567890123456789012345678901234567890123456789012345678901234567890123456 6 7 #include "globals.h" 8 9 // Code in this file calculates various tables used in eye rendering. 10 11 // Because 3D math is probably asking too much of our microcontroller, 12 // the round eyeball shape is faked using a 2D displacement map, a la 13 // Photoshop's displacement filter or old demoscene & screensaver tricks. 14 // This is not really an accurate representation of 3D rotation, 15 // but works well enough for fooling the casual observer. 16 17 void calcDisplacement() { 18 // To save RAM, the displacement map is calculated for ONE QUARTER of 19 // the screen, then mirrored horizontally/vertically down the middle 20 // when rendering. Additionally, only a single axis displacement need 21 // be calculated, since eye shape is X/Y symmetrical one can just swap 22 // axes to look up displacement on the opposing axis. 23 if(displace = (uint8_t *)malloc((DISPLAY_SIZE/2) * (DISPLAY_SIZE/2))) { 24 float eyeRadius2 = (float)(eyeRadius * eyeRadius); 25 uint8_t x, y; 26 float dx, dy, d2, d, h, a, pa; 27 uint8_t *ptr = displace; 28 // Displacement is calculated for the first quadrant in traditional 29 // "+Y is up" Cartesian coordinate space; any mirroring or rotation 30 // is handled in eye rendering code. 31 for(y=0; y<(DISPLAY_SIZE/2); y++) { 32 yield(); // Periodic yield() makes sure mass storage filesystem stays alive 33 dy = (float)y + 0.5; 34 dy *= dy; // Now dy^2 35 for(x=0; x<(DISPLAY_SIZE/2); x++) { 36 // Get distance to origin point. Pixel centers are at +0.5, this is 37 // normal, desirable and by design -- screen center at (120.0,120.0) 38 // falls between pixels and allows numerically-correct mirroring. 39 dx = (float)x + 0.5; 40 d2 = dx * dx + dy; // Distance to origin, squared 41 if(d2 <= eyeRadius2) { // Pixel is within eye area 42 d = sqrt(d2); // Distance to origin 43 h = sqrt(eyeRadius2 - d2); // Height of eye hemisphere at d 44 a = atan2(d, h); // Angle from center: 0 to pi/2 45 //pa = a * eyeRadius; // Convert to pixels (no) 46 pa = a / M_PI_2 * mapRadius; // Convert to pixels 47 dx /= d; // Normalize dx part of 2D vector 48 *ptr++ = (uint8_t)(dx * pa) - x; // Round to pixel space (no +0.5) 49 } else { // Outside eye area 50 *ptr++ = 255; // Mark as out-of-eye-bounds 51 } 52 } 53 } 54 } 55 } 56 57 void calcMap(void) { 58 int pixels = mapRadius * mapRadius; 59 if(polarAngle = (uint8_t *)malloc(pixels * 2)) { // Single alloc for both tables 60 polarDist = (int8_t *)&polarAngle[pixels]; // Offset to second table 61 62 // CALCULATE POLAR ANGLE & DISTANCE 63 64 float mapRadius2 = mapRadius * mapRadius; // Radius squared 65 float iRad = screen2map(irisRadius); // Iris size in in polar map pixels 66 float irisRadius2 = iRad * iRad; // Iris size squared 67 68 uint8_t *anglePtr = polarAngle; 69 int8_t *distPtr = polarDist; 70 71 // Like the displacement map, only the first quadrant is calculated, 72 // and the other three quadrants are mirrored/rotated from this. 73 int x, y; 74 float dx, dy, dy2, d2, d, angle, xp; 75 for(y=0; y<mapRadius; y++) { 76 yield(); // Periodic yield() makes sure mass storage filesystem stays alive 77 dy = (float)y + 0.5; // Y distance to map center 78 dy2 = dy * dy; 79 for(x=0; x<mapRadius; x++) { 80 dx = (float)x + 0.5; // X distance to map center 81 d2 = dx * dx + dy2; // Distance to center of map, squared 82 if(d2 > mapRadius2) { // If it exceeds 1/2 map size, squared, 83 *anglePtr++ = 0; // then mark as out-of-eye-bounds 84 *distPtr++ = -128; 85 } else { // else pixel is within eye area... 86 angle = atan2(dy, dx); // -pi to +pi (0 to +pi/2 in 1st quadrant) 87 angle = M_PI_2 - angle; // Clockwise, 0 at top 88 angle *= 512.0 / M_PI; // 0 to <256 in 1st quadrant 89 *anglePtr++ = (uint8_t)angle; 90 d = sqrt(d2); 91 if(d2 > irisRadius2) { 92 // Point is in sclera 93 d = (mapRadius - d) / (mapRadius - iRad); 94 d *= 127.0; 95 *distPtr++ = (int8_t)d; // 0 to 127 96 } else { 97 // Point is in iris (-dist to indicate such) 98 d = (iRad - d) / iRad; 99 d *= -127.0; 100 *distPtr++ = (int8_t)d - 1; // -1 to -127 101 } 102 } 103 } 104 } 105 106 // If slit pupil is enabled, override iris area of polarDist map. 107 if(slitPupilRadius > 0) { 108 // Iterate over each pixel in the iris section of the polar map... 109 for(y=0; y < mapRadius; y++) { 110 yield(); // Periodic yield() makes sure mass storage filesystem stays alive 111 dy = y + 0.5; // Distance to center, Y component 112 dy2 = dy * dy; 113 for(x=0; x < mapRadius; x++) { 114 dx = x + 0.5; // Distance to center point, X component 115 d2 = dx * dx + dy2; // Distance to center, squared 116 if(d2 <= irisRadius2) { // If inside iris... 117 yield(); 118 xp = x + 0.5; 119 // This is a bit ugly in that it iteratively calculates the 120 // polarDist value...trial and error. It should be possible to 121 // algebraically simplify this and find the single polarDist 122 // point for a given pixel, but I've not worked that out yet. 123 // This is only needed once at startup, not a complete disaster. 124 for(int i=126; i>=0; i--) { 125 float ratio = i / 128.0; // 0.0 (open) to just-under-1.0 (slit) (>= 1.0 will cause trouble) 126 // Interpolate a point between top of iris and top of slit pupil, based on ratio 127 float y1 = iRad - (iRad - slitPupilRadius) * ratio; 128 // (x1 is 0 and thus dropped from equation below) 129 // And another point between right of iris and center of eye, inverse ratio 130 float x2 = iRad * (1.0 - ratio); 131 // (y2 is also zero, same deal) 132 // Find X coordinate of center of circle that crosses above two points 133 // and has Y at 0.0 134 float xc = (x2 * x2 - y1 * y1) / (2 * x2); 135 dx = x2 - xc; // Distance from center of circle to right edge 136 float r2 = dx * dx; // center-to-right distance squared 137 dx = xp - xc; // X component of... 138 d2 = dx * dx + dy2; // Distance from pixel to left 'xc' point 139 if(d2 <= r2) { // If point is within circle... 140 polarDist[y * mapRadius + x] = (int8_t)(-1 - i); // Set to distance 'i' 141 break; 142 } 143 } 144 } 145 } 146 } 147 } 148 } 149 } 150 151 // Scale a measurement in screen pixels to polar map pixels 152 float screen2map(int in) { 153 return atan2(in, sqrt(eyeRadius * eyeRadius - in * in)) / M_PI_2 * mapRadius; 154 } 155 156 // Inverse of above 157 float map2screen(int in) { 158 return sin((float)in / (float)mapRadius) * M_PI_2 * eyeRadius; 159 }