Animated Spiral Rotation - xelsed.ai

This sketch creates a mesmerizing 3D spiral galaxy with rotating arms, a glowing center core, and 20,000 stars with color gradients. The galaxy rotates continuously, with stars positioned using logarithmic spiral mathematics to create realistic galactic structure, featuring purple-to-blue color transitions from core to edge.

πŸŽ“ Concepts You'll Learn

3D WebGL renderingPolar to Cartesian coordinatesLogarithmic spiral patternsColor gradients with lerpColorArray data structuresProcedural generation3D transformations (translate, rotate)Transparency and layeringMathematical modeling

πŸ”„ Code Flow

Code flow showing setup, generatestars, draw, windowresized

πŸ’‘ Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> canvas-creation[canvas-creation] setup --> color-initialization[color-initialization] setup --> generatestars[generatestars] generatestars --> core-star-generation[core-star-generation] generatestars --> arm-star-generation[arm-star-generation] arm-star-generation --> coordinate-conversion[coordinate-conversion] coordinate-conversion --> star-object-creation[star-object-creation] star-object-creation --> generatestars setup --> camera-setup[camera-setup] setup --> windowresized[windowresized] draw[draw loop] --> rotation-animation[rotation-animation] draw --> glow-rendering[glow-rendering] draw --> star-rendering[star-rendering] rotation-animation --> draw click setup href "#fn-setup" click generatestars href "#fn-generatestars" click draw href "#fn-draw" click windowresized href "#fn-windowresized" click canvas-creation href "#sub-canvas-creation" click color-initialization href "#sub-color-initialization" click core-star-generation href "#sub-core-star-generation" click arm-star-generation href "#sub-arm-star-generation" click coordinate-conversion href "#sub-coordinate-conversion" click star-object-creation href "#sub-star-object-creation" click camera-setup href "#sub-camera-setup" click rotation-animation href "#sub-rotation-animation" click glow-rendering href "#sub-glow-rendering" click star-rendering href "#sub-star-rendering"

πŸ“ Code Breakdown

setup()

setup() runs once when the sketch starts. It initializes the canvas, defines colors, and generates all star data. Using WEBGL is crucial here because rendering 20,000 individual points would be too slow with 2D rendering.

function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
  noStroke();
  smooth();
  purpleColor = color(80, 0, 120);
  blueColor = color(0, 50, 150);
  glowCenterColor = color(255, 240, 220);
  generateStars();
}

πŸ”§ Subcomponents:

function-call WebGL Canvas Setup createCanvas(windowWidth, windowHeight, WEBGL)

Creates a full-window 3D canvas using WebGL for efficient rendering of thousands of points

calculation Color Palette Definition purpleColor = color(80, 0, 120); blueColor = color(0, 50, 150); glowCenterColor = color(255, 240, 220);

Defines three colors used throughout the sketch: purple for inner stars, blue for outer stars, and warm white for the glowing core

Line by Line:

createCanvas(windowWidth, windowHeight, WEBGL)
Creates a 3D canvas using WebGL (Web Graphics Library) that fills the entire window. WEBGL enables efficient 3D rendering needed for thousands of stars.
noStroke()
Disables outlines around shapes so stars and glow appear as solid filled objects without borders
smooth()
Enables anti-aliasing to smooth edges and reduce pixelation for a more polished visual appearance
purpleColor = color(80, 0, 120)
Creates a purple color (RGB: 80 red, 0 green, 120 blue) used for stars near the galaxy core
blueColor = color(0, 50, 150)
Creates a blue color (RGB: 0 red, 50 green, 150 blue) used for stars at the galaxy's outer edges
glowCenterColor = color(255, 240, 220)
Creates a warm white/yellow color (RGB: 255 red, 240 green, 220 blue) for the bright glowing center
generateStars()
Calls the function that creates all 20,000 star objects with their positions, colors, and sizes

generateStars()

This function uses procedural generation to create a realistic galaxy structure. The key insight is using a logarithmic spiral (theta increases with radius) to naturally create spiral arms. The 30/70 split between core and arm stars creates a dense center with thinner outer regions, matching real galaxies. Polar coordinates (radius and angle) are perfect for spiral patterns, then converted to Cartesian (x, y) for drawing.

function generateStars() {
  for (let i = 0; i < numStars; i++) {
    let r, theta, z;
    let starColor;
    if (random() < 0.3) {
      r = random(coreRadius);
      theta = random(TWO_PI);
      z = random(-15, 15);
      starColor = glowCenterColor;
    } else {
      let armIndex = floor(random(armCount));
      r = random(coreRadius, galaxyRadius);
      theta = (r - coreRadius) * armDensity + random(-armOffset, armOffset) + armIndex * TWO_PI / armCount;
      z = random(-r * 0.08, r * 0.08);
      starColor = lerpColor(purpleColor, blueColor, map(r, coreRadius, galaxyRadius, 0, 1, true));
    }
    let x = r * cos(theta);
    let y = r * sin(theta);
    stars.push({
      x: x,
      y: y,
      z: z,
      color: starColor,
      size: random(1, 3)
    });
  }
}

