setup()
setup() runs once when the sketch starts. This is where you initialize all your data structures, textures, and game state. For this sketch, setup() builds the entire 3D character: the spherical head mesh using mathematical sphere parameterization, the limbs with their dual rest/puddle positions, and the UI. Understanding how the Node3D grid is constructed is key to understanding how deformation works later.
🔬 These three lines use sine and cosine to convert lat/lon into 3D coordinates (sphere math). What happens if you add a multiplier like `let z = radius * sin(lat) * cos(lon) * 2;` to stretch the head in the Z direction?
let x = radius * sin(lat) * sin(lon);
let y = -radius * cos(lat);
let z = radius * sin(lat) * cos(lon);
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
textureMode(NORMAL);
buildUI();
// 1. Initialize Textures
faceTex = createGraphics(512, 512);
staticFaceTex = createGraphics(512, 512);
drawStaticFace(staticFaceTex);
torsoTex = createGraphics(512, 512);
torsoTex.background('#E76F51');
torsoTex.fill(255);
torsoTex.textAlign(CENTER, CENTER);
torsoTex.textStyle(BOLD);
torsoTex.textSize(150);
torsoTex.text("E", 128, 256);
torsoTex.text("E", 384, 256);
// 2. Initialize Face Nodes
for (let i = 0; i <= cols; i++) {
nodes[i] = [];
let lat = map(i, 0, cols, 0, PI);
for (let j = 0; j <= rows; j++) {
let lon = map(j, 0, rows, -PI, PI);
let x = radius * sin(lat) * sin(lon);
let y = -radius * cos(lat);
let z = radius * sin(lat) * cos(lon);
let u = map(lon, -PI, PI, 0, 1);
let v = map(lat, 0, PI, 0, 1);
nodes[i][j] = new Node3D(x, y, z, u, v, lat, lon);
}
}
// 3. Initialize Body Physics Nodes (Normal Position, Melted Bowl Position)
torsoNode = new LimbNode(0, 260, -20, 120, null, createVector(0, 680, 0));
lHandNode = new LimbNode(-160, 400, -20, 45, createVector(-160, 140, 0), createVector(-180, 40, 0));
rHandNode = new LimbNode(160, 400, -20, 45, createVector(160, 140, 0), createVector(180, 40, 0));
lFootNode = new LimbNode(-70, 600, -20, 45, createVector(-70, 340, 0), createVector(-80, 40, 80));
rFootNode = new LimbNode(70, 600, -20, 45, createVector(70, 340, 0), createVector(80, 40, 80));
speechSynth = window.speechSynthesis;
grabStartScreen = createVector();
// First blink sometime soon after play starts
nextBlinkFrame = int(random(40, 160));
}
Line-by-line explanation (17 lines)
🔧 Subcomponents:
createCanvas(windowWidth, windowHeight, WEBGL);
Creates a full-screen WEBGL canvas (3D rendering mode) and configures texture coordinates to use normalized 0–1 ranges
for (let i = 0; i <= cols; i++) { for (let j = 0; j <= rows; j++) { ... } }
Creates a 2D grid of Node3D particles arranged in latitude/longitude coordinates to form a sphere, storing both UV texture coordinates and spherical math data
torsoNode = new LimbNode(0, 260, -20, 120, null, createVector(0, 680, 0));
Spawns the torso and four limbs with both normal resting positions and puddle-melted positions stored in each node
createCanvas(windowWidth, windowHeight, WEBGL);- Creates a full-window WEBGL (3D) canvas; WEBGL mode unlocks 3D transforms, lighting, and mesh rendering
textureMode(NORMAL);- Sets texture coordinates to use normalized 0–1 range (instead of pixel coordinates), making textures map consistently regardless of size
buildUI();- Calls the UI builder to create HOME screen, CREDITS screen, and in-game buttons (PLAY, PUDDLE MODE, etc.)
faceTex = createGraphics(512, 512);- Creates an off-screen graphics buffer (512×512 pixels) that will hold the animated face texture updated every frame
staticFaceTex = createGraphics(512, 512);- Creates a second graphics buffer for the static (unchanging) parts of the face; drawn once and reused to save performance
drawStaticFace(staticFaceTex);- Pre-draws the static face features (eyes, cheeks, nose, eyebrows, mouth outline) once to a texture, avoiding redrawing them every frame
let lat = map(i, 0, cols, 0, PI);- Maps column index i to latitude values (0 to π), representing vertical position on a sphere from top pole to bottom pole
let lon = map(j, 0, rows, -PI, PI);- Maps row index j to longitude values (−π to π), representing horizontal wrap-around position on a sphere from left to right
let x = radius * sin(lat) * sin(lon);- Converts spherical latitude/longitude to Cartesian X coordinate using standard sphere parameterization; multiplied by radius (150) to scale the mesh
let y = -radius * cos(lat);- Converts latitude to Cartesian Y (vertical); negated so the north pole points upward; this is the head's vertical axis
let z = radius * sin(lat) * cos(lon);- Converts spherical latitude/longitude to Cartesian Z coordinate; forms the depth dimension of the sphere
let u = map(lon, -PI, PI, 0, 1);- Maps longitude to horizontal texture coordinate (0 to 1); tells the texture how to wrap left/right around the sphere
let v = map(lat, 0, PI, 0, 1);- Maps latitude to vertical texture coordinate (0 to 1); tells the texture how to wrap top/bottom around the sphere
nodes[i][j] = new Node3D(x, y, z, u, v, lat, lon);- Creates a mesh node at this grid position, storing position, texture coordinates, and spherical math needed for later deformations
torsoNode = new LimbNode(0, 260, -20, 120, null, createVector(0, 680, 0));- Creates the torso node at (0, 260, -20) with radius 100; puddleOffset is (0, 680, 0) so it falls to the bowl bottom when melting
lHandNode = new LimbNode(-160, 400, -20, 45, createVector(-160, 140, 0), createVector(-180, 40, 0));- Creates left hand node; localOffset (−160, 140, 0) is its normal position relative to torso; puddleOffset (−180, 40, 0) is where it lands in the puddle
nextBlinkFrame = int(random(40, 160));- Schedules the first blink to occur randomly between frame 40 and 160, so Eric's eyes don't stay frozen after loading