Two Ways to Build an Eyeball

Comparing/Contrasting Stage 6 (procedural) with Stage 7 (SWEyeball class)

Stage 6 Procedural Composition

The eyeball is built from 14 individual component variables managed directly in the sketch. All geometry, state, and drag logic live in global scope.

Open Stage 6 Demo →
Stage 7 SWEyeball Class

The same eyeball is fully encapsulated in SWEyeball. The sketch creates two instances and delegates all drawing and interaction to the class.

Open Stage 7 Demo →

The Core Difference

These two stages produce identical visual output on screen. The difference is entirely about where the knowledge lives.

Stage 6Procedural composition: the sketch is the eyeball. All geometry, all state, all behavior lives in global variables and free functions.
Stage 7Object-oriented encapsulation: the sketch uses the eyeball. SWEyeball is a self-contained unit that knows how to build, draw, and drag itself.

Side-by-Side Metrics

Measurement Stage 6 Procedural Stage 7 SWEyeball
Lines in setup() for eye construction ~70 ~20
Global variables for eye components 14 component vars + 3 drag objects 2  (leftEye, rightEye)
Lines in draw() for eyes 8 2
Lines in mouse handlers (combined) ~80 ~12
Adding a 3rd eye to the scene Copy/paste ~70 lines; invent 7 new variable names 1 constructor call

What the Sketch Code Looks Like

The most striking way to see the difference is to compare equivalent passages side by side.

Creating the eyes — setup()

Stage 6   ~70 lines of geometry + disk construction

// 12 geometry calculations...
let glintTopRadius = eyeRadius * pupilFactor
                   * glintFactor;
let glintTopOffsetX = glintTopDistance
                   * cos(radians(glintAngleDeg));
// ...then 6 SWPoint + SWDisk calls per eye,
// repeated twice for left and right,
// then drag-state setup for each eye.

Stage 7   2 constructor calls

leftEye = new SWEyeball(
    new SWPoint(-2.5, 6), // center
    eyeRadius,             // sclera radius
    2,                     // border thickness
    [swWhite, swBlack],    // sclera colors
    pupilFactor,           // 0.4
    irisThickness,         // iris ring weight
    [swBlack, swBlue]      // pupil colors
);
rightEye = new SWEyeball(
    new SWPoint(2.5, 6), ...);

Drawing — draw()

Stage 6

leftEyeBase.drawOnGrid(grid);
leftPupil.drawOnGrid(grid);
leftTopGlint.drawOnGrid(grid);
leftBottomGlint.drawOnGrid(grid);
rightEyeBase.drawOnGrid(grid);
rightPupil.drawOnGrid(grid);
rightTopGlint.drawOnGrid(grid);
rightBottomGlint.drawOnGrid(grid);

Stage 7

leftEye.drawOnGrid(grid);
rightEye.drawOnGrid(grid);

Drawing order (sclera → pupil → top glint → bottom glint) is handled internally by SWEyeball.drawOnGrid().

Mouse interaction — mousePressed()

Stage 6   ~40 lines per handler

if (leftPupil.center.isDraggable &&
    leftPupil.center.containsPoint(...)) {
    draggedPoint = leftPupil.center;
    leftDragDependents.topGlint = leftTopGlint;
    leftDragDependents.bottomGlint = ...;
    leftDragDependents.topGlintCenter = ...;
    // ... repeated for right eye
}

Stage 7

if (leftEye.handleMousePressed(
        mouseX, mouseY, grid)) return;
if (rightEye.handleMousePressed(
        mouseX, mouseY, grid)) return;
showGrid = !showGrid;

Educational Perspective

Stage 6 is valuable as a learning stage, not a destination

Its virtues are real:

  • Every internal decision is visible — nothing is hidden. When a student reads leftTopGlintCenter = new SWPoint(leftPupilCenter.x + glintTopOffsetX, ...) they see exactly how a glint gets positioned.
  • The geometric relationships (offset math, angle calculations, distance factors) are spelled out explicitly.
  • Students can trace the entire program without ever leaving the sketch file.

Stage 6’s liabilities become clear as complexity grows

  • Variable naming burden. leftTopGlintCenter, rightBottomGlintCenter — by the time you have a ghost with multiple eyeballs, you need a systematic naming convention just to keep track of components. This cognitive load is the problem that OOP was invented to solve.
  • Duplication is fragile. The left and right eyes are constructed by copy-paste. If you change glint behavior, you must change it in two places — and eventually someone forgets one.
  • Drag state is tangled with drawing state. draggedPoint, leftDragDependents, and rightDragDependents are conceptually part of the eyeball, but they live at the sketch level. The sketch is responsible for data that belongs to the eye.

