pleasedontcode

# Emotional Infant rev_03

Mar 9th, 2026
28
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Arduino 31.22 KB | None | 0 0
  1. /********* Pleasedontcode.com **********
  2.  
  3.     Pleasedontcode thanks you for automatic code generation! Enjoy your code!
  4.  
  5.     - Terms and Conditions:
  6.     You have a non-exclusive, revocable, worldwide, royalty-free license
  7.     for personal and commercial use. Attribution is optional; modifications
  8.     are allowed, but you're responsible for code maintenance. We're not
  9.     liable for any loss or damage. For full terms,
  10.     please visit pleasedontcode.com/termsandconditions.
  11.  
  12.     - Project: # Emotional Infant
  13.     - Version: 003
  14.     - Source Code NOT compiled for: ESP32S3 Dev Module
  15.     - Source Code created on: 2026-03-09 21:20:17
  16.  
  17. ********* Pleasedontcode.com **********/
  18.  
  19. /****** SYSTEM REQUIREMENTS *****/
  20. /****** SYSTEM REQUIREMENT 1 *****/
  21.     /* Emotion system with 8 states: neutral, happy, sad, */
  22.     /* excited, sleepy, curious, shy, playful. Each */
  23.     /* emotion modifies facial features, animation speed, */
  24.     /* and micro-movement patterns for lifelike */
  25.     /* expression */
  26. /****** SYSTEM REQUIREMENT 2 *****/
  27.     /* Modular architecture with separate animation */
  28.     /* modules, physics engine, emotion state machine. */
  29.     /* Code fully commented, optimized for ESP32-S3 */
  30.     /* performance, minimal memory footprint */
  31. /****** END SYSTEM REQUIREMENTS *****/
  32.  
  33.  
  34. /* START CODE */
  35.  
  36. /****** DEFINITION OF LIBRARIES *****/
  37. #include <Adafruit_SSD1306.h>  // https://github.com/adafruit/Adafruit_SSD1306
  38. #include <Adafruit_GFX.h>      // https://github.com/adafruit/Adafruit-GFX-Library
  39. #include <EasyButton.h>        // https://github.com/evert-arias/EasyButton
  40.  
  41. /****** SYSTEM REQUIREMENTS *****/
  42. // SR1: Procedural face animation system: render eyes with physics-based blinking,
  43. //      pupil tracking with inertia damping, iris glow effects, eyelids, eyebrows,
  44. //      mouth expressions, cheeks, blush, nose on 128x64 OLED at 45FPS
  45. // SR2: Emotion system with 8 states: neutral, happy, sad, excited, sleepy, curious,
  46. //      shy, playful. Each emotion modifies facial features, animation speed, and
  47. //      micro-movement patterns for lifelike expression
  48. // SR3: Touch button input cycles through emotions sequentially. When idle (no touch
  49. //      for 5 seconds), enter autonomous baby-like behavior mode with random gestures,
  50. //      exploration, micro-movements, and periodic state changes
  51. // SR4: Modular architecture with separate animation modules, physics engine, emotion
  52. //      state machine. Code fully commented, optimized for ESP32-S3 performance,
  53. //      minimal memory footprint
  54.  
  55. /****** FUNCTION PROTOTYPES *****/
  56. void setup(void);
  57. void loop(void);
  58.  
  59. /***** DEFINITION OF DIGITAL INPUT PINS *****/
  60. const uint8_t faceDisplay_PushButton_PIN_D1 = 1;
  61.  
  62. /***** DEFINITION OF DIGITAL OUTPUT PINS *****/
  63. const uint8_t emotionButton_LED_PIN_D2 = 2;
  64.  
  65. /****** OLED DISPLAY CONFIGURATION *****/
  66. #define SCREEN_WIDTH 128
  67. #define SCREEN_HEIGHT 64
  68. #define OLED_RESET -1
  69. #define SCREEN_ADDRESS 0x3C
  70.  
  71. // Create display instance using I2C
  72. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  73.  
  74. /****** EASY BUTTON CONFIGURATION *****/
  75. EasyButton button(faceDisplay_PushButton_PIN_D1);
  76.  
  77. /****** EMOTION ENUMERATION *****/
  78. enum Emotion {
  79.   NEUTRAL = 0,
  80.   HAPPY = 1,
  81.   SAD = 2,
  82.   EXCITED = 3,
  83.   SLEEPY = 4,
  84.   CURIOUS = 5,
  85.   SHY = 6,
  86.   PLAYFUL = 7
  87. };
  88.  
  89. /****** PHYSICS ENGINE STRUCTURE *****/
  90. struct PhysicsState {
  91.   // Eye pupil tracking physics
  92.   float pupilX;           // Current pupil X position (-1.0 to 1.0)
  93.   float pupilY;           // Current pupil Y position (-1.0 to 1.0)
  94.   float pupilVelX;        // Pupil velocity X (inertia)
  95.   float pupilVelY;        // Pupil velocity Y (inertia)
  96.   float targetPupilX;     // Target pupil X position
  97.   float targetPupilY;     // Target pupil Y position
  98.  
  99.   // Blinking physics
  100.   float blinkValue;       // 0.0 (open) to 1.0 (closed)
  101.   float blinkVel;         // Blinking velocity for smooth animation
  102.   bool isBlinking;        // Currently blinking
  103.   unsigned long blinkStartTime;
  104.  
  105.   // Eyelid animation
  106.   float eyelidOffset;     // Additional eyelid movement beyond blink
  107.  
  108.   // Eyebrow animation
  109.   float eyebrowAngle;     // Eyebrow rotation in degrees
  110.   float eyebrowTargetAngle;
  111.  
  112.   // Iris glow
  113.   float irisGlow;         // 0.0 to 1.0 glow intensity
  114.  
  115.   // Mouth expression
  116.   float mouthHeight;      // Mouth curvature
  117.   float mouthAngle;       // Mouth angle/tilt
  118.  
  119.   // Cheek blush
  120.   float cheekBlush;       // Cheek color intensity
  121.  
  122.   // Nose animation
  123.   float noseWiggle;       // Nose movement micro-animation
  124. };
  125.  
  126. /****** ANIMATION STATE STRUCTURE *****/
  127. struct AnimationState {
  128.   Emotion currentEmotion;
  129.   Emotion targetEmotion;
  130.  
  131.   // Autonomous behavior
  132.   bool autonomousMode;
  133.   unsigned long lastInputTime;
  134.   unsigned long idleThreshold;  // 5000ms = 5 seconds
  135.  
  136.   // Animation timing
  137.   unsigned long frameTime;      // Current frame timestamp
  138.   unsigned long lastFrameTime;
  139.   float deltaTime;              // Time since last frame in seconds
  140.   uint32_t frameCount;          // Total frames rendered
  141.  
  142.   // Animation phase for cyclic behaviors
  143.   float animationPhase;         // 0.0 to 1.0, wraps around
  144.   float phaseVelocity;          // Controls animation speed based on emotion
  145.  
  146.   // Micro-movement - moved from PhysicsState to AnimationState
  147.   float microMoveX;             // Subtle position jitter
  148.   float microMoveY;             // Subtle position jitter
  149.  
  150.   // Gaze direction
  151.   float gazeX;                  // Where the eyes are looking
  152.   float gazeY;
  153. };
  154.  
  155. /****** EMOTION CONFIGURATION STRUCTURE *****/
  156. struct EmotionConfig {
  157.   float animationSpeed;         // How fast animations play
  158.   float blinkFrequency;         // How often to blink (blinks per second)
  159.   float pupilTrackingIntensity; // How responsive pupil is to gaze
  160.   float microMoveAmount;        // Amount of micro-movement jitter
  161.   int8_t eyebrowAngle;          // Base eyebrow angle in degrees
  162.   float mouthHeight;            // Base mouth curve height
  163.   float cheekColor;             // Cheek blush intensity (0.0-1.0)
  164.   float irisGlowIntensity;      // Iris glow brightness
  165. };
  166.  
  167. /****** GLOBAL STATE VARIABLES *****/
  168. PhysicsState physics;
  169. AnimationState animation;
  170. EmotionConfig emotionConfigs[8];
  171.  
  172. /****** FRAME RATE LIMITING VARIABLES *****/
  173. unsigned long lastFrameTimeMs = 0;
  174. const unsigned long FRAME_TIME_MS = 22;  // 22ms per frame for 45 FPS
  175.  
  176. /****** INITIALIZE EMOTION CONFIGURATIONS *****/
  177. void initializeEmotionConfigs() {
  178.   // NEUTRAL: calm, baseline state
  179.   emotionConfigs[NEUTRAL] = {
  180.     1.0f,      // animationSpeed
  181.     1.5f,      // blinkFrequency (blinks per second)
  182.     0.7f,      // pupilTrackingIntensity
  183.     0.5f,      // microMoveAmount
  184.     0,         // eyebrowAngle
  185.     0.3f,      // mouthHeight
  186.     0.1f,      // cheekColor
  187.     0.3f       // irisGlowIntensity
  188.   };
  189.  
  190.   // HAPPY: cheerful, animated
  191.   emotionConfigs[HAPPY] = {
  192.     1.5f,      // animationSpeed (faster)
  193.     2.0f,      // blinkFrequency
  194.     0.8f,      // pupilTrackingIntensity
  195.     1.0f,      // microMoveAmount (more movement)
  196.     15,        // eyebrowAngle (raised)
  197.     0.7f,      // mouthHeight (big smile)
  198.     0.6f,      // cheekColor (strong blush)
  199.     0.5f       // irisGlowIntensity
  200.   };
  201.  
  202.   // SAD: depressed, slower movements
  203.   emotionConfigs[SAD] = {
  204.     0.5f,      // animationSpeed (slower)
  205.     0.8f,      // blinkFrequency
  206.     0.5f,      // pupilTrackingIntensity
  207.     0.2f,      // microMoveAmount
  208.     -15,       // eyebrowAngle (lowered)
  209.     -0.5f,     // mouthHeight (frown)
  210.     0.2f,      // cheekColor
  211.     0.1f       // irisGlowIntensity
  212.   };
  213.  
  214.   // EXCITED: very animated, rapid blinks
  215.   emotionConfigs[EXCITED] = {
  216.     2.0f,      // animationSpeed (very fast)
  217.     3.0f,      // blinkFrequency
  218.     0.9f,      // pupilTrackingIntensity
  219.     1.5f,      // microMoveAmount
  220.     20,        // eyebrowAngle
  221.     0.8f,      // mouthHeight
  222.     0.7f,      // cheekColor
  223.     0.7f       // irisGlowIntensity
  224.   };
  225.  
  226.   // SLEEPY: slow, droopy eyes
  227.   emotionConfigs[SLEEPY] = {
  228.     0.3f,      // animationSpeed
  229.     0.3f,      // blinkFrequency (frequent half-blinks)
  230.     0.3f,      // pupilTrackingIntensity
  231.     0.1f,      // microMoveAmount
  232.     -20,       // eyebrowAngle (relaxed)
  233.     0.2f,      // mouthHeight
  234.     0.05f,     // cheekColor
  235.     0.05f      // irisGlowIntensity
  236.   };
  237.  
  238.   // CURIOUS: searching movements, alert
  239.   emotionConfigs[CURIOUS] = {
  240.     1.3f,      // animationSpeed
  241.     1.2f,      // blinkFrequency
  242.     1.0f,      // pupilTrackingIntensity (maximum)
  243.     0.8f,      // microMoveAmount
  244.     10,        // eyebrowAngle
  245.     0.4f,      // mouthHeight
  246.     0.2f,      // cheekColor
  247.     0.6f       // irisGlowIntensity
  248.   };
  249.  
  250.   // SHY: minimal movements, down-looking
  251.   emotionConfigs[SHY] = {
  252.     0.6f,      // animationSpeed
  253.     1.0f,      // blinkFrequency
  254.     0.4f,      // pupilTrackingIntensity
  255.     0.3f,      // microMoveAmount
  256.     -10,       // eyebrowAngle
  257.     0.1f,      // mouthHeight
  258.     0.4f,      // cheekColor (blushing)
  259.     0.2f       // irisGlowIntensity
  260.   };
  261.  
  262.   // PLAYFUL: bouncy, dynamic movements
  263.   emotionConfigs[PLAYFUL] = {
  264.     1.8f,      // animationSpeed
  265.     2.5f,      // blinkFrequency
  266.     0.85f,     // pupilTrackingIntensity
  267.     1.3f,      // microMoveAmount
  268.     12,        // eyebrowAngle
  269.     0.6f,      // mouthHeight
  270.     0.5f,      // cheekColor
  271.     0.6f       // irisGlowIntensity
  272.   };
  273. }
  274.  
  275. /****** PHYSICS ENGINE: UPDATE PUPIL TRACKING *****/
  276. void updatePupilTracking(float deltaTime) {
  277.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  278.  
  279.   // Calculate target pupil position based on gaze
  280.   physics.targetPupilX = animation.gazeX * config.pupilTrackingIntensity;
  281.   physics.targetPupilY = animation.gazeY * config.pupilTrackingIntensity;
  282.  
  283.   // Inertia damping: smooth acceleration towards target
  284.   float inertiaFactor = 5.0f * config.pupilTrackingIntensity;
  285.   float maxVelocity = 2.0f;
  286.  
  287.   // Update velocity with acceleration towards target
  288.   physics.pupilVelX += (physics.targetPupilX - physics.pupilX) * inertiaFactor * deltaTime;
  289.   physics.pupilVelY += (physics.targetPupilY - physics.pupilY) * inertiaFactor * deltaTime;
  290.  
  291.   // Clamp velocity
  292.   physics.pupilVelX = constrain(physics.pupilVelX, -maxVelocity, maxVelocity);
  293.   physics.pupilVelY = constrain(physics.pupilVelY, -maxVelocity, maxVelocity);
  294.  
  295.   // Apply damping (friction)
  296.   float damping = 0.92f;
  297.   physics.pupilVelX *= damping;
  298.   physics.pupilVelY *= damping;
  299.  
  300.   // Update position
  301.   physics.pupilX += physics.pupilVelX * deltaTime;
  302.   physics.pupilY += physics.pupilVelY * deltaTime;
  303.  
  304.   // Clamp pupil position within iris circle, not extending beyond
  305.   // Maximum distance pupil can move from center is limited to iris radius minus safety margin
  306.   float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
  307.   if (pupilDist > 0.65f) {
  308.     float scale = 0.65f / pupilDist;
  309.     physics.pupilX *= scale;
  310.     physics.pupilY *= scale;
  311.   }
  312. }
  313.  
  314. /****** PHYSICS ENGINE: UPDATE BLINKING *****/
  315. void updateBlinking(float deltaTime) {
  316.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  317.  
  318.   // Calculate blink duration based on emotion
  319.   float blinkDuration = 0.15f;  // 150ms standard blink
  320.   float blinkInterval = 1.0f / config.blinkFrequency;
  321.  
  322.   if (!physics.isBlinking) {
  323.     // Check if it's time to blink
  324.     float timeSinceBlink = (millis() - physics.blinkStartTime) / 1000.0f;
  325.    
  326.     // Add some randomness to blink timing
  327.     float randomBlink = 0.8f + (random(40) / 100.0f);  // 0.8 to 1.2 variation
  328.    
  329.     if (timeSinceBlink > blinkInterval * randomBlink) {
  330.       physics.isBlinking = true;
  331.       physics.blinkStartTime = millis();
  332.       physics.blinkVel = 4.0f / blinkDuration;  // Velocity to close eyes in blinkDuration
  333.     }
  334.   } else {
  335.     // Animate blinking
  336.     float timeSinceBlink = (millis() - physics.blinkStartTime) / 1000.0f;
  337.    
  338.     if (timeSinceBlink < 0.075f) {
  339.       // Closing phase (first 75ms)
  340.       physics.blinkValue += physics.blinkVel * deltaTime;
  341.       physics.blinkValue = min(physics.blinkValue, 1.0f);
  342.     } else if (timeSinceBlink < 0.15f) {
  343.       // Opening phase (next 75ms)
  344.       physics.blinkValue -= physics.blinkVel * deltaTime;
  345.       physics.blinkValue = max(physics.blinkValue, 0.0f);
  346.     } else {
  347.       // Blink complete
  348.       physics.isBlinking = false;
  349.       physics.blinkValue = 0.0f;
  350.       physics.blinkStartTime = millis();
  351.     }
  352.   }
  353. }
  354.  
  355. /****** PHYSICS ENGINE: UPDATE EYEBROW ANIMATION *****/
  356. void updateEyebrows(float deltaTime) {
  357.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  358.  
  359.   // Base eyebrow angle from emotion config
  360.   physics.eyebrowTargetAngle = config.eyebrowAngle;
  361.  
  362.   // Add micro-animations based on emotion phase
  363.   float emotionInfluence = sin(animation.animationPhase * M_PI * 2.0f);
  364.  
  365.   switch(animation.currentEmotion) {
  366.     case HAPPY:
  367.     case EXCITED:
  368.       physics.eyebrowTargetAngle += emotionInfluence * 5.0f;
  369.       break;
  370.     case CURIOUS:
  371.       physics.eyebrowTargetAngle += abs(emotionInfluence) * 3.0f;
  372.       break;
  373.     case SHY:
  374.       physics.eyebrowTargetAngle -= abs(emotionInfluence) * 2.0f;
  375.       break;
  376.     default:
  377.       break;
  378.   }
  379.  
  380.   // Smoothly interpolate to target angle
  381.   float lerpFactor = 8.0f * deltaTime;
  382.   physics.eyebrowAngle += (physics.eyebrowTargetAngle - physics.eyebrowAngle) * lerpFactor;
  383. }
  384.  
  385. /****** PHYSICS ENGINE: UPDATE MOUTH EXPRESSION *****/
  386. void updateMouthExpression(float deltaTime) {
  387.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  388.  
  389.   // Base mouth height from emotion
  390.   float targetMouthHeight = config.mouthHeight;
  391.  
  392.   // Add animation variation
  393.   float animVariation = sin(animation.animationPhase * M_PI * 2.0f);
  394.   targetMouthHeight += animVariation * 0.1f * config.animationSpeed;
  395.  
  396.   // Add mouth angle (smile tilt)
  397.   physics.mouthAngle = config.eyebrowAngle * 0.3f;
  398.  
  399.   // Smooth interpolation
  400.   float lerpFactor = 6.0f * deltaTime;
  401.   physics.mouthHeight += (targetMouthHeight - physics.mouthHeight) * lerpFactor;
  402. }
  403.  
  404. /****** PHYSICS ENGINE: UPDATE CHEEK BLUSH *****/
  405. void updateCheeks(float deltaTime) {
  406.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  407.  
  408.   // Base cheek blush from emotion
  409.   float targetCheekBlush = config.cheekColor;
  410.  
  411.   // Pulse the blush slightly based on animation phase
  412.   float pulseInfluence = (sin(animation.animationPhase * M_PI * 2.0f) + 1.0f) * 0.5f;
  413.   targetCheekBlush += pulseInfluence * 0.15f * config.animationSpeed;
  414.  
  415.   // Smooth interpolation
  416.   float lerpFactor = 3.0f * deltaTime;
  417.   physics.cheekBlush += (targetCheekBlush - physics.cheekBlush) * lerpFactor;
  418.   physics.cheekBlush = constrain(physics.cheekBlush, 0.0f, 1.0f);
  419. }
  420.  
  421. /****** PHYSICS ENGINE: UPDATE IRIS GLOW *****/
  422. void updateIrisGlow(float deltaTime) {
  423.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  424.  
  425.   // Base glow intensity from emotion
  426.   float targetGlow = config.irisGlowIntensity;
  427.  
  428.   // Pulse glow subtly
  429.   float glowPulse = (sin(animation.animationPhase * M_PI * 4.0f) + 1.0f) * 0.5f;
  430.   targetGlow += glowPulse * 0.1f;
  431.  
  432.   // Smooth interpolation
  433.   float lerpFactor = 4.0f * deltaTime;
  434.   physics.irisGlow += (targetGlow - physics.irisGlow) * lerpFactor;
  435.   physics.irisGlow = constrain(physics.irisGlow, 0.0f, 1.0f);
  436. }
  437.  
  438. /****** PHYSICS ENGINE: UPDATE NOSE WIGGLE *****/
  439. void updateNose(float deltaTime) {
  440.   // Nose wiggles subtly with high-frequency animation
  441.   physics.noseWiggle = sin(animation.animationPhase * M_PI * 8.0f) * 0.5f;
  442. }
  443.  
  444. /****** PHYSICS ENGINE: UPDATE MICRO-MOVEMENTS *****/
  445. void updateMicroMovements(float deltaTime) {
  446.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  447.  
  448.   // Generate micro-movement using noise-like pattern
  449.   float time = millis() / 1000.0f;
  450.  
  451.   // Use sine waves at different frequencies to create natural micro-jitter
  452.   animation.microMoveX = sin(time * 2.3f) * 0.5f + sin(time * 3.7f) * 0.3f;
  453.   animation.microMoveY = cos(time * 1.9f) * 0.5f + cos(time * 4.1f) * 0.3f;
  454.  
  455.   // Scale by emotion's micro-move amount
  456.   animation.microMoveX *= config.microMoveAmount;
  457.   animation.microMoveY *= config.microMoveAmount;
  458. }
  459.  
  460. /****** ANIMATION ENGINE: UPDATE GAZE *****/
  461. void updateGaze(float deltaTime) {
  462.   // Generate gaze pattern based on emotion
  463.   float time = millis() / 1000.0f;
  464.  
  465.   switch(animation.currentEmotion) {
  466.     case NEUTRAL:
  467.       // Gentle scanning
  468.       animation.gazeX = sin(time * 0.5f) * 0.7f;
  469.       animation.gazeY = cos(time * 0.7f) * 0.5f;
  470.       break;
  471.      
  472.     case HAPPY:
  473.       // Looking upward and around
  474.       animation.gazeX = sin(time * 0.8f) * 0.8f;
  475.       animation.gazeY = 0.5f + cos(time * 0.6f) * 0.3f;
  476.       break;
  477.      
  478.     case SAD:
  479.       // Looking downward
  480.       animation.gazeX = sin(time * 0.3f) * 0.4f;
  481.       animation.gazeY = -0.6f + sin(time * 0.5f) * 0.2f;
  482.       break;
  483.      
  484.     case EXCITED:
  485.       // Rapid, wide scanning
  486.       animation.gazeX = sin(time * 1.5f);
  487.       animation.gazeY = cos(time * 1.8f);
  488.       break;
  489.      
  490.     case SLEEPY:
  491.       // Slowly drooping gaze
  492.       animation.gazeX = sin(time * 0.2f) * 0.3f;
  493.       animation.gazeY = -0.5f - abs(sin(time * 0.3f)) * 0.3f;
  494.       break;
  495.      
  496.     case CURIOUS:
  497.       // Focused searching
  498.       animation.gazeX = sin(time * 1.2f) * 0.9f;
  499.       animation.gazeY = cos(time * 1.0f) * 0.8f;
  500.       break;
  501.      
  502.     case SHY:
  503.       // Shy glancing down
  504.       animation.gazeX = sin(time * 0.4f) * 0.3f;
  505.       animation.gazeY = -0.7f + sin(time * 0.6f) * 0.2f;
  506.       break;
  507.      
  508.     case PLAYFUL:
  509.       // Bouncy, playful eye movement
  510.       animation.gazeX = sin(time * 1.3f) * 0.85f;
  511.       animation.gazeY = abs(sin(time * 1.6f)) * 0.7f;
  512.       break;
  513.   }
  514.  
  515.   // Clamp gaze to valid range
  516.   animation.gazeX = constrain(animation.gazeX, -1.0f, 1.0f);
  517.   animation.gazeY = constrain(animation.gazeY, -1.0f, 1.0f);
  518. }
  519.  
  520. /****** ANIMATION ENGINE: UPDATE AUTONOMOUS BEHAVIOR *****/
  521. void updateAutonomousBehavior(float deltaTime) {
  522.   // Generate random gestures and state changes in autonomous mode
  523.   static float autonomousPhase = 0.0f;
  524.  
  525.   // Slowly accumulate phase
  526.   autonomousPhase += deltaTime * 0.2f;
  527.   if (autonomousPhase > 1.0f) {
  528.     autonomousPhase -= 1.0f;
  529.    
  530.     // Randomly change emotion during autonomous mode
  531.     if (random(100) < 30) {  // 30% chance each cycle
  532.       animation.targetEmotion = (Emotion)(random(8));
  533.     }
  534.   }
  535.  
  536.   // Add more pronounced micro-movements in autonomous mode
  537.   float autonomousJitter = sin(millis() / 500.0f) * 0.3f;
  538.   animation.microMoveX += autonomousJitter;
  539.   animation.microMoveY += cos(millis() / 700.0f) * 0.3f;
  540. }
  541.  
  542. /****** ANIMATION ENGINE: UPDATE ANIMATION PHASE *****/
  543. void updateAnimationPhase(float deltaTime) {
  544.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  545.  
  546.   // Update animation phase (cycles 0.0 to 1.0)
  547.   animation.animationPhase += deltaTime * config.animationSpeed;
  548.   if (animation.animationPhase > 1.0f) {
  549.     animation.animationPhase -= 1.0f;
  550.   }
  551. }
  552.  
  553. /****** ANIMATION ENGINE: UPDATE EMOTION TRANSITION *****/
  554. void updateEmotionTransition(float deltaTime) {
  555.   // Smooth transition between emotions
  556.   if (animation.currentEmotion != animation.targetEmotion) {
  557.     // Immediate emotion change (can be made smoother if needed)
  558.     animation.currentEmotion = animation.targetEmotion;
  559.   }
  560. }
  561.  
  562. /****** ANIMATION ENGINE: MAIN UPDATE LOOP *****/
  563. void updateAnimation() {
  564.   // Calculate delta time
  565.   unsigned long currentTime = millis();
  566.   animation.frameTime = currentTime;
  567.  
  568.   if (animation.lastFrameTime == 0) {
  569.     animation.lastFrameTime = currentTime;
  570.   }
  571.  
  572.   animation.deltaTime = (currentTime - animation.lastFrameTime) / 1000.0f;
  573.   animation.lastFrameTime = currentTime;
  574.  
  575.   // Cap delta time to prevent physics instability
  576.   if (animation.deltaTime > 0.033f) {
  577.     animation.deltaTime = 0.033f;
  578.   }
  579.  
  580.   // Update physics systems
  581.   updatePupilTracking(animation.deltaTime);
  582.   updateBlinking(animation.deltaTime);
  583.   updateEyebrows(animation.deltaTime);
  584.   updateMouthExpression(animation.deltaTime);
  585.   updateCheeks(animation.deltaTime);
  586.   updateIrisGlow(animation.deltaTime);
  587.   updateNose(animation.deltaTime);
  588.   updateMicroMovements(animation.deltaTime);
  589.   updateGaze(animation.deltaTime);
  590.  
  591.   // Update animation control
  592.   updateAnimationPhase(animation.deltaTime);
  593.   updateEmotionTransition(animation.deltaTime);
  594.  
  595.   // Update autonomous behavior if active
  596.   if (animation.autonomousMode) {
  597.     updateAutonomousBehavior(animation.deltaTime);
  598.   }
  599.  
  600.   // Increment frame count
  601.   animation.frameCount++;
  602. }
  603.  
  604. /****** RENDERING: DRAW LEFT EYE *****/
  605. void drawLeftEye(int16_t centerX, int16_t centerY, int16_t radius) {
  606.   // Eye white (sclera)
  607.   display.fillCircle(centerX, centerY, radius, SSD1306_WHITE);
  608.  
  609.   // Apply eyelid closure (blink effect) with bounds checking
  610.   if (physics.blinkValue > 0.1f) {
  611.     int16_t eyelidHeight = (int16_t)(radius * 2.0f * physics.blinkValue);
  612.     // Ensure eyelidHeight does not exceed display bounds
  613.     eyelidHeight = min(eyelidHeight, (int16_t)(radius * 2));
  614.     // Top eyelid with safe fillRect bounds
  615.     int16_t safeX = max((int16_t)(centerX - radius), (int16_t)0);
  616.     int16_t safeY = max((int16_t)(centerY - radius), (int16_t)0);
  617.     int16_t safeWidth = min((int16_t)(radius * 2), (int16_t)(SCREEN_WIDTH - safeX));
  618.     int16_t safeHeight = min(eyelidHeight, (int16_t)(SCREEN_HEIGHT - safeY));
  619.     if (safeWidth > 0 && safeHeight > 0) {
  620.       display.fillRect(safeX, safeY, safeWidth, safeHeight, SSD1306_BLACK);
  621.     }
  622.   }
  623.  
  624.   // Calculate pupil position with physics, constrained within iris circle
  625.   float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
  626.   float pupilScale = 1.0f;
  627.   if (pupilDist > 0.65f) {
  628.     pupilScale = 0.65f / pupilDist;
  629.   }
  630.   int16_t pupilX = centerX + (int16_t)(physics.pupilX * pupilScale * (radius - 5));
  631.   int16_t pupilY = centerY + (int16_t)(physics.pupilY * pupilScale * (radius - 5));
  632.  
  633.   // Iris (darker circle)
  634.   display.fillCircle(pupilX, pupilY, radius / 2, SSD1306_BLACK);
  635.  
  636.   // Pupil (very dark center)
  637.   display.fillCircle(pupilX, pupilY, radius / 3, SSD1306_BLACK);
  638.  
  639.   // Iris glow effect (small highlights)
  640.   if (physics.irisGlow > 0.0f) {
  641.     int8_t glowIntensity = (int8_t)(physics.irisGlow * 255);
  642.     if (glowIntensity > 50) {
  643.       display.drawCircle(pupilX - 2, pupilY - 2, 2, SSD1306_WHITE);
  644.     }
  645.   }
  646. }
  647.  
  648. /****** RENDERING: DRAW RIGHT EYE *****/
  649. void drawRightEye(int16_t centerX, int16_t centerY, int16_t radius) {
  650.   // Eye white (sclera)
  651.   display.fillCircle(centerX, centerY, radius, SSD1306_WHITE);
  652.  
  653.   // Apply eyelid closure (blink effect) with bounds checking
  654.   if (physics.blinkValue > 0.1f) {
  655.     int16_t eyelidHeight = (int16_t)(radius * 2.0f * physics.blinkValue);
  656.     // Ensure eyelidHeight does not exceed display bounds
  657.     eyelidHeight = min(eyelidHeight, (int16_t)(radius * 2));
  658.     // Top eyelid with safe fillRect bounds
  659.     int16_t safeX = max((int16_t)(centerX - radius), (int16_t)0);
  660.     int16_t safeY = max((int16_t)(centerY - radius), (int16_t)0);
  661.     int16_t safeWidth = min((int16_t)(radius * 2), (int16_t)(SCREEN_WIDTH - safeX));
  662.     int16_t safeHeight = min(eyelidHeight, (int16_t)(SCREEN_HEIGHT - safeY));
  663.     if (safeWidth > 0 && safeHeight > 0) {
  664.       display.fillRect(safeX, safeY, safeWidth, safeHeight, SSD1306_BLACK);
  665.     }
  666.   }
  667.  
  668.   // Calculate pupil position with physics, constrained within iris circle
  669.   float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
  670.   float pupilScale = 1.0f;
  671.   if (pupilDist > 0.65f) {
  672.     pupilScale = 0.65f / pupilDist;
  673.   }
  674.   int16_t pupilX = centerX + (int16_t)(physics.pupilX * pupilScale * (radius - 5));
  675.   int16_t pupilY = centerY + (int16_t)(physics.pupilY * pupilScale * (radius - 5));
  676.  
  677.   // Iris (darker circle)
  678.   display.fillCircle(pupilX, pupilY, radius / 2, SSD1306_BLACK);
  679.  
  680.   // Pupil (very dark center)
  681.   display.fillCircle(pupilX, pupilY, radius / 3, SSD1306_BLACK);
  682.  
  683.   // Iris glow effect (small highlights)
  684.   if (physics.irisGlow > 0.0f) {
  685.     int8_t glowIntensity = (int8_t)(physics.irisGlow * 255);
  686.     if (glowIntensity > 50) {
  687.       display.drawCircle(pupilX - 2, pupilY - 2, 2, SSD1306_WHITE);
  688.     }
  689.   }
  690. }
  691.  
  692. /****** RENDERING: DRAW EYEBROWS *****/
  693. void drawEyebrows(int16_t leftEyeX, int16_t rightEyeX, int16_t eyeY, int16_t eyeRadius) {
  694.   int16_t eyebrowY = eyeY - eyeRadius - 8;
  695.   int16_t eyebrowWidth = eyeRadius + 5;
  696.  
  697.   // Convert angle to slope for drawing
  698.   int8_t angleOffset = (int8_t)(physics.eyebrowAngle * 0.3f);
  699.  
  700.   // Left eyebrow (with angle) - draw using line-based approach
  701.   display.drawLine(leftEyeX - eyebrowWidth, eyebrowY + angleOffset,
  702.                    leftEyeX + eyebrowWidth, eyebrowY - angleOffset, SSD1306_WHITE);
  703.   display.drawLine(leftEyeX - eyebrowWidth, eyebrowY + angleOffset + 1,
  704.                    leftEyeX + eyebrowWidth, eyebrowY - angleOffset + 1, SSD1306_WHITE);
  705.  
  706.   // Right eyebrow (with angle) - draw using line-based approach
  707.   display.drawLine(rightEyeX - eyebrowWidth, eyebrowY - angleOffset,
  708.                    rightEyeX + eyebrowWidth, eyebrowY + angleOffset, SSD1306_WHITE);
  709.   display.drawLine(rightEyeX - eyebrowWidth, eyebrowY - angleOffset + 1,
  710.                    rightEyeX + eyebrowWidth, eyebrowY + angleOffset + 1, SSD1306_WHITE);
  711. }
  712.  
  713. /****** RENDERING: DRAW MOUTH *****/
  714. void drawMouth(int16_t centerX, int16_t centerY) {
  715.   int16_t mouthWidth = 20;
  716.   int16_t mouthY = centerY + 10;
  717.  
  718.   // Mouth curve based on expression
  719.   int16_t curveHeight = (int16_t)(physics.mouthHeight * 8.0f);
  720.  
  721.   if (curveHeight > 0) {
  722.     // Happy/smiling mouth (arc) - draw using line-based curves with step=1
  723.     for (int16_t x = -mouthWidth; x <= mouthWidth; x += 1) {
  724.       int16_t y = (int16_t)(curveHeight - (x * x) / (float)(mouthWidth * mouthWidth) * curveHeight);
  725.       display.drawPixel(centerX + x, mouthY + y, SSD1306_WHITE);
  726.     }
  727.   } else if (curveHeight < 0) {
  728.     // Sad mouth (inverted arc) - draw using line-based curves with step=1
  729.     for (int16_t x = -mouthWidth; x <= mouthWidth; x += 1) {
  730.       int16_t y = (int16_t)(curveHeight + (x * x) / (float)(mouthWidth * mouthWidth) * abs(curveHeight));
  731.       display.drawPixel(centerX + x, mouthY + y, SSD1306_WHITE);
  732.     }
  733.   } else {
  734.     // Neutral mouth (straight line)
  735.     display.drawLine(centerX - mouthWidth, mouthY, centerX + mouthWidth, mouthY, SSD1306_WHITE);
  736.   }
  737. }
  738.  
  739. /****** RENDERING: DRAW NOSE *****/
  740. void drawNose(int16_t centerX, int16_t centerY) {
  741.   int16_t noseX = centerX + (int16_t)(physics.noseWiggle * 2.0f);
  742.   int16_t noseY = centerY - 2;
  743.  
  744.   // Simple nose (small triangle)
  745.   display.drawLine(noseX, noseY, noseX - 2, noseY + 3, SSD1306_WHITE);
  746.   display.drawLine(noseX, noseY, noseX + 2, noseY + 3, SSD1306_WHITE);
  747.   display.drawLine(noseX - 2, noseY + 3, noseX + 2, noseY + 3, SSD1306_WHITE);
  748. }
  749.  
  750. /****** RENDERING: DRAW CHEEKS *****/
  751. void drawCheeks(int16_t leftEyeX, int16_t rightEyeX, int16_t eyeY) {
  752.   int16_t cheekRadius = 5;
  753.   int16_t cheekY = eyeY + 10;
  754.  
  755.   // Cheek blush intensity determines if we draw
  756.   if (physics.cheekBlush > 0.1f) {
  757.     // Left cheek - use simple fillCircle at position with opacity simulation using single circle
  758.     display.fillCircle(leftEyeX - 20, cheekY, cheekRadius, SSD1306_WHITE);
  759.    
  760.     // Right cheek - use simple fillCircle at position with opacity simulation using single circle
  761.     display.fillCircle(rightEyeX + 20, cheekY, cheekRadius, SSD1306_WHITE);
  762.   }
  763. }
  764.  
  765. /****** RENDERING: MAIN DRAW FUNCTION *****/
  766. void drawFace() {
  767.   // Clear display
  768.   display.clearDisplay();
  769.  
  770.   // Calculate face position with micro-movements
  771.   int16_t faceCenterX = SCREEN_WIDTH / 2 + (int16_t)(animation.microMoveX * 2.0f);
  772.   int16_t faceCenterY = SCREEN_HEIGHT / 2 + (int16_t)(animation.microMoveY * 2.0f);
  773.  
  774.   // Increased eye radius from 8 to 12 pixels for better visibility
  775.   int16_t eyeRadius = 12;
  776.   int16_t eyeSeparation = 30;
  777.   int16_t eyeY = faceCenterY - 10;
  778.  
  779.   // Draw eyes
  780.   drawLeftEye(faceCenterX - eyeSeparation / 2, eyeY, eyeRadius);
  781.   drawRightEye(faceCenterX + eyeSeparation / 2, eyeY, eyeRadius);
  782.  
  783.   // Draw eyebrows
  784.   drawEyebrows(faceCenterX - eyeSeparation / 2, faceCenterX + eyeSeparation / 2, eyeY, eyeRadius);
  785.  
  786.   // Draw nose
  787.   drawNose(faceCenterX, faceCenterY);
  788.  
  789.   // Draw mouth
  790.   drawMouth(faceCenterX, faceCenterY);
  791.  
  792.   // Draw cheeks
  793.   drawCheeks(faceCenterX - eyeSeparation / 2, faceCenterX + eyeSeparation / 2, eyeY);
  794.  
  795.   // Display status text (optional, comment out if needed for more room)
  796.   display.setTextSize(1);
  797.   display.setTextColor(SSD1306_WHITE);
  798.   display.setCursor(0, 0);
  799.   display.println(animation.frameCount);
  800.  
  801.   // Push to display
  802.   display.display();
  803. }
  804.  
  805. /****** BUTTON EVENT HANDLER *****/
  806. void handleButtonPress() {
  807.   // Cycle to next emotion
  808.   animation.targetEmotion = (Emotion)((animation.currentEmotion + 1) % 8);
  809.   animation.lastInputTime = millis();
  810.   animation.autonomousMode = false;
  811.  
  812.   // Blink indicator LED
  813.   digitalWrite(emotionButton_LED_PIN_D2, HIGH);
  814.   delay(100);
  815.   digitalWrite(emotionButton_LED_PIN_D2, LOW);
  816. }
  817.  
  818. /****** SETUP FUNCTION *****/
  819. void setup(void) {
  820.   // Initialize serial for debugging
  821.   Serial.begin(115200);
  822.   delay(100);
  823.  
  824.   // Configure GPIO pins
  825.   pinMode(faceDisplay_PushButton_PIN_D1, INPUT_PULLUP);
  826.   pinMode(emotionButton_LED_PIN_D2, OUTPUT);
  827.   digitalWrite(emotionButton_LED_PIN_D2, LOW);
  828.  
  829.   // Initialize OLED display
  830.   if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
  831.     Serial.println(F("SSD1306 allocation failed"));
  832.     for (;;);  // Halt if display fails
  833.   }
  834.  
  835.   // Clear and show startup message
  836.   display.clearDisplay();
  837.   display.setTextSize(1);
  838.   display.setTextColor(SSD1306_WHITE);
  839.   display.setCursor(32, 28);
  840.   display.println(F("Initializing..."));
  841.   display.display();
  842.   delay(1000);
  843.  
  844.   // Initialize emotion configurations
  845.   initializeEmotionConfigs();
  846.  
  847.   // Initialize animation state
  848.   animation.currentEmotion = NEUTRAL;
  849.   animation.targetEmotion = NEUTRAL;
  850.   animation.autonomousMode = false;
  851.   animation.lastInputTime = millis();
  852.   animation.idleThreshold = 5000;  // 5 seconds
  853.   animation.frameTime = 0;
  854.   animation.lastFrameTime = 0;
  855.   animation.frameCount = 0;
  856.   animation.animationPhase = 0.0f;
  857.   animation.phaseVelocity = 0.0f;
  858.   // Initialize micro-movement variables in AnimationState
  859.   animation.microMoveX = 0.0f;
  860.   animation.microMoveY = 0.0f;
  861.   animation.gazeX = 0.0f;
  862.   animation.gazeY = 0.0f;
  863.  
  864.   // Initialize physics state
  865.   physics.pupilX = 0.0f;
  866.   physics.pupilY = 0.0f;
  867.   physics.pupilVelX = 0.0f;
  868.   physics.pupilVelY = 0.0f;
  869.   physics.targetPupilX = 0.0f;
  870.   physics.targetPupilY = 0.0f;
  871.   physics.blinkValue = 0.0f;
  872.   physics.blinkVel = 0.0f;
  873.   physics.isBlinking = false;
  874.   physics.blinkStartTime = millis();
  875.   physics.eyelidOffset = 0.0f;
  876.   physics.eyebrowAngle = 0.0f;
  877.   physics.eyebrowTargetAngle = 0.0f;
  878.   physics.irisGlow = 0.3f;
  879.   physics.mouthHeight = 0.3f;
  880.   physics.mouthAngle = 0.0f;
  881.   physics.cheekBlush = 0.0f;
  882.   physics.noseWiggle = 0.0f;
  883.  
  884.   // Setup EasyButton
  885.   button.begin();
  886.   button.onPressed(handleButtonPress);
  887.  
  888.   // Initialize frame rate limiter
  889.   lastFrameTimeMs = millis();
  890.  
  891.   Serial.println(F("Face animation system initialized!"));
  892.   Serial.println(F("Press button to cycle emotions."));
  893.   Serial.println(F("System idle for 5 seconds to enter autonomous mode."));
  894. }
  895.  
  896. /****** MAIN LOOP *****/
  897. void loop(void) {
  898.   // Read button input
  899.   button.read();
  900.  
  901.   // Check for idle timeout (autonomous mode)
  902.   unsigned long timeSinceLastInput = millis() - animation.lastInputTime;
  903.   if (timeSinceLastInput > animation.idleThreshold && !animation.autonomousMode) {
  904.     animation.autonomousMode = true;
  905.     Serial.println(F("Entering autonomous mode..."));
  906.   }
  907.  
  908.   // Update animation physics and state
  909.   updateAnimation();
  910.  
  911.   // Render face to OLED
  912.   drawFace();
  913.  
  914.   // Frame rate limiting (22ms per frame for 45 FPS) to eliminate tearing
  915.   unsigned long currentTime = millis();
  916.   unsigned long elapsedTime = currentTime - lastFrameTimeMs;
  917.   if (elapsedTime < FRAME_TIME_MS) {
  918.     delay(FRAME_TIME_MS - elapsedTime);
  919.   }
  920.   lastFrameTimeMs = millis();
  921. }
  922.  
  923. /* END CODE */
  924.  
Advertisement
Add Comment
Please, Sign In to add comment