@@ -8,8 +8,8 @@ MOVE-AND-BLINK EYES for Adafruit EyeLights (LED Glasses + Driver).
88I'd written a very cool squash-and-stretch effect for the eye movement,
99but unfortunately the resolution is such that the pupils just look like
1010circles regardless. I'm keeping it in despite the added complexity,
11- because LED matrix densities WILL improve, and this way the code won't
12- require a re-write at such a later time.
11+ because this WILL look great later on a bigger matrix or a TFT/OLED,
12+ and this way the hard parts won't require a re-write at such time.
1313*/
1414
1515#include < Adafruit_IS31FL3741.h> // For LED driver
@@ -24,6 +24,13 @@ TwoWire *i2c = &Wire; // e.g. change this to &Wire1 for QT Py RP2040
2424Adafruit_EyeLights_buffered glasses (true ); // Buffered + 3X canvas
2525GFXcanvas16 *canvas; // Pointer to canvas object
2626
27+ // Reading through the code, you'll see a lot of references to this "3X"
28+ // space. This is referring to the glasses' optional "offscreen" drawing
29+ // canvas that's 3 times the resolution of the LED matrix (i.e. 15 pixels
30+ // tall instead of 5), which gets scaled down to provide some degree of
31+ // antialiasing. It's why the pupils have soft edges and can make
32+ // fractional-pixel motions.
33+
2734uint32_t frames = 0 ;
2835uint32_t start_time;
2936
@@ -84,21 +91,17 @@ void setup() { // Runs once at program start...
8491
8592 // Initialize hardware
8693 Serial.begin (115200 );
87- while (!Serial);
88- Serial.println (" HEY!" ); yield ();
8994 if (! glasses.begin (IS3741_ADDR_DEFAULT, i2c)) err (" IS3741 not found" , 2 );
9095
9196 canvas = glasses.getCanvas ();
9297 if (!canvas) err (" Can't allocate canvas" , 5 );
9398
94- Serial.println (" A" );
9599 i2c->setClock (1000000 );
96100
97101 // Configure glasses for reduced brightness, enable output
98102 glasses.setLEDscaling (0xFF );
99103 glasses.setGlobalCurrent (20 );
100104 glasses.enable (true );
101- Serial.println (" B" ); yield ();
102105
103106 // INITIALIZE TABLES & OTHER GLOBALS ----
104107
@@ -110,7 +113,6 @@ Serial.println("B"); yield();
110113 }
111114
112115 ring_open_color_packed = gammify (ring_open_color);
113- Serial.println (" C" ); yield ();
114116
115117 start_time = millis ();
116118}
@@ -152,16 +154,18 @@ void rasterize(float point1[2], float point2[2], int rect[4]) {
152154
153155 // Like I'm sure there's a way to rasterize this by spans rather than
154156 // all these square roots on every pixel, but for now...
155- for (int y=rect[1 ]; y<rect[3 ]; y++) { // For each row...
156- float dy1 = (float )y - point1[1 ]; // Y distance from pixel to first point
157- float dy2 = (float )y - point2[1 ]; // " to second
158- dy1 *= dy1; // Y1^2
159- dy2 *= dy2; // Y2^2
160- for (int x=rect[0 ]; x<rect[2 ]; x++) { // For each column...
161- float dx1 = (float )x - point1[0 ]; // X distance from pixel to first point
162- float dx2 = (float )x - point2[0 ]; // " to second
163- float d1 = sqrt (dx1 * dx1 + dy1); // 2D distance to first point
164- float d2 = sqrt (dx2 * dx2 + dy2); // " to second
157+ for (int y=rect[1 ]; y<rect[3 ]; y++) { // For each row...
158+ float y5 = (float )y + 0.5 ; // Pixel center
159+ float dy1 = y5 - point1[1 ]; // Y distance from pixel to first point
160+ float dy2 = y5 - point2[1 ]; // " to second
161+ dy1 *= dy1; // Y1^2
162+ dy2 *= dy2; // Y2^2
163+ for (int x=rect[0 ]; x<rect[2 ]; x++) { // For each column...
164+ float x5 = (float )x + 0.5 ; // Pixel center
165+ float dx1 = x5 - point1[0 ]; // X distance from pixel to first point
166+ float dx2 = x5 - point2[0 ]; // " to second
167+ float d1 = sqrt (dx1 * dx1 + dy1); // 2D distance to first point
168+ float d2 = sqrt (dx2 * dx2 + dy2); // " to second
165169 if ((d1 + d2 + d) <= perimeter) {
166170 canvas->drawPixel (x, y, eye_color); // Point is inside ellipse
167171 }
@@ -170,11 +174,8 @@ void rasterize(float point1[2], float point2[2], int rect[4]) {
170174}
171175
172176void loop () { // Repeat forever...
173- yield ();
174- Serial.println (" 1" ); yield ();
175177 canvas->fillScreen (0 );
176178
177-
178179 // The eye animation logic is a carry-over from like a billion
179180 // prior eye projects, so this might be comment-light.
180181 uint32_t now = micros (); // 'Snapshot' the time once per frame
@@ -202,7 +203,6 @@ Serial.println("1"); yield();
202203 upper = ratio * 15.0 - 4.0 ; // Upper eyelid pos. in 3X space
203204 lower = 23.0 - ratio * 8.0 ; // Lower eyelid pos. in 3X space
204205 }
205- Serial.println (" 2" ); yield ();
206206
207207 // Eye movement logic. Two points, 'p1' and 'p2', are the foci of an
208208 // ellipse. p1 moves from current to next position a little faster
@@ -224,23 +224,21 @@ Serial.println("2"); yield();
224224 delta[0 ] = next_pos[0 ] - cur_pos[0 ];
225225 delta[1 ] = next_pos[1 ] - cur_pos[1 ];
226226 ratio = (float )elapsed / (float )move_duration;
227- if (ratio < 0.7 ) { // First 70% of move time
228- // p1 is in motion
227+ if (ratio < 0.6 ) { // First 60% of move time, p1 is in motion
229228 // Easing function: 3*e^2-2*e^3 0.0 to 1.0
230- float e = ratio / 0.7 ; // 0.0 to 1.0
229+ float e = ratio / 0.6 ; // 0.0 to 1.0
231230 e = 3 * e * e - 2 * e * e * e;
232231 p1[0 ] = cur_pos[0 ] + delta[0 ] * e;
233232 p1[1 ] = cur_pos[1 ] + delta[1 ] * e;
234- } else { // Last 30 % of move time
233+ } else { // Last 40 % of move time
235234 memcpy (&p1, &next_pos, sizeof next_pos); // p1 has reached end position
236235 }
237- if (ratio > 0.2 ) { // Last 80% of move time
238- // p2 is in motion
239- float e = (ratio - 0.2 ) / 0.8 ; // 0.0 to 1.0
236+ if (ratio > 0.3 ) { // Last 70% of move time, p2 is in motion
237+ float e = (ratio - 0.3 ) / 0.7 ; // 0.0 to 1.0
240238 e = 3 * e * e - 2 * e * e * e; // Easing func.
241239 p2[0 ] = cur_pos[0 ] + delta[0 ] * e;
242240 p2[1 ] = cur_pos[1 ] + delta[1 ] * e;
243- } else { // First 20 % of move time
241+ } else { // First 30 % of move time
244242 memcpy (&p2, &cur_pos, sizeof cur_pos); // p2 waits at start position
245243 }
246244 }
@@ -257,7 +255,6 @@ Serial.println("2"); yield();
257255 next_pos[1 ] = 7.5 + sin (angle) * dist * 0.8 ;
258256 }
259257 }
260- Serial.println (" 3" ); yield ();
261258
262259 // Draw the raster part of each eye...
263260 for (uint8_t e=0 ; e<2 ; e++) {
@@ -297,8 +294,6 @@ bounds[3] = 5 * 3;
297294 rasterize (p1a, p2a, bounds); // Render ellipse into buffer
298295 }
299296
300- Serial.println (" 4" ); yield ();
301-
302297 // If the eye is currently blinking, and if the top edge of the
303298 // eyelid overlaps the bitmap, draw a scanline across the bitmap
304299 // and update the bounds rect so the whole width of the bitmap
@@ -307,10 +302,8 @@ Serial.println("4"); yield();
307302 canvas->fillRect (0 , 0 , canvas->width (), (int )upper + 1 , 0x0004 );
308303 }
309304
310- Serial.println (" 5" ); yield ();
311305
312306 glasses.scale ();
313- Serial.println (" 6" ); yield ();
314307
315308 // Matrix and rings share a few pixels. To make the rings take
316309 // precedence, they're drawn later. So blink state is revisited now...
@@ -333,11 +326,7 @@ Serial.println("6"); yield();
333326 glasses.right_ring .fill (ring_open_color_packed);
334327 }
335328
336- Serial.println (" 7" ); yield ();
337-
338-
339329 glasses.show ();
340- Serial.println (" 8" ); yield ();
341330
342331 frames += 1 ;
343332 elapsed = millis () - start_time;
0 commit comments