254 lines
7.9 KiB
C++
254 lines
7.9 KiB
C++
#include <U8g2lib.h>
|
|
#include <SPI.h>
|
|
#include <math.h>
|
|
|
|
#define CS 5
|
|
#define DC 16
|
|
#define RESET 17
|
|
|
|
U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R2, CS, DC, RESET);
|
|
|
|
// ---- Géométrie commune ----
|
|
const int EYE_R = 29;
|
|
const int BAR_THICK = 5;
|
|
const int BAR_PITCH = 6;
|
|
const int BAR_ROUND = 2;
|
|
const int N_BARS = 10;
|
|
|
|
const int EYE_L_CX = 32;
|
|
const int EYE_R_CX = 96;
|
|
const int EYE_CY = 32;
|
|
|
|
const int PUPIL_R = 11;
|
|
|
|
enum Emotion {
|
|
NEUTRAL = 0, HAPPY, SAD, ANGRY, SURPRISED,
|
|
SLEEPY, WINK, LOVE, DIZZY, DEAD, N_EMOTIONS
|
|
};
|
|
|
|
// ---- Utilitaire : trace une barre horizontale arrondie dont la largeur
|
|
// suit le profil d'un cercle de rayon eyeR, à l'offset vertical dy ----
|
|
void drawBar(int cx, int cy, int dy, int eyeR) {
|
|
int dy2 = dy * dy;
|
|
int r2 = eyeR * eyeR;
|
|
if (dy2 >= r2) return;
|
|
int halfW = (int)sqrtf((float)(r2 - dy2));
|
|
int topY = cy + dy - BAR_THICK / 2;
|
|
int w = 2 * halfW + 1;
|
|
if (w > 2 * BAR_ROUND && BAR_THICK > 2 * BAR_ROUND) {
|
|
u8g2.drawRBox(cx - halfW, topY, w, BAR_THICK, BAR_ROUND);
|
|
} else {
|
|
u8g2.drawBox(cx - halfW, topY, w, BAR_THICK);
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// 1) NEUTRAL : état "par défaut", 10 barres, 4e supprimée, pupille haute,
|
|
// 3e barre redessinée par-dessus la pupille.
|
|
// =========================================================================
|
|
void drawNeutral(int cx, int cy) {
|
|
const int SKIP_BAR = 3;
|
|
const int OVER_BAR = 2;
|
|
const int PUPIL_DY = -3;
|
|
|
|
for (int i = 0; i < N_BARS; i++) {
|
|
if (i == SKIP_BAR) continue;
|
|
float off = (i - (N_BARS - 1) / 2.0f) * BAR_PITCH;
|
|
drawBar(cx, cy, (int)roundf(off), EYE_R);
|
|
}
|
|
u8g2.setDrawColor(0);
|
|
u8g2.drawDisc(cx, cy + PUPIL_DY, PUPIL_R);
|
|
u8g2.setDrawColor(1);
|
|
float off = (OVER_BAR - (N_BARS - 1) / 2.0f) * BAR_PITCH;
|
|
drawBar(cx, cy, (int)roundf(off), EYE_R);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 2) HAPPY : arc courbé vers le haut (^_^) — croissant fait avec 2 discs
|
|
// =========================================================================
|
|
void drawHappy(int cx, int cy) {
|
|
u8g2.drawDisc(cx, cy + 8, EYE_R - 2);
|
|
u8g2.setDrawColor(0);
|
|
u8g2.drawDisc(cx, cy + 16, EYE_R - 2);
|
|
u8g2.setDrawColor(1);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 3) SAD : arc inversé (⌣) + petite larme sur l'œil gauche
|
|
// =========================================================================
|
|
void drawSad(int cx, int cy) {
|
|
u8g2.drawDisc(cx, cy - 8, EYE_R - 2);
|
|
u8g2.setDrawColor(0);
|
|
u8g2.drawDisc(cx, cy - 16, EYE_R - 2);
|
|
u8g2.setDrawColor(1);
|
|
// larme sous l'œil (côté extérieur)
|
|
bool isLeft = (cx < 64);
|
|
int tx = isLeft ? (cx - 14) : (cx + 14);
|
|
u8g2.drawDisc(tx, cy + 20, 3);
|
|
u8g2.drawTriangle(tx - 3, cy + 20, tx + 3, cy + 20, tx, cy + 14);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 4) ANGRY : œil écrasé + sourcil diagonal froncé
|
|
// =========================================================================
|
|
void drawAngry(int cx, int cy) {
|
|
int cyShift = cy + 6;
|
|
int smallR = EYE_R - 6;
|
|
// 4 barres centrales
|
|
for (int i = 3; i <= 6; i++) {
|
|
float off = (i - (N_BARS - 1) / 2.0f) * BAR_PITCH;
|
|
drawBar(cx, cyShift, (int)roundf(off), smallR);
|
|
}
|
|
// petite pupille
|
|
u8g2.setDrawColor(0);
|
|
u8g2.drawDisc(cx, cyShift, 5);
|
|
u8g2.setDrawColor(1);
|
|
|
|
// sourcil diagonal épais
|
|
bool isLeft = (cx < 64);
|
|
int x1, y1, x2, y2;
|
|
if (isLeft) {
|
|
x1 = cx - smallR - 2; y1 = cy - smallR - 4;
|
|
x2 = cx + smallR + 2; y2 = cy - smallR + 6;
|
|
} else {
|
|
x1 = cx - smallR - 2; y1 = cy - smallR + 6;
|
|
x2 = cx + smallR + 2; y2 = cy - smallR - 4;
|
|
}
|
|
for (int k = -2; k <= 2; k++) {
|
|
u8g2.drawLine(x1, y1 + k, x2, y2 + k);
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// 5) SURPRISED : œil plus grand, toutes les barres présentes, petite pupille
|
|
// =========================================================================
|
|
void drawSurprised(int cx, int cy) {
|
|
for (int i = 0; i < N_BARS; i++) {
|
|
float off = (i - (N_BARS - 1) / 2.0f) * BAR_PITCH;
|
|
drawBar(cx, cy, (int)roundf(off), EYE_R);
|
|
}
|
|
u8g2.setDrawColor(0);
|
|
u8g2.drawDisc(cx, cy, 5);
|
|
u8g2.setDrawColor(1);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 6) SLEEPY : seulement les barres du bas + paupière épaisse en haut
|
|
// =========================================================================
|
|
void drawSleepy(int cx, int cy) {
|
|
for (int i = 5; i < N_BARS; i++) {
|
|
float off = (i - (N_BARS - 1) / 2.0f) * BAR_PITCH;
|
|
drawBar(cx, cy, (int)roundf(off), EYE_R);
|
|
}
|
|
u8g2.drawRBox(cx - EYE_R + 2, cy - 2, 2 * EYE_R - 3, 4, 2);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 7) WINK : œil gauche normal, œil droit fermé (barre horizontale)
|
|
// =========================================================================
|
|
void drawWinkClosed(int cx, int cy) {
|
|
u8g2.drawRBox(cx - EYE_R + 4, cy - 3, 2 * EYE_R - 7, 6, 3);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 8) LOVE : cœur plein (2 discs + triangle)
|
|
// =========================================================================
|
|
void drawHeart(int cx, int cy) {
|
|
const int r = 11;
|
|
int topY = cy - 6;
|
|
u8g2.drawDisc(cx - 7, topY, r);
|
|
u8g2.drawDisc(cx + 7, topY, r);
|
|
u8g2.drawTriangle(cx - 17, topY + 2, cx + 17, topY + 2, cx, cy + 20);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 9) DIZZY : cercles concentriques + petite pupille décalée
|
|
// =========================================================================
|
|
void drawDizzy(int cx, int cy) {
|
|
u8g2.drawCircle(cx, cy, EYE_R - 2);
|
|
u8g2.drawCircle(cx, cy, EYE_R - 8);
|
|
u8g2.drawCircle(cx, cy, EYE_R - 14);
|
|
u8g2.drawCircle(cx, cy, EYE_R - 20);
|
|
u8g2.drawDisc(cx + 4, cy - 2, 3);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 10) DEAD : deux lignes épaisses en X
|
|
// =========================================================================
|
|
void drawDead(int cx, int cy) {
|
|
int a = EYE_R - 4;
|
|
for (int k = -2; k <= 2; k++) {
|
|
u8g2.drawLine(cx - a + k, cy - a, cx + a + k, cy + a);
|
|
u8g2.drawLine(cx - a + k, cy + a, cx + a + k, cy - a);
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// Cycle des émotions
|
|
// =========================================================================
|
|
void drawEmotion(Emotion e) {
|
|
u8g2.clearBuffer();
|
|
switch (e) {
|
|
case NEUTRAL:
|
|
drawNeutral(EYE_L_CX, EYE_CY);
|
|
drawNeutral(EYE_R_CX, EYE_CY);
|
|
break;
|
|
case HAPPY:
|
|
drawHappy(EYE_L_CX, EYE_CY);
|
|
drawHappy(EYE_R_CX, EYE_CY);
|
|
break;
|
|
case SAD:
|
|
drawSad(EYE_L_CX, EYE_CY);
|
|
drawSad(EYE_R_CX, EYE_CY);
|
|
break;
|
|
case ANGRY:
|
|
drawAngry(EYE_L_CX, EYE_CY);
|
|
drawAngry(EYE_R_CX, EYE_CY);
|
|
break;
|
|
case SURPRISED:
|
|
drawSurprised(EYE_L_CX, EYE_CY);
|
|
drawSurprised(EYE_R_CX, EYE_CY);
|
|
break;
|
|
case SLEEPY:
|
|
drawSleepy(EYE_L_CX, EYE_CY);
|
|
drawSleepy(EYE_R_CX, EYE_CY);
|
|
break;
|
|
case WINK:
|
|
drawNeutral(EYE_L_CX, EYE_CY);
|
|
drawWinkClosed(EYE_R_CX, EYE_CY);
|
|
break;
|
|
case LOVE:
|
|
drawHeart(EYE_L_CX, EYE_CY);
|
|
drawHeart(EYE_R_CX, EYE_CY);
|
|
break;
|
|
case DIZZY:
|
|
drawDizzy(EYE_L_CX, EYE_CY);
|
|
drawDizzy(EYE_R_CX, EYE_CY);
|
|
break;
|
|
case DEAD:
|
|
drawDead(EYE_L_CX, EYE_CY);
|
|
drawDead(EYE_R_CX, EYE_CY);
|
|
break;
|
|
default: break;
|
|
}
|
|
u8g2.sendBuffer();
|
|
}
|
|
|
|
const unsigned long EMOTION_INTERVAL = 3000;
|
|
unsigned long lastSwitch = 0;
|
|
int current = 0;
|
|
|
|
void setup() {
|
|
u8g2.begin();
|
|
drawEmotion((Emotion)current);
|
|
lastSwitch = millis();
|
|
}
|
|
|
|
void loop() {
|
|
if (millis() - lastSwitch >= EMOTION_INTERVAL) {
|
|
current = (current + 1) % N_EMOTIONS;
|
|
drawEmotion((Emotion)current);
|
|
lastSwitch = millis();
|
|
}
|
|
}
|