Sketch 2025-12-24 00:08

This sketch creates an animated orbital system featuring a morphing planet at the center and a smaller moon orbiting around it. Both shapes continuously transform between a triangle and a dodecagon while rotating, with colors that shift through the rainbow spectrum creating a mesmerizing, symmetrical animation.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the morphing — Multiply the frameCount by a larger number to make the planet and moon transform between triangle and dodecagon faster.
  2. Freeze the planet's spin — Remove the planet's rotation so it morphs but stays still; you'll see the geometry more clearly.
  3. Make the moon orbit faster — Increase the orbit speed multiplier so the moon zips around the planet more quickly.
  4. Sync the moon's morphing to the planet's — Remove the (1 - ) so the moon morphs in the same direction as the planet instead of opposite—they'll both be triangles or dodecagons at the same time.
  5. Add a white outline
  6. Brighten the background — Change the background from almost black to a lighter gray so the shapes have less contrast but feel less intense.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a hypnotic orbital animation where a colorful polygon at the center smoothly morphs between a triangle and a 12-sided shape while a complementary-colored moon orbits around it in the opposite morphing phase. The animation combines several powerful p5.js techniques: custom polygon drawing with beginShape/vertex, smooth morphing via sine waves, orbital motion using trigonometry, dynamic color animation with HSB color mode, and canvas transformations (translate and rotate) that allow both the planet and moon to spin independently around their own centers.

The code is organized into setup(), which configures the canvas and color mode, and draw(), which runs 60 times per second to animate everything. A windowResized() function keeps the animation centered when the window changes size. By studying this sketch, you will learn how to combine animation loops, trigonometric functions, and state variables to create complex, layered motion and how HSB color mode makes it trivial to create a dynamic rainbow effect that orbits with the shapes.

⚙️ How It Works

  1. When the sketch starts, setup() creates a full-window canvas, switches to HSB color mode for easier color animation, and positions the planet at the center.
  2. Every frame, draw() clears the background with a very dark gray to create motion without trails.
  3. The sketch calculates the current number of sides for the planet by using abs(sin(frameCount * 0.02)) to smoothly oscillate between minSidesPlanet (3) and maxSidesPlanet (12), and rounds the result so it's always a whole number.
  4. The moon's side count morphs in reverse: when the planet is a triangle, the moon becomes a dodecagon, and vice versa, creating a visual push-pull effect.
  5. The hue is incremented by 0.5 each frame and wrapped around 360 to create an endless rainbow cycle, while saturation and brightness oscillate gently using cosine and sine for visual richness.
  6. The planet polygon is drawn using a for-loop that calculates vertex positions using cosine and sine, then apply rotate() and translate() transforms so it spins at its own center.
  7. The moon's orbital position is calculated using cos(moonAngle) and sin(moonAngle), placing it at orbitalRadius distance from the planet, and it also spins independently using a different rotation speed.
  8. The moon's fill color is set to hue + 180 degrees (the complementary color on the color wheel) for visual contrast.

🎓 Concepts You'll Learn

Custom polygon drawing with beginShape/vertexTrigonometric functions (sin, cos) for smooth animation and orbital motionHSB color mode for dynamic rainbow effectsCanvas transformations (translate, rotate, push, pop)Morphing through interpolation and sine wavesComplementary colors on the color wheel

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It's where you initialize your canvas, set modes, and establish starting positions for animated objects.

