Eye 11 adds idle wander: when the mouse isn’t being followed, the pupils drift on their own
using Perlin noise — a smooth, continuous random function that produces natural-looking motion
instead of jumpy randomness. Each eye has its own private noise seed, so they wander independently even
with identical settings. This is the default behavior on launch; Follow mode overrides it.
Big Idea 1 — Why Perlin Noise Beats random()
Calling random() every frame produces white noise — each value is completely unrelated
to the last. Pupils would teleport around rather than drift. Perlin noise is smooth:
neighboring values in the noise field are always close to each other, so the output changes
gradually over time. The eye wanders rather than twitches.
p5.js makes this easy: noise(t) returns a smooth value in [0, 1]
as t advances by small steps each frame.
Big Idea 2 — Two-Channel Cartesian Noise
A common mistake is to use one noise channel and treat its output as a radius, then pick a
random angle. That drives the pupil back and forth through the center, giving a
pendulum feel rather than a 2-D wander. Instead, lookingAround() samples
two independent channels — one for X displacement, one for Y — so the
pupil can roam anywhere in 2-D space smoothly. The two channels are sampled at different
offsets in the noise field to ensure they have no correlation.
Big Idea 3 — Separating Amplitude from Speed
The lookingAround(strength) parameter controls how far the pupil travels
(0 = centered, 1 = full sclera-edge travel).
A separate instance property, noiseSpeed, controls how fast the wandering changes per frame.
These two parameters are independent because they produce completely different personalities:
LOOK_STRENGTH |
noiseSpeed |
Character |
Feels like… |
| 0.8 | 0.002 | Wide & slow | Dreamy, sleepy, half-awake |
| 0.8 | 0.005 | Wide & relaxed | Default — naturally curious |
| 0.8 | 0.015 | Wide & quick | Alert, excited, overstimulated |
| 0.3 | 0.005 | Tight & relaxed | Subtle, barely-moving gaze |
| 0.3 | 0.02 | Tight & fast | Nervous, darting, anxious |
| 1.0 | 0.003 | Maximum & slow | Dramatic, theatrical roaming |
| 0.5 | 0.008 | Medium | Balanced, focused but wandering |
Big Idea 4 — Priority Chains and Modes
All three animation modes — drag, Follow, and
lookingAround — coexist because draw() enforces a
simple priority chain each frame:
- Drag is highest priority. If the user is dragging a pupil,
handleMouseDragged() has already moved it, and the other modes are skipped.
- Follow ON — pupils rotate to face the cursor.
Track ON gives a shared midpoint angle (perfectly in sync);
Track OFF lets each eye compute its own angle (natural vergence / “wonky” mode).
- lookingAround — the idle default. Fires when Follow is OFF.
Track ON mirrors the left eye’s noise output to the right for synchronized wandering;
Track OFF lets each eye follow its own unique noise path.
The sketch doesn’t need to manage conflicts — it just runs the chain top-to-bottom every frame
and the first matching condition wins.
Big Idea 5 — Procedural Animation and Emergent Behavior
Nobody hand-animated these eyes. The wandering is procedural: generated from
a mathematical rule (Perlin noise) rather than a keyframe sequence.
And because each eye has a different noise seed, their independent wandering
emerges into something that looks like two creatures sharing a space,
occasionally glancing toward each other, occasionally apart —
all from about ten lines of code.