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 →The same eyeball is fully encapsulated in SWEyeball. The sketch creates two instances and delegates all drawing and interaction to the class.
The Core Difference
These two stages produce identical visual output on screen. The difference is entirely about where the knowledge lives.
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, andrightDragDependentsare 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.
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:
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
| Method | Call from | Purpose |
|---|---|---|
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) | anytime | Sets sclera transparency (0–100) |
rebuild() | after property changes | Recomputes internal geometry after glint properties are modified |
toString() | anytime | Returns a concise string description |
Customizable Glint Properties (set before rebuild())
| Property | Default | Meaning |
|---|---|---|
glintFactor | 0.4 | Top glint radius = pupilRadius × glintFactor |
glintColor | swWhite | Fill and stroke color of both glints |
glintAngleDeg | 135 | Angle for the primary (top-left) glint |
glintBottomAngleDeg | 315 | Angle for the secondary (bottom-right) glint |
glintDistanceFactor | 1.0 | Distance of top glint from pupil center (× pupilRadius) |
glintBottomDistFactor | 0.7 | Distance of bottom glint from pupil center (× pupilRadius) |
isPupilDraggable | true | Enable / 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.