πŸ”§ Subcomponents:

conditional Core Star Creation (30% probability) if (random() < 0.3) { r = random(coreRadius); theta = random(TWO_PI); z = random(-15, 15); starColor = glowCenterColor; }

Creates dense, bright stars in the galactic core with limited vertical spread to form a thick central bulge

conditional Spiral Arm Star Creation (70% probability) else { let armIndex = floor(random(armCount)); r = random(coreRadius, galaxyRadius); theta = (r - coreRadius) * armDensity + random(-armOffset, armOffset) + armIndex * TWO_PI / armCount; z = random(-r * 0.08, r * 0.08); starColor = lerpColor(purpleColor, blueColor, map(r, coreRadius, galaxyRadius, 0, 1, true)); }

Creates stars distributed along logarithmic spiral arms with color gradients and increasing vertical variation based on distance from center

calculation Polar to Cartesian Conversion let x = r * cos(theta); let y = r * sin(theta);

Converts polar coordinates (radius and angle) into Cartesian coordinates (x and y) needed for drawing

calculation Star Data Object stars.push({ x: x, y: y, z: z, color: starColor, size: random(1, 3) });

Creates an object storing all information about one star and adds it to the stars array

Line by Line:

for (let i = 0; i < numStars; i++)
Loops 20,000 times to create 20,000 stars. Each iteration generates one star's data.
if (random() < 0.3)
30% of the time, creates a core star. random() returns a value between 0 and 1, so this condition is true 30% of the time.
r = random(coreRadius)
For core stars, sets radius to a random value between 0 and coreRadius (80), placing it anywhere within the dense center
theta = random(TWO_PI)
For core stars, sets angle to a random value between 0 and 2Ο€ (360 degrees), distributing them evenly around the center
z = random(-15, 15)
For core stars, sets height between -15 and 15, creating a thick bulge at the center (limited vertical spread)
let armIndex = floor(random(armCount))
For arm stars, randomly chooses which of the 2 spiral arms this star belongs to (0 or 1)
r = random(coreRadius, galaxyRadius)
For arm stars, sets radius between 80 and 450, placing it somewhere in the spiral arms (not in the core)
theta = (r - coreRadius) * armDensity + random(-armOffset, armOffset) + armIndex * TWO_PI / armCount
Creates the spiral pattern: (r - coreRadius) * armDensity makes angle increase with radius (creating the spiral), random(-armOffset, armOffset) adds thickness to arms, and armIndex * TWO_PI / armCount offsets each arm by 180 degrees
z = random(-r * 0.08, r * 0.08)
For arm stars, vertical spread increases with distance from center (8% of radius), creating a thin disc that gets thicker towards edges
starColor = lerpColor(purpleColor, blueColor, map(r, coreRadius, galaxyRadius, 0, 1, true))
Blends between purple (at core) and blue (at edge) based on the star's distance from center. map() converts radius to a 0-1 value, and lerpColor interpolates between the two colors.
let x = r * cos(theta); let y = r * sin(theta);
Converts polar coordinates (r and theta) to Cartesian coordinates (x and y) using trigonometry: x = radius Γ— cos(angle), y = radius Γ— sin(angle)
stars.push({ x: x, y: y, z: z, color: starColor, size: random(1, 3) })
Creates an object with all the star's data and adds it to the stars array. This object stores position (x, y, z), color, and size for later drawing.

draw()

draw() runs 60 times per second, creating smooth animation. The key techniques here are: (1) 3D transformations (translate, rotate) to position and orient the galaxy, (2) layered transparency to create the glow effect, and (3) efficient POINTS rendering for thousands of stars. The rotation happens because currentRotation increases every frameβ€”this is how animation works in p5.js.

function draw() {
  background(0);
  translate(0, 0, -300);
  rotateX(PI / 2);
  rotateZ(currentRotation);
  currentRotation += 0.002;
  let glowRadius = coreRadius * 1.8;
  let glowLayers = 15;
  for (let i = glowLayers; i >= 1; i--) {
    let r = map(i, 0, glowLayers, 0, glowRadius);
    let alpha = map(i, 0, glowLayers, 0, 80);
    fill(glowCenterColor, alpha);
    sphere(r);
  }
  beginShape(POINTS);
  for (let i = 0; i < stars.length; i++) {
    let star = stars[i];
    fill(star.color);
    vertex(star.x, star.y, star.z);
  }
  endShape();
}

