To compile this code you will need the following libraries.
This guide will show you how to install the libraries:
```// Googly Eye Goggles
// By Bill Earl
//
// The googly eye effect is based on a physical model of a pendulum.
// The pendulum motion is driven by accelerations in 2 axis.
// Eye color varies with orientation of the magnetometer

#include <Wire.h>

#define neoPixelPin 10

// We could do this as 2 16-pixel rings wired in parallel.
// But keeping them separate lets us do the right and left
// eyes separately if we want.

float pos = 8;  // Starting center position of pupil
float increment = 2 * 3.14159 / 16; // distance between pixels in radians
float MomentumH = 0; // horizontal component of pupil rotational inertia
float MomentumV = 0; // vertical component of pupil rotational inertia

// Tuning constants. (a.k.a. "Fudge Factors)
// These can be tweaked to adjust the liveliness and sensitivity of the eyes.
const float friction = 0.995; // frictional damping constant.  1.0 is no friction.
const float swing = 60;  // arbitrary divisor for gravitational force
const float gravity = 200;  // arbitrary divisor for lateral acceleration
const float nod = 7.5; // accelerometer threshold for toggling modes

long nodStart = 0;
long nodTime = 2000;

bool antiGravity = false;  // The pendulum will anti-gravitate to the top.
bool mirroredEyes = false; // The left eye will mirror the right.

const float halfWidth = 1.25; // half-width of pupil (in pixels)

// Pi for calculations - not the raspberry type
const float Pi = 3.14159;

void setup(void)
{
strip.begin();
strip.show(); // Initialize all pixels to 'off'  sensor_t sensor;

// Initialize the sensors
accel.begin();
mag.begin();

resetModes();
}

// main processing loop
void loop(void)
{
sensors_event_t event;
mag.getEvent(&event);

// Calculate the angle of the vector y,x from magnetic North
float heading = (atan2(event.magnetic.y,event.magnetic.x) * 180) / Pi;

// Normalize to 0-360 for a compass heading
{
}

// Now read the accelerometer to control the motion.
accel.getEvent(&event);

// Check for mode change commands
CheckForNods(event);

// apply a little frictional damping to keep things in control and prevent perpetual motion
MomentumH *= friction;
MomentumV *= friction;

// Calculate the horizontal and vertical effect on the virtual pendulum
// 'pos' is a pixel address, so we multiply by 'increment' to get radians.
float TorqueH = cos(pos * increment);  // peaks at top and bottom of the swing
float TorqueV = sin(pos * increment);    // peaks when the pendulum is horizontal

// Add the incremental acceleration to the existing momentum
// This code assumes that the accelerometer is mounted upside-down, level
// and with the X-axis pointed forward.  So the Y axis reads the horizontal
// acceleration and the inverse of the Z axis is gravity.
// For other orientations of the sensor, just change the axis to match.
MomentumH += TorqueH * event.acceleration.y / swing;
if (antiGravity)
{
MomentumV += TorqueV * event.acceleration.z / gravity;
}
else
{
MomentumV -= TorqueV * event.acceleration.z / gravity;
}

// Calculate the new position
pos += MomentumH + MomentumV;

// handle the wrap-arounds at the top
while (round(pos) < 0) pos += 16.0;
while (round(pos) > 15) pos -= 16.0;

// Now re-compute the display
for (int i = 0; i < 16; i++)
{
// Compute the distance bewteen the pixel and the center
// point of the virtual pendulum.
float diff = i - pos;

// Light up nearby pixels proportional to their proximity to 'pos'
if (fabs(diff) <= halfWidth)
{
uint32_t color;
float proximity = halfWidth - fabs(diff) * 200;

// pick a color based on heading & proximity to 'pos'

// do both eyes
strip.setPixelColor(i, color);
if (mirroredEyes)
{
strip.setPixelColor(31 - i, color);
}
else
{
strip.setPixelColor(i + 16, color);
}
}
else // all others are off
{
strip.setPixelColor(i, 0);
if (mirroredEyes)
{
strip.setPixelColor(31 - i, 0);
}
else
{
strip.setPixelColor(i + 16, 0);
}
}
}
// Now show it!
strip.show();
}

// choose a color based on the compass heading and proximity to "pos".
{
uint32_t color;

// Choose eye color based on the compass heading
{
color = strip.Color(0, 0, proximity);
}
{
color = strip.Color(0, proximity, proximity);
}
{
color = strip.Color(0, proximity, 0);
}
{
color = strip.Color(proximity, proximity, 0);
}
{
color = strip.Color(proximity, 0, 0);
}
else // 300-360
{
color = strip.Color(proximity, 0, proximity);
}
}

// monitor orientation for mode-change 'gestures'
void CheckForNods(sensors_event_t event)
{
if (event.acceleration.x > nod)
{
if (millis() - nodStart > nodTime)
{
antiGravity = false;
nodStart = millis(); // reset timer
spinDown();
}
}
else if (event.acceleration.x < -(nod + 1))
{
if (millis() - nodStart > nodTime)
{
antiGravity = true;
spinUp();
nodStart = millis(); // reset timer
}
}
else if (event.acceleration.y > nod)
{
if (millis() - nodStart > nodTime)
{
mirroredEyes = false;
spinDown();
nodStart = millis(); // reset timer
}
}
else if (event.acceleration.y < -nod)
{
if (millis() - nodStart > nodTime)
{
mirroredEyes = true;
spinUp();
nodStart = millis(); // reset timer
}
}
else // no nods in progress
{
nodStart = millis(); // reset timer
}
}

// Reset to default
void resetModes()
{
antiGravity = false;
mirroredEyes = false;

/// spin-up
spin(strip.Color(255,0,0), 1, 500);
spin(strip.Color(0,255,0), 1, 500);
spin(strip.Color(0,0,255), 1, 500);
spinUp();
}

void spinUp()
{
for (int i = 300; i > 0;  i -= 20)
{
spin(strip.Color(255,255,255), 1, i);
}
pos = 0;
// leave it with some momentum and let it 'coast' to a stop
MomentumH = 3;
}

void spinDown()
{
for (int i = 1; i < 300; i++)
{
spin(strip.Color(255,255,255), 1, i += 20);
}
// Stop it dead at the top and let it swing to the bottom on its own
pos = 0;
MomentumH = MomentumV = 0;
}

// utility function for feedback on mode changes.
void spin(uint32_t color, int count, int time)
{
for (int j = 0; j < count; j++)
{
for (int i = 0; i < 16; i++)
{
strip.setPixelColor(i, color);
strip.setPixelColor(31 - i, color);
strip.show();
delay(max(time / 16, 1));
strip.setPixelColor(i, 0);
strip.setPixelColor(31 - i, 0);
strip.show();
}
}
}```

This guide was first published on Oct 08, 2013. It was last updated on Mar 08, 2024.