function setup() {
  createCanvas(windowWidth, windowHeight);
  colorMode(HSB, 360, 100, 100);
  noStroke();
  planetX = width / 2;
  planetY = height / 2;
}
Line-by-line explanation (5 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, so the animation adapts to any screen size.
colorMode(HSB, 360, 100, 100);
Switches from RGB to HSB color mode where Hue (0-360) controls rainbow color, Saturation (0-100) controls color intensity, and Brightness (0-100) controls lightness—this makes animating colors much easier.
noStroke();
Disables outlines around shapes so only the fill color is visible, creating cleaner solid polygons.
planetX = width / 2;
Positions the planet's center horizontally in the middle of the canvas.
planetY = height / 2;
Positions the planet's center vertically in the middle of the canvas.

draw()

draw() is the heartbeat of every p5.js animation—it runs 60 times per second (or your monitor's refresh rate). Everything that moves or changes visibly is calculated and drawn here. By stacking transformations (translate, rotate) with push/pop, you can independently position and spin different shapes without interfering with each other, which is exactly what this sketch does with the planet and moon.

function draw() {
  background(0, 0, 10);
  let sinVal = abs(sin(frameCount * 0.02));
  currentPlanetSides = minSidesPlanet + sinVal * (maxSidesPlanet - minSidesPlanet);
  currentPlanetSides = round(currentPlanetSides);
  currentMoonSides = minSidesMoon + (1 - sinVal) * (maxSidesMoon - minSidesMoon);
  currentMoonSides = round(currentMoonSides);
  hue = (hue + 0.5) % 360;
  saturation = 70 + abs(sin(frameCount * 0.015)) * 30;
  brightness = 80 + abs(cos(frameCount * 0.01)) * 20;
  fill(hue, saturation, brightness);
  push();
  translate(planetX, planetY);
  rotate(frameCount * 0.01);
  beginShape();
  for (let i = 0; i < currentPlanetSides; i++) {
    let angle = map(i, 0, currentPlanetSides, 0, TWO_PI);
    let x = (planetSize / 2) * cos(angle);
    let y = (planetSize / 2) * sin(angle);
    vertex(x, y);
  }
  endShape(CLOSE);
  pop();
  let moonAngle = -frameCount * 0.03;
  let moonX = planetX + orbitalRadius * cos(moonAngle);
  let moonY = planetY + orbitalRadius * sin(moonAngle);
  fill((hue + 180) % 360, saturation * 0.8, brightness * 1.2);
  push();
  translate(moonX, moonY);
  rotate(frameCount * 0.05);
  beginShape();
  for (let i = 0; i < currentMoonSides; i++) {
    let angle = map(i, 0, currentMoonSides, 0, TWO_PI);
    let x = (moonSize / 2) * cos(angle);
    let y = (moonSize / 2) * sin(angle);
    vertex(x, y);
  }
  endShape(CLOSE);
  pop();
}
Line-by-line explanation (23 lines)

🔧 Subcomponents:

calculation Planet side morphing let sinVal = abs(sin(frameCount * 0.02)); currentPlanetSides = minSidesPlanet + sinVal * (maxSidesPlanet - minSidesPlanet); currentPlanetSides = round(currentPlanetSides);

Uses a sine wave to smoothly oscillate the planet's side count between 3 and 12, creating the morphing effect.

calculation Moon reverse morphing currentMoonSides = minSidesMoon + (1 - sinVal) * (maxSidesMoon - minSidesMoon); currentMoonSides = round(currentMoonSides);

Inverts the sine wave so the moon morphs opposite to the planet—when planet is a triangle, moon is a dodecagon.

calculation Dynamic HSB color animation hue = (hue + 0.5) % 360; saturation = 70 + abs(sin(frameCount * 0.015)) * 30; brightness = 80 + abs(cos(frameCount * 0.01)) * 20;

Continuously cycles hue through the rainbow and gently oscillates saturation and brightness for a shimmering effect.

for-loop Planet polygon vertex loop for (let i = 0; i < currentPlanetSides; i++) { let angle = map(i, 0, currentPlanetSides, 0, TWO_PI); let x = (planetSize / 2) * cos(angle); let y = (planetSize / 2) * sin(angle); vertex(x, y); }

Calculates and draws each vertex of the planet polygon in a circle using trigonometry.

for-loop Moon polygon vertex loop for (let i = 0; i < currentMoonSides; i++) { let angle = map(i, 0, currentMoonSides, 0, TWO_PI); let x = (moonSize / 2) * cos(angle); let y = (moonSize / 2) * sin(angle); vertex(x, y); }

Calculates and draws the moon's polygon vertices the same way as the planet, but scaled to moonSize.

background(0, 0, 10);
Fills the canvas with a very dark gray (almost black) in HSB mode, clearing the previous frame so animation appears smooth without trails.
let sinVal = abs(sin(frameCount * 0.02));
Creates a smooth wave between 0 and 1 that repeats; abs() ensures it never goes negative, and the 0.02 multiplier slows the oscillation.
currentPlanetSides = minSidesPlanet + sinVal * (maxSidesPlanet - minSidesPlanet);
Interpolates between 3 and 12 sides: when sinVal is 0 it's 3, when sinVal is 1 it's 12, and in between it's a decimal number like 7.5.
currentPlanetSides = round(currentPlanetSides);
Converts the decimal to a whole number (3, 4, 5... 12) so the polygon always has an integer number of sides.
currentMoonSides = minSidesMoon + (1 - sinVal) * (maxSidesMoon - minSidesMoon);
Uses (1 - sinVal) to invert the planet's pattern: when planet is a triangle (sinVal=0, 1-0=1), moon becomes a dodecagon.
hue = (hue + 0.5) % 360;
Increments hue by 0.5 every frame and wraps it back to 0 when it exceeds 360, creating an endless rainbow cycle.
saturation = 70 + abs(sin(frameCount * 0.015)) * 30;
Oscillates saturation between 70 and 100 using sine, making colors pulse between muted and vivid.
brightness = 80 + abs(cos(frameCount * 0.01)) * 20;
Oscillates brightness between 80 and 100 using cosine (out of phase with saturation), adding shimmer.
fill(hue, saturation, brightness);
Sets the fill color for the planet using the dynamically calculated HSB values.
push();
Saves the current canvas state (position, rotation, color, etc.) so transformations only affect the planet.
translate(planetX, planetY);
Moves the origin (0, 0) to the planet's center, so all subsequent drawing is relative to that point.
rotate(frameCount * 0.01);
Spins the planet around its own center; the 0.01 multiplier controls rotation speed.
beginShape();
Starts defining a custom polygon shape with vertices instead of a built-in shape like circle() or rect().
let angle = map(i, 0, currentPlanetSides, 0, TWO_PI);
Converts the loop counter i (0, 1, 2... currentPlanetSides-1) into an angle in radians that spans a full circle.
let x = (planetSize / 2) * cos(angle);
Uses cosine to calculate the x-coordinate of a vertex positioned at a distance of planetSize/2 from the center.
let y = (planetSize / 2) * sin(angle);
Uses sine to calculate the y-coordinate of the same vertex, completing its position on the circle.
vertex(x, y);
Adds this (x, y) point as a corner of the polygon being drawn.
endShape(CLOSE);
Finishes the polygon and CLOSE connects the last vertex back to the first, creating a sealed shape.
pop();
Restores the canvas state to before push(), removing all the translate and rotate transformations.
let moonAngle = -frameCount * 0.03;
Calculates the moon's orbital angle; the negative sign makes it orbit counter-clockwise, opposite to typical convention.
let moonX = planetX + orbitalRadius * cos(moonAngle);
Uses cosine to position the moon to the left/right of the planet center based on the orbital angle.
let moonY = planetY + orbitalRadius * sin(moonAngle);
Uses sine to position the moon above/below the planet center, completing its orbital position.
fill((hue + 180) % 360, saturation * 0.8, brightness * 1.2);
Sets the moon's color to a complementary hue (180 degrees opposite on the color wheel), with slightly reduced saturation and increased brightness for contrast.

windowResized()

windowResized() is a special p5.js function that fires automatically whenever the user resizes their browser window. Without it, the canvas would stay at its original size. This is your chance to recalculate positions or redraw layouts to match the new dimensions.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  planetX = width / 2;
  planetY = height / 2; 
}
Line-by-line explanation (3 lines)
resizeCanvas(windowWidth, windowHeight);
Automatically adjusts the canvas size to match the new browser window dimensions when the user resizes their window.
planetX = width / 2;
Re-centers the planet horizontally after the resize so it stays in the middle of the new canvas.
planetY = height / 2;
Re-centers the planet vertically after the resize so it stays in the middle of the new canvas.

📦 Key Variables

planetX number

Stores the x-coordinate (horizontal position) of the planet's center on the canvas.

let planetX = width / 2;
planetY number

Stores the y-coordinate (vertical position) of the planet's center on the canvas.

let planetY = height / 2;
planetSize number

Controls the diameter of the main planet polygon—larger values make it bigger.

let planetSize = 200;
moonSize number

Controls the diameter of the orbiting moon polygon—larger values make it bigger.

let moonSize = 40;
orbitalRadius number

Controls how far the moon orbits from the planet's center—calculated as 75% of the planet's size.

let orbitalRadius = planetSize * 0.75;
minSidesPlanet number

The minimum number of sides the planet polygon will have during morphing (a triangle).

let minSidesPlanet = 3;
maxSidesPlanet number

The maximum number of sides the planet polygon will have during morphing (almost a circle).

let maxSidesPlanet = 12;
currentPlanetSides number

Updated every frame to hold the planet's current number of sides, oscillating between min and max based on the sine wave.

currentPlanetSides = round(minSidesPlanet + sinVal * (maxSidesPlanet - minSidesPlanet));
minSidesMoon number

The minimum number of sides the moon polygon will have during morphing.

let minSidesMoon = 3;
maxSidesMoon number

The maximum number of sides the moon polygon will have during morphing.

let maxSidesMoon = 12;
currentMoonSides number

Updated every frame to hold the moon's current number of sides, oscillating in reverse of the planet.

currentMoonSides = round(minSidesMoon + (1 - sinVal) * (maxSidesMoon - minSidesMoon));
hue number

Stores the current hue value (0-360) for the planet's color; increments each frame to cycle through the rainbow.

let hue = 0;
saturation number

Stores the current saturation value (0-100) for the planet's color; oscillates to make colors pulse between muted and vivid.

let saturation = 80;
brightness number

Stores the current brightness value (0-100) for the planet's color; oscillates to create a shimmering effect.

let brightness = 90;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

PERFORMANCE draw() - polygon vertex loops

The two for-loops that generate polygon vertices (lines 46-51 and 72-77) are identical in logic and could be refactored into a reusable helper function to reduce code duplication.

💡 Create a helper function like drawPolygon(sides, size) that takes the number of sides and size as parameters, then call it twice—once for the planet and once for the moon. This improves maintainability and makes the code easier to extend.

STYLE draw()

The moon's color calculation uses magic numbers (180 for hue offset, 0.8 for saturation, 1.2 for brightness) without explanation.

💡 Define these as named constants at the top of the sketch (e.g., HUE_COMPLEMENT = 180, MOON_SAT_FACTOR = 0.8, MOON_BRIGHTNESS_FACTOR = 1.2) so their purpose is clear and they're easy to tune.

FEATURE draw() - animation speeds

The animation speeds are hardcoded in multiple places (0.02 for morphing, 0.01 for planet rotation, 0.03 for moon orbit, 0.05 for moon self-rotation, 0.015 for saturation, 0.01 for brightness).

💡 Declare these as global variables (MORPH_SPEED, PLANET_ROTATION_SPEED, MOON_ORBIT_SPEED, etc.) so users can adjust all timing effects from one easy-to-find location without hunting through the draw() function.

BUG orbitalRadius initialization

orbitalRadius is calculated based on planetSize during initialization, but if planetSize changes, orbitalRadius does not update automatically.

💡 Either recalculate orbitalRadius every frame in draw() (orbitalRadius = planetSize * 0.75;) or make it a separate tunable that users can control independently.

🔄 Code Flow

Code flow showing setup, draw, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> planetmorph[planet-morphing] draw --> moonreverse[moon-reverse-morphing] draw --> coloranim[color-animation] draw --> planetpoly[planet-polygon] draw --> moonpoly[moon-polygon] planetmorph --> calcplanet[Calculate Planet Side Count] calcplanet --> drawplanet[Draw Planet Vertices] moonreverse --> calcmoon[Calculate Moon Side Count] calcmoon --> drawmoon[Draw Moon Vertices] coloranim --> cyclecolor[Cycle Hue] cyclecolor --> adjustcolor[Adjust Saturation and Brightness] planetpoly --> loopplanet[Planet Polygon Vertex Loop] loopplanet --> drawvertexplanet[Draw Vertex of Planet] moonpoly --> loopmoon[Moon Polygon Vertex Loop] loopmoon --> drawvertexmoon[Draw Vertex of Moon] draw --> draw click setup href "#fn-setup" click draw href "#fn-draw" click planetmorph href "#sub-planet-morphing" click moonreverse href "#sub-moon-reverse-morphing" click coloranim href "#sub-color-animation" click planetpoly href "#sub-planet-polygon" click moonpoly href "#sub-moon-polygon"

❓ Frequently Asked Questions

What visual elements does the Sketch 2025-12-24 00:08 create?

The sketch visually represents a morphing polygon that simulates a planet, accompanied by an orbiting moon, with dynamic colors transitioning through HSB values.

Are there any interactive features in this p5.js sketch?

The sketch itself does not include interactive features, but users can modify global variables to change the appearance and behavior of the planet and moon.

What creative coding techniques are showcased in this sketch?

This sketch demonstrates polygon morphing, dynamic color manipulation using HSB, and the concept of orbital motion between two shapes.

Preview

Sketch 2025-12-24 00:08 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2025-12-24 00:08 - Code flow showing setup, draw, windowresized
Code Flow Diagram