πŸ”§ Subcomponents:

calculation 3D Camera and Galaxy Orientation translate(0, 0, -300); rotateX(PI / 2);

Positions the camera 300 units back and rotates the galaxy 90 degrees so it appears flat and horizontal on screen

calculation Galaxy Rotation rotateZ(currentRotation); currentRotation += 0.002;

Rotates the entire galaxy around its center axis and increments the rotation angle each frame to create continuous animation

for-loop Glowing Center Layers for (let i = glowLayers; i >= 1; i--) { let r = map(i, 0, glowLayers, 0, glowRadius); let alpha = map(i, 0, glowLayers, 0, 80); fill(glowCenterColor, alpha); sphere(r); }

Draws 15 transparent spheres of increasing size to create a soft glowing halo effect at the galaxy's center

for-loop Star Point Rendering beginShape(POINTS); for (let i = 0; i < stars.length; i++) { let star = stars[i]; fill(star.color); vertex(star.x, star.y, star.z); } endShape();

Efficiently draws all 20,000 stars as individual points using the POINTS shape mode, which is optimized for WebGL

Line by Line:

background(0)
Clears the canvas to black (RGB: 0, 0, 0) at the start of each frame, erasing the previous frame
translate(0, 0, -300)
Moves the camera 300 units backward along the Z-axis so we can see the entire galaxy from a distance
rotateX(PI / 2)
Rotates the galaxy 90 degrees around the X-axis, making it lie flat on the XY plane so we view it from above
rotateZ(currentRotation)
Rotates the entire galaxy around its vertical Z-axis by the current rotation angle, creating the spinning effect
currentRotation += 0.002
Increases the rotation angle by 0.002 radians each frame. At 60 fps, this creates a smooth, continuous rotation
let glowRadius = coreRadius * 1.8
Sets the maximum radius of the glow effect to 1.8 times the core radius (144 units), extending beyond the actual core
let glowLayers = 15
Defines how many transparent spheres will be drawn to create the glow. More layers = smoother gradient but slower rendering
for (let i = glowLayers; i >= 1; i--)
Loops from 15 down to 1, drawing spheres from largest to smallest. Backwards order ensures larger transparent layers don't hide smaller opaque ones
let r = map(i, 0, glowLayers, 0, glowRadius)
Converts layer number (1-15) to a radius (0-144). Layer 15 gets radius 144, layer 1 gets radius ~9.6
let alpha = map(i, 0, glowLayers, 0, 80)
Converts layer number to transparency (0-80). Outer layers (small i) are more transparent, inner layers (large i) are more opaque
fill(glowCenterColor, alpha)
Sets the fill color to warm white with the calculated alpha (transparency). Lower alpha = more transparent.
sphere(r)
Draws a 3D sphere at the origin with the calculated radius. In WebGL, this is efficient even with multiple spheres
beginShape(POINTS)
Starts drawing a shape using POINTS mode, which is optimized in WebGL for drawing thousands of individual points efficiently
for (let i = 0; i < stars.length; i++)
Loops through all 20,000 stars in the stars array
let star = stars[i]
Gets the current star object, which contains x, y, z position, color, and size
fill(star.color)
Sets the fill color to this specific star's color (purple for core stars, blue for outer stars)
vertex(star.x, star.y, star.z)
Adds a point at the star's 3D coordinates to the POINTS shape. The point will be drawn with the current fill color
endShape()
Finishes drawing the POINTS shape, rendering all 20,000 points to the screen

windowResized()

windowResized() is a special p5.js function that gets called automatically whenever the window size changes. Without this, the canvas would stay at its original size and look broken when the window is resized. This ensures the galaxy always fills the entire screen.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

Line by Line:

resizeCanvas(windowWidth, windowHeight)
Automatically resizes the canvas to match the current window dimensions whenever the user resizes their browser window

πŸ“¦ Key Variables

stars array

Stores all 20,000 star objects. Each object contains x, y, z coordinates, color, and size. This array is created once in generateStars() and used every frame in draw().

let stars = []; // Later filled with objects like {x: 100, y: 50, z: 10, color: color(80,0,120), size: 2}
numStars number

Defines how many stars to generate. Set to 20,000 for a dense galaxy. Higher values create more detail but slower performance.

let numStars = 20000;
galaxyRadius number

The maximum distance from the center where stars can appear. Set to 450, defining the outer edge of the galaxy.

let galaxyRadius = 450;
coreRadius number

The radius of the dense central core. Set to 80. Stars in the core are always bright and densely packed.

let coreRadius = 80;
armDensity number

Controls how tightly the spiral arms wrap. Set to 0.007. Smaller values create looser, wider spirals; larger values create tighter spirals.

