my profile

This sketch creates a beautifully lit 3D Earth rotating slowly in a starfield, with a satellite orbiting around it and leaving a glowing trail. The scene responds to mouse dragging for rotation control, and interactive sliders let you adjust gravity and satellite parameters in real time to watch the orbit transform.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the starfield denser
  2. Change Earth's color to green
  3. Make the satellite trail glow red
  4. Slow down Earth's rotation
  5. Make the satellite orbit much closer
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch demonstrates advanced p5.js techniques by rendering a photorealistic rotating Earth in 3D space using WEBGL, surrounding it with a procedurally generated starfield, and animating a small satellite that orbits while leaving a smooth glowing trail behind it. The visual appeal comes from careful lighting with directional and ambient light, semi-transparent materials that glow, and smooth animation at 60 frames per second—all building a mesmerizing space scene that feels alive.

The code is organized around three main pillars: a setup() function that initializes the 3D scene and camera, a draw() loop that updates the satellite's orbital position and renders the Earth, stars, and trail, and several helper functions (drawStars(), drawEarth(), updateSatellite(), drawTrail()) that handle specific visual elements. You will learn how p5.js handles 3D transformations with translate() and rotateY(), how to manage interactive sliders for real-time parameter tuning, and how to simulate orbital physics using velocity and acceleration.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-screen WEBGL canvas, initializes the camera to look at the scene from a distance, creates the starfield with random points, and sets up slider controls for gravity and satellite speed
  2. On each frame, draw() updates the background and lighting, draws the rotating Earth with a slight tilt, updates the satellite's position based on orbital physics (gravity pulling it toward Earth), and draws the satellite with its glowing trail
  3. The satellite's velocity and acceleration are recalculated every frame using a simplified physics model where gravity = (mass / distance²) pulls toward Earth's center
  4. The trail is drawn by storing recent satellite positions in an array and connecting them with semi-transparent lines that fade over time
  5. Mouse interactions let you drag to rotate the entire scene by adjusting the camera angle, and sliders let you adjust gravity strength and satellite speed to see immediate changes in the orbit shape

🎓 Concepts You'll Learn

3D rendering with WEBGLOrbital mechanics and gravity simulationCamera control and mouse interactionLighting and material propertiesParticle trails and history buffersInteractive real-time parameter adjustment

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It prepares the 3D scene by creating the canvas, initializing all objects and variables, and setting up interactive controls. Understanding setup() is crucial because every 3D sketch must establish its camera, lighting, and initial geometry here.

function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
  perspective(fov, width / height, 0.1, 10000);
  drawingContext.enable(drawingContext.DEPTH_TEST);
  
  // Initialize starfield
  for (let i = 0; i < numStars; i++) {
    stars.push({
      x: random(-2000, 2000),
      y: random(-2000, 2000),
      z: random(-2000, 2000),
      brightness: random(100, 255)
    });
  }
  
  // Initialize satellite position
  satellitePos = createVector(400, 0, 0);
  satelliteVel = createVector(0, 0, satelliteSpeed);
  
  // Initialize UI controls
  gravitySlider = createSlider(0.1, 2, 0.5, 0.05);
  gravitySlider.position(20, 20);
  
  speedSlider = createSlider(1, 8, 3, 0.5);
  speedSlider.position(20, 50);
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

initialization Canvas and perspective setup createCanvas(windowWidth, windowHeight, WEBGL);

Creates a full-screen WEBGL canvas for 3D rendering

for-loop Starfield initialization loop for (let i = 0; i < numStars; i++) { stars.push({...}); }

Populates the stars array with random star objects distributed throughout space

initialization Satellite initialization satellitePos = createVector(400, 0, 0);

Places the satellite at its starting orbital position

initialization Interactive slider controls gravitySlider = createSlider(0.1, 2, 0.5, 0.05);

Creates interactive sliders to adjust gravity and speed in real time