Stage 7 teaches the why of classes, not just the syntax

Students can see the before and after side by side and ask: what did the class actually do for us? The answer is concrete:

  • It moved 14 variables and 3 drag objects inside the class where they belong.
  • It turned “what the sketch knows about an eye” from a scattered collection of variables into a single named thing.
  • The sketch now reads like a design description rather than an implementation manual.
The most important lesson Stage 7 demonstrates is the Single Responsibility Principle made visible: the sketch is responsible for the scene; SWEyeball is responsible for the eye.

Professional Perspective

Stage 6 is a prototype

In professional practice, Stage 6 is exactly the kind of code you write when you are figuring something out — exploratory, direct, fast to write. This is legitimate and has a name: spike code or proof of concept. The professional problem is treating prototype code as production code.

Stage 7 is the production pattern

Several properties make it professionally sound:

Reusability

SWEyeball can be dropped into any future sketch — a ghost, a monster, an animated face — with a single constructor call and no knowledge of its internals.

Maintainability

Changing glint geometry means changing code in exactly one place — _buildComponents() in swEyeball.js. In Stage 6, the same change touches both eyes and potentially the drag state too.

API Design: the handle* pattern

The handleMousePressed / handleMouseDragged / handleMouseReleased pattern is a well-established convention for delegating event handling to objects that own the relevant state. The sketch calls the method; the object decides whether to act. This is how UI component libraries like Bootstrap handle events internally.

Extensibility

The rebuild() / property-override pattern (glintFactor, glintAngleDeg, etc.) gives users a clean configuration API without exposing internal construction. A future SWGhost class can create two SWEyeball instances and customize them without touching swEyeball.js at all:

// Future SWGhost usage — not yet built, but now possible:
let ghost = new SWGhost(new SWPoint(0, 0));
ghost.leftEye.glintAngleDeg  = 120;  // tweak one eye's glint angle
ghost.leftEye.rebuild();             // recompute geometry

The Honest Trade-off

Stage 7 does have one cost: indirection. To understand what actually draws on screen, a reader must now follow the call chain:

leftEye.drawOnGrid(grid)
  → SWEyeball._buildComponents()  (called at construction)
  → eyeBase.drawOnGrid(grid)
  → pupil.drawOnGrid(grid)
  → topGlint.drawOnGrid(grid)
  → bottomGlint.drawOnGrid(grid)

In Stage 6, the entire drawing path is in plain sight. This is the fundamental trade-off of encapsulation everywhere in software:

You pay with indirection; you buy with simplicity at the point of use.
The right answer depends on scale. For a two-eye sketch, Stage 6 is honestly fine. For a ghost with multiple eyes, an animated face, or a library shared across projects, Stage 7 is the only defensible choice.

The progression from Stage 6 to Stage 7 is therefore not just a coding exercise — it demonstrates the exact moment in a project’s growth when a class starts paying for itself.

SWEyeball Class Quick Reference

Constructor

new SWEyeball(
    eyeCenter,       // SWPoint   – position in user coordinates
    radius,          // number    – sclera (white) radius
    baseThickness,   // number    – sclera border stroke weight
    eyeColors,       // SWColor[2]  – [sclera fill, sclera border]
    pupilFactor,     // number    – pupil radius = radius × pupilFactor
    irisThickness,   // number    – iris ring stroke weight
    pupilColors      // SWColor[2]  – [pupil fill, iris stroke]
)

Key Methods

MethodCall fromPurpose
drawOnGrid(grid)draw()Renders all 4 internal disks in correct z-order
handleMousePressed(mx, my, grid)mousePressed()Returns true if drag started on this eye
handleMouseDragged(mx, my, grid)mouseDragged()Moves pupil + glints if this eye is being dragged
handleMouseReleased()mouseReleased()Clears drag state
setBaseAlpha(alpha)anytimeSets sclera transparency (0–100)
rebuild()after property changesRecomputes internal geometry after glint properties are modified
toString()anytimeReturns a concise string description

Customizable Glint Properties (set before rebuild())

PropertyDefaultMeaning
glintFactor0.4Top glint radius = pupilRadius × glintFactor
glintColorswWhiteFill and stroke color of both glints
glintAngleDeg135Angle for the primary (top-left) glint
glintBottomAngleDeg315Angle for the secondary (bottom-right) glint
glintDistanceFactor1.0Distance of top glint from pupil center (× pupilRadius)
glintBottomDistFactor0.7Distance of bottom glint from pupil center (× pupilRadius)
isPupilDraggabletrueEnable / disable pupil drag interaction

Try It Yourself

Open both demos and drag the pupils around. The behavior is identical — the code behind it is not.