1#include <Wire.h>
2#include <Adafruit_GFX.h>
3#include <Adafruit_SSD1306.h>
4
5#define SCREEN_WIDTH 128
6#define SCREEN_HEIGHT 64
7#define OLED_RESET -1
8Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
9
10// --- BUTTON CONFIG ---
11const int buttonPin = 7; // Changed to Pin D7 for Arduino Uno
12int buttonState = HIGH;
13int lastButtonState = HIGH;
14unsigned long lastDebounceTime = 0;
15unsigned long debounceDelay = 50;
16
17// --- EMOTION PARTICLES (16x16) ---
18const unsigned char bmp_heart[] PROGMEM = { 0x00, 0x00, 0x0c, 0x60, 0x1e, 0xf0, 0x3f, 0xf8, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f, 0xf0, 0x0f, 0xe0, 0x07, 0xc0, 0x03, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
19const unsigned char bmp_zzz[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x7e, 0x00, 0x00, 0x3c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00 };
20const unsigned char bmp_anger[] PROGMEM = { 0x00, 0x00, 0x11, 0x10, 0x2a, 0x90, 0x44, 0x40, 0x80, 0x20, 0x80, 0x20, 0x44, 0x40, 0x2a, 0x90, 0x11, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
21
22// --- MOODS ---
23#define MOOD_NORMAL 0
24#define MOOD_HAPPY 1
25#define MOOD_SURPRISED 2
26#define MOOD_SLEEPY 3
27#define MOOD_ANGRY 4
28#define MOOD_SAD 5
29#define MOOD_EXCITED 6
30#define MOOD_LOVE 7
31#define MOOD_SUSPICIOUS 8
32
33int currentMood = MOOD_NORMAL;
34const int maxMoods = 8;
35
36// --- PHYSICS ENGINE VARIABLES ---
37unsigned long lastSaccade = 0;
38unsigned long saccadeInterval = 3000;
39float breathVal = 0;
40
41struct Eye {
42 float x, y;
43 float w, h;
44 float targetX, targetY, targetW, targetH;
45 float pupilX, pupilY;
46 float targetPupilX, targetPupilY;
47 float velX, velY, velW, velH;
48 float pVelX, pVelY;
49
50 float k = 0.12;
51 float d = 0.60;
52 float pk = 0.08;
53 float pd = 0.50;
54
55 bool blinking;
56 unsigned long lastBlink;
57 unsigned long nextBlinkTime;
58
59 void init(float _x, float _y, float _w, float _h) {
60 x = targetX = _x;
61 y = targetY = _y;
62 w = targetW = _w;
63 h = targetH = _h;
64 pupilX = targetPupilX = 0;
65 pupilY = targetPupilY = 0;
66 velX = velY = velW = velH = 0;
67 pVelX = pVelY = 0;
68 nextBlinkTime = millis() + random(1000, 4000);
69 blinking = false;
70 }
71
72 void update() {
73 float ax = (targetX - x) * k;
74 float ay = (targetY - y) * k;
75 float aw = (targetW - w) * k;
76 float ah = (targetH - h) * k;
77
78 velX = (velX + ax) * d;
79 velY = (velY + ay) * d;
80 velW = (velW + aw) * d;
81 velH = (velH + ah) * d;
82
83 x += velX;
84 y += velY;
85 w += velW;
86 h += velH;
87
88 float pax = (targetPupilX - pupilX) * pk;
89 float pay = (targetPupilY - pupilY) * pk;
90 pVelX = (pVelX + pax) * pd;
91 pVelY = (pVelY + pay) * pd;
92 pupilX += pVelX;
93 pupilY += pVelY;
94 }
95};
96
97Eye leftEye, rightEye;
98
99void setup() {
100 Serial.begin(9600);
101 pinMode(buttonPin, INPUT_PULLUP);
102
103 // Start I2C (Pins A4 and A5 on Uno are handled automatically by Wire.begin)
104 Wire.begin();
105 Wire.setClock(400000); // 400kHz Fast Mode for zero lag
106
107 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
108 Serial.println(F("SSD1306 allocation failed"));
109 for(;;);
110 }
111
112 display.setTextColor(SSD1306_WHITE);
113 leftEye.init(18, 14, 36, 36);
114 rightEye.init(74, 14, 36, 36);
115}
116
117void loop() {
118 handleButton();
119 updatePhysicsAndMood();
120
121 display.clearDisplay();
122 drawEmoPage();
123 display.display();
124}
125
126void handleButton() {
127 int reading = digitalRead(buttonPin);
128 if (reading != lastButtonState) {
129 lastDebounceTime = millis();
130 }
131 if ((millis() - lastDebounceTime) > debounceDelay) {
132 if (reading != buttonState) {
133 buttonState = reading;
134 if (buttonState == LOW) {
135 currentMood++;
136 if (currentMood > maxMoods) currentMood = 0;
137 lastSaccade = 0;
138 }
139 }
140 }
141 lastButtonState = reading;
142}
143
144void updatePhysicsAndMood() {
145 unsigned long now = millis();
146 breathVal = sin(now / 800.0) * 1.5;
147
148 if (now > leftEye.nextBlinkTime) {
149 leftEye.blinking = true;
150 leftEye.lastBlink = now;
151 rightEye.blinking = true;
152 leftEye.nextBlinkTime = now + random(2000, 6000);
153 }
154
155 if (leftEye.blinking) {
156 leftEye.targetH = 2;
157 rightEye.targetH = 2;
158 if (now - leftEye.lastBlink > 120) {
159 leftEye.blinking = false;
160 rightEye.blinking = false;
161 }
162 }
163
164 if (!leftEye.blinking && now - lastSaccade > saccadeInterval) {
165 lastSaccade = now;
166 saccadeInterval = random(500, 3000);
167
168 int dir = random(0, 10);
169 float lx = 0, ly = 0;
170
171 if (dir < 4) { lx = 0; ly = 0; }
172 else if (dir == 4) { lx = -6; ly = -4; }
173 else if (dir == 5) { lx = 6; ly = -4; }
174 else if (dir == 6) { lx = -6; ly = 4; }
175 else if (dir == 7) { lx = 6; ly = 4; }
176 else if (dir == 8) { lx = 8; ly = 0; }
177 else if (dir == 9) { lx = -8; ly = 0; }
178
179 leftEye.targetPupilX = lx;
180 leftEye.targetPupilY = ly;
181 rightEye.targetPupilX = lx;
182 rightEye.targetPupilY = ly;
183
184 leftEye.targetX = 18 + (lx * 0.3);
185 leftEye.targetY = 14 + (ly * 0.3);
186 rightEye.targetX = 74 + (lx * 0.3);
187 rightEye.targetY = 14 + (ly * 0.3);
188 }
189
190 if (!leftEye.blinking) {
191 float baseW = 36;
192 float baseH = 36;
193 baseH += breathVal;
194
195 switch (currentMood) {
196 case MOOD_NORMAL:
197 leftEye.targetW = baseW; leftEye.targetH = baseH;
198 rightEye.targetW = baseW; rightEye.targetH = baseH;
199 break;
200 case MOOD_HAPPY:
201 case MOOD_LOVE:
202 case MOOD_EXCITED:
203 leftEye.targetW = 40; leftEye.targetH = 32;
204 rightEye.targetW = 40; rightEye.targetH = 32;
205 break;
206 case MOOD_SURPRISED:
207 leftEye.targetW = 30; leftEye.targetH = 45;
208 rightEye.targetW = 30; rightEye.targetH = 45;
209 break;
210 case MOOD_SLEEPY:
211 leftEye.targetW = 38; leftEye.targetH = 30;
212 rightEye.targetW = 38; rightEye.targetH = 30;
213 break;
214 case MOOD_ANGRY:
215 leftEye.targetW = 34; leftEye.targetH = 32;
216 rightEye.targetW = 34; rightEye.targetH = 32;
217 break;
218 case MOOD_SAD:
219 leftEye.targetW = 34; leftEye.targetH = 40;
220 rightEye.targetW = 34; rightEye.targetH = 40;
221 break;
222 case MOOD_SUSPICIOUS:
223 leftEye.targetW = 36; leftEye.targetH = 20;
224 rightEye.targetW = 36; rightEye.targetH = 42;
225 break;
226 }
227 }
228
229 leftEye.update();
230 rightEye.update();
231}
232
233void drawEmoPage() {
234 if (currentMood == MOOD_LOVE) {
235 display.drawBitmap(56, 0, bmp_heart, 16, 16, SSD1306_WHITE);
236 } else if (currentMood == MOOD_SLEEPY) {
237 display.drawBitmap(110, 0, bmp_zzz, 16, 16, SSD1306_WHITE);
238 } else if (currentMood == MOOD_ANGRY) {
239 display.drawBitmap(56, 0, bmp_anger, 16, 16, SSD1306_WHITE);
240 }
241
242 drawUltraProEye(leftEye, true);
243 drawUltraProEye(rightEye, false);
244}
245
246void drawUltraProEye(Eye& e, bool isLeft) {
247 int ix = (int)e.x;
248 int iy = (int)e.y;
249 int iw = (int)e.w;
250 int ih = (int)e.h;
251
252 int r = 8;
253 if (iw < 20) r = 3;
254
255 display.fillRoundRect(ix, iy, iw, ih, r, SSD1306_WHITE);
256
257 int cx = ix + iw / 2;
258 int cy = iy + ih / 2;
259 int pw = iw / 2.2;
260 int ph = ih / 2.2;
261
262 int px = cx + (int)e.pupilX - (pw / 2);
263 int py = cy + (int)e.pupilY - (ph / 2);
264
265 if (px < ix) px = ix;
266 if (px + pw > ix + iw) px = ix + iw - pw;
267 if (py < iy) py = iy;
268 if (py + ph > iy + ih) py = iy + ih - ph;
269
270 display.fillRoundRect(px, py, pw, ph, r / 2, SSD1306_BLACK);
271
272 if (iw > 15 && ih > 15) {
273 display.fillCircle(px + pw - 4, py + 4, 2, SSD1306_WHITE);
274 }
275
276 drawEyelidMask(e.x, e.y, e.w, e.h, currentMood, isLeft);
277}
278
279void drawEyelidMask(float x, float y, float w, float h, int mood, bool isLeft) {
280 int ix = (int)x;
281 int iy = (int)y;
282 int iw = (int)w;
283 int ih = (int)h;
284
285 if (mood == MOOD_ANGRY) {
286 if (isLeft) {
287 display.fillTriangle(ix - 5, iy - 5, ix + iw + 5, iy + 10, ix - 5, iy + 15, SSD1306_BLACK);
288 } else {
289 display.fillTriangle(ix + iw + 5, iy - 5, ix - 5, iy + 10, ix + iw + 5, iy + 15, SSD1306_BLACK);
290 }
291 }
292 else if (mood == MOOD_SAD) {
293 if (isLeft) {
294 display.fillTriangle(ix + iw + 5, iy - 5, ix - 5, iy + 10, ix + iw + 5, iy + 15, SSD1306_BLACK);
295 } else {
296 display.fillTriangle(ix - 5, iy - 5, ix + iw + 5, iy + 10, ix - 5, iy + 15, SSD1306_BLACK);
297 }
298 }
299 else if (mood == MOOD_HAPPY || mood == MOOD_LOVE || mood == MOOD_EXCITED) {
300 display.fillRect(ix, iy + ih - 12, iw, 14, SSD1306_BLACK);
301 display.fillCircle(ix + iw / 2, iy + ih + 6, iw / 1.3, SSD1306_BLACK);
302 }
303 else if (mood == MOOD_SLEEPY) {
304 display.fillRect(ix, iy, iw, ih / 2 + 2, SSD1306_BLACK);
305 }
306 else if (mood == MOOD_SUSPICIOUS) {
307 if (isLeft) display.fillRect(ix, iy, iw, ih / 2 - 2, SSD1306_BLACK);
308 else display.fillRect(ix, iy + ih - 8, iw, 8, SSD1306_BLACK);
309 }
310}