createCanvas(windowWidth, windowHeight, WEBGL);
Creates a full-screen canvas with WEBGL rendering mode, which enables 3D graphics like lighting, camera control, and depth
perspective(fov, width / height, 0.1, 10000);
Sets up the camera's perspective view—objects closer to the camera appear larger, and those at depth 10000 are barely visible
drawingContext.enable(drawingContext.DEPTH_TEST);
Enables depth testing so that 3D objects correctly hide behind each other based on distance from camera
for (let i = 0; i < numStars; i++) {
Loops through and creates numStars (e.g., 1000) random star objects
stars.push({ x: random(-2000, 2000), y: random(-2000, 2000), z: random(-2000, 2000), brightness: random(100, 255) });
Adds each star as an object with random x, y, z positions in a large space and a random brightness value
satellitePos = createVector(400, 0, 0);
Places the satellite 400 units away from Earth's center on the x-axis as its starting position
satelliteVel = createVector(0, 0, satelliteSpeed);
Gives the satellite an initial velocity in the z direction so it starts moving in an orbit
gravitySlider = createSlider(0.1, 2, 0.5, 0.05);
Creates an interactive slider ranging from 0.1 to 2 with starting value 0.5 and step size 0.05

draw()

draw() runs 60 times per second and is where all animation and rendering happens. Every frame we clear the canvas, update physics and mouse interaction, and redraw all objects. This is the heartbeat of your sketch—understanding the draw loop is understanding p5.js animation itself.

function draw() {
  background(5, 5, 15);
  
  // Lighting
  directionalLight(255, 255, 255, 0.3, 0.5, -1);
  ambientLight(100, 100, 120);
  
  // Rotate view with mouse
  rotateX(mouseY * 0.01);
  rotateY(mouseX * 0.01);
  
  // Draw starfield
  drawStars();
  
  // Draw Earth
  push();
  rotateY(frameCount * earthRotationSpeed);
  drawEarth();
  pop();
  
  // Update and draw satellite
  updateSatellite();
  drawTrail();
  
  // Draw satellite
  push();
  translate(satellitePos.x, satellitePos.y, satellitePos.z);
  fill(100, 200, 255);
  sphere(15);
  pop();
  
  // Update sliders
  gravityStrength = gravitySlider.value();
  satelliteSpeed = speedSlider.value();
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

initialization Scene lighting directionalLight(255, 255, 255, 0.3, 0.5, -1);

Creates directional lighting (like sunlight) coming from an angle to illuminate the Earth and satellite

calculation Mouse-controlled view rotation rotateX(mouseY * 0.01); rotateY(mouseX * 0.01);

Maps mouse position to camera rotation so dragging the mouse rotates the view

calculation Earth's spin animation rotateY(frameCount * earthRotationSpeed);

Rotates Earth on its axis each frame, creating the appearance of rotation over time

calculation Draw satellite sphere translate(satellitePos.x, satellitePos.y, satellitePos.z); sphere(15);

Moves the coordinate system to the satellite's position and draws a sphere at that location

background(5, 5, 15);
Fills the entire canvas with a dark navy color (5, 5, 15) each frame, erasing the previous frame
directionalLight(255, 255, 255, 0.3, 0.5, -1);
Creates white directional light (like sunlight) pointing from direction (0.3, 0.5, -1)—this illuminates one side of the Earth
ambientLight(100, 100, 120);
Adds ambient light (gentle blue-tinted light) so shadowed areas are still faintly visible instead of completely black
rotateX(mouseY * 0.01);
Tilts the view up and down based on vertical mouse position—dragging up tilts the view, dragging down tilts it back
rotateY(mouseX * 0.01);
Spins the view left and right based on horizontal mouse position—dragging right spins the scene to the right
drawStars();
Calls the helper function that draws all star points throughout the scene
rotateY(frameCount * earthRotationSpeed);
Spins Earth incrementally each frame—frameCount increases by 1 every frame, so Earth rotates continuously
drawEarth();
Calls the helper function that draws the Earth sphere with its texture or color
updateSatellite();
Calls the helper function that updates the satellite's velocity and position based on orbital physics
drawTrail();
Calls the helper function that draws the glowing trail of previous satellite positions
translate(satellitePos.x, satellitePos.y, satellitePos.z);
Moves the origin (0,0,0) to the satellite's current position so the sphere is drawn there
sphere(15);
Draws a sphere with radius 15 at the satellite's position
gravityStrength = gravitySlider.value();
Reads the current slider value and updates the gravityStrength variable so orbital physics change in real time

drawStars()

drawStars() is a helper function that isolates star rendering into one clean piece of code. Each star is a small sphere at a 3D position. Notice how push()/pop() preserve the coordinate system—this keeps transformations from affecting other objects.

function drawStars() {
  push();
  fill(255);
  noStroke();
  for (let star of stars) {
    push();
    translate(star.x, star.y, star.z);
    sphere(2);
    pop();
  }
  pop();
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

for-loop Star drawing loop for (let star of stars) { ... }

Iterates through every star object and draws each one at its 3D position

for (let star of stars) {
Loops through each star object in the stars array one at a time
translate(star.x, star.y, star.z);
Moves the drawing origin to this star's 3D position
sphere(2);
Draws a small white sphere (radius 2) at the translated position to represent the star

drawEarth()

drawEarth() encapsulates all Earth rendering. It demonstrates how specularMaterial() and shininess() make 3D objects look more realistic by simulating light bouncing off different surface types.

function drawEarth() {
  push();
  fill(50, 120, 255);
  specularMaterial(200, 200, 220);
  shininess(80);
  sphere(100);
  pop();
}
Line-by-line explanation (4 lines)
fill(50, 120, 255);
Sets the Earth's color to a light blue (RGB: 50, 120, 255)
specularMaterial(200, 200, 220);
Makes the Earth's surface reflective and shiny, creating highlights where light hits
shininess(80);
Controls how sharp the light reflections are—80 is moderately shiny; lower values are duller, higher values are mirror-like
sphere(100);
Draws the Earth as a sphere with radius 100 in its final color and material properties

updateSatellite()

updateSatellite() is where physics happens. It uses vectors to calculate gravity as an inverse square law, then applies that force as acceleration. This is real orbital mechanics simplified: each frame, Earth pulls the satellite closer, which accelerates its velocity, which changes its position. Study this function to understand how vectors and physics work together.

function updateSatellite() {
  // Calculate gravity
  let earthPos = createVector(0, 0, 0);
  let distance = p5.Vector.dist(satellitePos, earthPos);
  let gravityForce = (gravityStrength * 100000) / (distance * distance);
  
  // Direction from satellite to Earth
  let directionToEarth = p5.Vector.sub(earthPos, satellitePos);
  directionToEarth.normalize();
  
  // Apply gravity as acceleration
  let gravityAccel = directionToEarth.copy();
  gravityAccel.mult(gravityForce);
  satelliteVel.add(gravityAccel);
  
  // Update position
  satellitePos.add(satelliteVel);
  
  // Store trail history
  if (trailHistory.length > trailLength) {
    trailHistory.shift();
  }
  trailHistory.push(satellitePos.copy());
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

calculation Distance calculation let distance = p5.Vector.dist(satellitePos, earthPos);

Computes the distance between satellite and Earth using the distance formula

calculation Gravity force calculation let gravityForce = (gravityStrength * 100000) / (distance * distance);

Calculates gravitational pull using inverse square law—force weakens as distance increases

calculation Direction to Earth let directionToEarth = p5.Vector.sub(earthPos, satellitePos); directionToEarth.normalize();

Finds which direction from satellite points toward Earth and normalizes it to unit length

conditional Trail history management if (trailHistory.length > trailLength) { trailHistory.shift(); }

Keeps the trail history array at a fixed size by removing oldest positions when it gets too long

let earthPos = createVector(0, 0, 0);
Earth is fixed at the origin (0, 0, 0)—this is the center point that gravity pulls toward
let distance = p5.Vector.dist(satellitePos, earthPos);
Calculates the straight-line distance between the satellite and Earth using the distance formula
let gravityForce = (gravityStrength * 100000) / (distance * distance);
Uses the inverse square law—gravity gets weaker the farther away the satellite is, squared
let directionToEarth = p5.Vector.sub(earthPos, satellitePos);
Subtracts satellite position from Earth position to get a vector pointing from satellite toward Earth
directionToEarth.normalize();
Scales this vector down to length 1 while keeping its direction—this makes it a pure direction
let gravityAccel = directionToEarth.copy();
Copies the direction vector so we can scale it without changing the original
gravityAccel.mult(gravityForce);
Scales the direction by the gravity force to create an acceleration vector pulling the satellite toward Earth
satelliteVel.add(gravityAccel);
Adds the gravitational acceleration to the satellite's velocity, making it accelerate toward Earth
satellitePos.add(satelliteVel);
Moves the satellite by its velocity each frame, causing it to travel through space
if (trailHistory.length > trailLength) {
Checks if the trail history array has grown beyond the maximum length
trailHistory.shift();
Removes the oldest position from the trail so we never exceed trailLength positions
trailHistory.push(satellitePos.copy());
Adds the satellite's current position to the trail history, recording where it has been

drawTrail()

drawTrail() uses beginShape() and vertex() to draw a continuous line through all stored satellite positions. The semi-transparent blue color makes it glow against the dark background. This technique—storing history and drawing it—is fundamental to creating motion trails, sensor data visualization, and many other effects.

function drawTrail() {
  push();
  noFill();
  strokeWeight(2);
  stroke(100, 200, 255, 150);
  
  beginShape();
  for (let i = 0; i < trailHistory.length; i++) {
    let pos = trailHistory[i];
    vertex(pos.x, pos.y, pos.z);
  }
  endShape();
  pop();
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

for-loop Trail vertex drawing loop for (let i = 0; i < trailHistory.length; i++) {

Iterates through each stored position in the trail history

noFill();
Tells p5.js not to fill shapes—we only want the outline (stroke) of the trail
strokeWeight(2);
Sets the line thickness to 2 pixels
stroke(100, 200, 255, 150);
Sets the line color to light blue with 150 opacity (out of 255), making it semi-transparent
beginShape();
Tells p5.js to start recording vertices for a continuous line
for (let i = 0; i < trailHistory.length; i++) {
Loops through every stored position in the trail history array
vertex(pos.x, pos.y, pos.z);
Adds this position as a point on the line—the line will connect all these vertices
endShape();
Tells p5.js to finish drawing the shape, connecting all vertices into one continuous line

📦 Key Variables

stars array

Stores all star objects with x, y, z positions and brightness values for the starfield

let stars = [];
satellitePos p5.Vector

Stores the satellite's current 3D position in space

let satellitePos = createVector(400, 0, 0);
satelliteVel p5.Vector

Stores the satellite's current velocity in 3D space—changes each frame due to gravity

let satelliteVel = createVector(0, 0, 3);
trailHistory array

Stores recent satellite positions to draw the glowing trail behind it

let trailHistory = [];
gravityStrength number

Controls how strongly Earth pulls the satellite—adjusted by slider in real time

let gravityStrength = 0.5;
satelliteSpeed number

Controls the satellite's initial orbital speed—affects orbit shape when adjusted

let satelliteSpeed = 3;
earthRotationSpeed number

Controls how fast Earth spins on its axis each frame

let earthRotationSpeed = 0.002;
trailLength number

Maximum number of positions stored in the trail before old ones are removed

let trailLength = 100;
gravitySlider p5.Slider

Interactive slider element that lets users adjust gravity in real time

let gravitySlider;
speedSlider p5.Slider

Interactive slider element that lets users adjust satellite speed in real time

let speedSlider;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

FEATURE drawEarth()

Earth is a solid blue sphere with no surface detail or texture—it lacks visual realism

💡 Consider loading an image texture with loadImage() and using texture() in drawEarth() to map a real Earth photograph onto the sphere, or use noise() to generate procedural continents and clouds

PERFORMANCE drawStars()

Every star calls push() and pop() individually—1000 stars means 2000 function calls each frame

💡 Use a single push()/pop() outside the loop and translate to each star position, or better yet, use a single point cloud rendered with a vertex shader for massive performance improvement

BUG updateSatellite()

If distance becomes very small (satellite crashes into Earth), gravityForce becomes extremely large, potentially causing instability or erratic behavior

💡 Add a minimum distance constraint: `distance = max(distance, 150)` to prevent the satellite from getting too close and destabilizing

STYLE draw()

Hardcoded numbers like 0.01 for mouse rotation sensitivity and 0.3, 0.5, -1 for light direction are not obvious in meaning

💡 Extract these as named constants at the top of the sketch (e.g., `const MOUSE_SENSITIVITY = 0.01`) to make the code more readable and tweakable

FEATURE Setup and draw()

No escape mechanism to reset the view or restart the orbit simulation without reloading the page

💡 Add keyboard controls like pressing 'r' to reset the satellite to its starting position and velocity, or 'c' to clear the trail history for a clean slate

🔄 Code Flow

Code flow showing setup, draw, drawstars, drawearth, updatesatellite, drawtrail

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] click setup href "#fn-setup" click draw href "#fn-draw" setup --> canvas-setup[canvas-setup] setup --> starfield-loop[starfield-loop] setup --> satellite-init[satellite-init] setup --> slider-creation[slider-creation] setup --> lighting-setup[lighting-setup] click canvas-setup href "#sub-canvas-setup" click starfield-loop href "#sub-starfield-loop" click satellite-init href "#sub-satellite-init" click slider-creation href "#sub-slider-creation" click lighting-setup href "#sub-lighting-setup" draw --> mouse-rotation[mouse-rotation] draw --> earth-rotation[earth-rotation] draw --> satellite-draw[satellite-draw] draw --> drawstars[drawstars] draw --> drawearth[drawearth] draw --> updatesatellite[updatesatellite] draw --> drawtrail[drawtrail] click mouse-rotation href "#sub-mouse-rotation" click earth-rotation href "#sub-earth-rotation" click satellite-draw href "#sub-satellite-draw" click drawstars href "#fn-drawstars" click drawearth href "#fn-drawearth" click updatesatellite href "#fn-updatesatellite" click drawtrail href "#fn-drawtrail" updatesatellite --> distance-calc[distance-calc] updatesatellite --> gravity-calc[gravity-calc] updatesatellite --> direction-calc[direction-calc] click distance-calc href "#sub-distance-calc" click gravity-calc href "#sub-gravity-calc" click direction-calc href "#sub-direction-calc" drawtrail --> trail-buffer[trail-buffer] drawtrail --> trail-loop[trail-loop] click trail-buffer href "#sub-trail-buffer" click trail-loop href "#sub-trail-loop" drawstars --> star-loop[star-loop] click star-loop href "#sub-star-loop" drawearth --> earth-rotation[earth-rotation]

❓ Frequently Asked Questions

What visual experience does the 'my profile' sketch provide?

The sketch creates a visually stunning 3D Earth that slowly rotates in a glowing starfield, complemented by a tiny satellite orbiting and leaving a smooth glowing trail.

How can users interact with the 'my profile' sketch?

Users can drag to rotate the scene and adjust gravity and satellite settings, allowing them to see real-time changes in the satellite's orbit.

What creative coding concepts are showcased in the 'my profile' sketch?

This sketch demonstrates 3D rendering techniques, real-time interactivity, and smooth animation to create an engaging visual experience.

Preview

my profile - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of my profile - Code flow showing setup, draw, drawstars, drawearth, updatesatellite, drawtrail
Code Flow Diagram