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