let armDensity = 0.007;
armCount number

The number of spiral arms in the galaxy. Set to 2, creating two major arms (like many real galaxies).

let armCount = 2;
armOffset number

Adds randomness to star positions within the arms. Set to 0.6. Higher values make arms thicker and more diffuse.

let armOffset = 0.6;
purpleColor p5.Color

Stores the purple color (RGB: 80, 0, 120) used for stars near the galaxy core.

let purpleColor = color(80, 0, 120);
blueColor p5.Color

Stores the blue color (RGB: 0, 50, 150) used for stars at the galaxy's outer edges.

let blueColor = color(0, 50, 150);
glowCenterColor p5.Color

Stores the warm white/yellow color (RGB: 255, 240, 220) used for the glowing center and core stars.

let glowCenterColor = color(255, 240, 220);
currentRotation number

Tracks the current rotation angle of the galaxy in radians. Increases by 0.002 each frame to create continuous rotation.

let currentRotation = 0;

πŸ§ͺ Try This!

Experiment with the code by making these changes:

  1. Change armDensity from 0.007 to 0.02 (line 11) to create much tighter, more compact spiral arms. Then try 0.003 for looser arms. Notice how this single parameter dramatically changes the galaxy shape.
  2. Modify armCount from 2 to 3 or 4 (line 12) to create a galaxy with 3 or 4 spiral arms instead of 2. This creates a different galactic structure like barred spiral galaxies.
  3. Change currentRotation += 0.002 (line 54) to currentRotation += 0.005 to make the galaxy spin twice as fast, or try 0.0005 for a very slow rotation. Experiment with different speeds.
  4. Modify the glow effect by changing glowLayers from 15 to 5 (line 49) for a sharper glow, or 30 for a softer, more diffuse glow. Also try changing glowRadius from coreRadius * 1.8 to coreRadius * 3 to extend the glow further.
  5. Change the color gradient by modifying purpleColor (line 27) to color(255, 0, 0) for red, or blueColor (line 28) to color(0, 255, 0) for green. Create your own color scheme for the galaxy.
  6. Increase numStars from 20000 to 50000 (line 6) to create a denser galaxy with more detail, but be aware this will slow down your computer. Try 10000 for a sparser galaxy.
  7. Modify the core star probability from 0.3 (30%) on line 32 to 0.5 (50%) to create a larger, brighter core, or 0.1 (10%) for a smaller core. This changes the galaxy's structure.
  8. Change the z-variation for core stars from random(-15, 15) (line 35) to random(-50, 50) to create a thicker, puffier central bulge, or random(-5, 5) for a flatter core.
Open in Editor & Experiment β†’

πŸ”§ Potential Improvements

Here are some ways this code could be enhanced:

PERFORMANCE draw() function, star rendering loop (lines 57-63)

Setting fill() inside the loop for each star is inefficient. In WebGL, changing fill color for each vertex can cause performance overhead.

πŸ’‘ Consider grouping stars by color and drawing them in batches, or use vertex coloring with a custom shader for better performance with 20,000 stars.

BUG generateStars() function, spiral theta calculation (line 44)

The random(-armOffset, armOffset) is recalculated for every star, which means the same star position could be generated multiple times with different z values, creating inconsistent arm thickness.

πŸ’‘ Store the armOffset value as part of the star object to ensure consistent arm structure, or use a seeded random function for reproducible results.

STYLE setup() function (lines 19-26)

Color definitions are scattered throughout setup(). The code would be more organized if all parameters were grouped at the top of the file.

πŸ’‘ Move all galaxy parameters (colors, radii, densities) to the top of the file near the other variable declarations for better code organization and easier tweaking.

FEATURE draw() function (line 54)

The rotation speed is hardcoded to 0.002. Users cannot interactively control the rotation speed.

πŸ’‘ Add mouse interaction like rotationSpeed = map(mouseX, 0, width, 0, 0.01) to let users control rotation speed by moving the mouse left and right.

FEATURE generateStars() function (line 32)

The 30% core star probability is hardcoded and cannot be easily adjusted without editing code.

πŸ’‘ Create a variable let coreStarProbability = 0.3 at the top of the file so users can easily experiment with different galaxy structures by changing one value.

PERFORMANCE draw() function, glow rendering (lines 49-55)

Drawing 15 spheres every frame is computationally expensive. Each sphere is a complex 3D shape.

πŸ’‘ Consider using a pre-rendered texture or simpler geometry (like a flat disc with radial gradient) to represent the glow, which would be much faster.

Preview

Animated Spiral Rotation - xelsed.ai - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Animated Spiral Rotation - xelsed.ai - Code flow showing setup, generatestars, draw, windowresized
Code Flow Diagram