tu-tu-du-du max verstappen

This sketch creates a dynamic 3D visualization of swirling, racing-inspired lines that spin through space against a deep blue background, with the name "MAX VERSTAPPEN" displayed prominently at the center. The lines use Perlin noise to create organic, flowing motion that continuously rotates, evoking speed and energy inspired by Formula 1 racing.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the lines red instead of yellow — The stroke color instantly changes, shifting the visual from yellow energy to a more aggressive red
  2. Slow down the rotation by half — The scene spins at a much more leisurely pace, letting you see the structure more clearly
  3. Double the line thickness — The strokes become bolder and more visible, making the entire visualization look thicker and more prominent
  4. Add dramatic depth with larger Z variation — Points extend much farther forward and backward in 3D space, creating a more exaggerated sense of depth
  5. Make the text bigger and more readable — The name "MAX VERSTAPPEN" grows larger and dominates the center of the screen more prominently
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch animates a stunning 3D racing scene built with p5.js's WEBGL mode. It draws fifty swirling lines that twist through three-dimensional space, each shaped by Perlin noise to create organic, flowing curves. The entire scene rotates continuously around two axes, and "MAX VERSTAPPEN" hovers at the center in bold white text. The deep blue background echoes Red Bull Racing's iconic colors, making this a tribute that merges sports with generative art.

The code is organized into a preload() function that loads a custom font, a setup() that prepares the 3D canvas and color palette, and a draw() function that handles all the animation. You will learn how WEBGL mode unlocks 3D transformations, how Perlin noise creates organic shapes, how nested loops build complex geometric structures, and how push() and pop() let you rotate text independently from the background geometry.

⚙️ How It Works

  1. When the sketch loads, preload() downloads the Oswald font from a CDN, then setup() creates a full-window WEBGL canvas and defines the Red Bull racing color palette
  2. Every frame, draw() clears the canvas with deep blue and applies continuous rotations around the X and Y axes to make the scene spin
  3. A nested loop draws 50 separate lines: the outer loop cycles through each line, the inner loop calculates 100 points per line using Perlin noise to vary their radius and depth
  4. Perlin noise is sampled at different coordinates for each line and point, creating smooth, organic variation that evolves over time as frameCount increases
  5. The noise values are mapped to a radius multiplier and a Z-depth, so each point sits at a unique distance from the center in 3D space
  6. After drawing the lines, push() and pop() isolate a counter-rotation that keeps "MAX VERSTAPPEN" somewhat readable despite the spinning background, then the text is translated forward and drawn

🎓 Concepts You'll Learn

WEBGL 3D modePerlin noiseCoordinate transformations (translate, rotate)Nested loopsPush and pop state isolationVertex-based geometryFrame-based animation

📝 Code Breakdown

preload()

preload() runs once before setup(), making it the perfect place to load external resources like fonts, images, and sounds that your sketch needs from the start.

function preload() {
  // Load a racing-inspired font from Fontsource CDN for WEBGL text
  racingFont = loadFont('https://unpkg.com/@fontsource/oswald@latest/files/oswald-latin-400-normal.woff');
}
Line-by-line explanation (1 lines)
racingFont = loadFont('https://unpkg.com/@fontsource/oswald@latest/files/oswald-latin-400-normal.woff');
Loads the Oswald font from a CDN URL before setup() runs—preload() ensures the font is ready before the sketch tries to use it

setup()

setup() runs once at the start. Use it to create your canvas, load assets, and define constants and initial values that the rest of your sketch will use.

function setup() {
  // Use WEBGL mode for a more dynamic and 3D feel
  createCanvas(windowWidth, windowHeight, WEBGL);
  
  // Set the font for text in WEBGL mode
  textFont(racingFont);
  textSize(48); // Adjust text size as needed
  textAlign(CENTER, CENTER); // Center text
  
  // Define Red Bull Racing inspired color palette
  colors = {
    darkBlue: color(0, 24, 69), // Primary Red Bull dark blue
    red: color(220, 0, 0),     // Red accents
    yellow: color(255, 200, 0), // Yellow accents
    white: color(255)          // White accents
  };
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Canvas and WEBGL initialization createCanvas(windowWidth, windowHeight, WEBGL);

Creates a full-window 3D canvas in WEBGL mode, which enables 3D transformations like rotateX and rotateY

calculation Text configuration textFont(racingFont); textSize(48); textAlign(CENTER, CENTER);

Applies the loaded font, sets text size, and centers text so it displays at the origin point

calculation Color palette definition colors = { darkBlue: color(0, 24, 69), red: color(220, 0, 0), yellow: color(255, 200, 0), white: color(255) };

Stores RGB color values in an object for reuse throughout the sketch, keeping the code DRY and the colors consistent

createCanvas(windowWidth, windowHeight, WEBGL);
Creates a canvas the size of the window in WEBGL mode—this unlocks 3D transformations and rotations
textFont(racingFont);
Tells p5.js to use the loaded Oswald font for all text drawn after this line
textSize(48);
Sets the font size to 48 pixels—larger numbers make the text bigger
textAlign(CENTER, CENTER);
Aligns text to its center point, so when you draw at (0, 0), the text appears centered rather than left-aligned
colors = {
Begins an object that stores all the colors used in the sketch—using an object keeps colors organized and easy to reuse

draw()

draw() runs 60 times per second, continuously updating the animation. This is where all the magic happens: scene rotation, noise-based line generation, and text rendering all happen here every frame.

🔬 This inner loop creates each point on a line by calculating angles and noise. What happens if you change the noise offsets—try adding 2000 to the second xNoise instead of 1000? Or remove the speed term entirely to see the lines stop animating?

    for (let j = 0; j < detail; j++) {
      let angle = map(j, 0, detail - 1, 0, TWO_PI) + angleOffset;
      
      // Calculate noise value for current point
      let xNoise = noise(cos(angle) * noiseScale + speed, sin(angle) * noiseScale + speed);
      let yNoise = noise(cos(angle) * noiseScale + 1000 + speed, sin(angle) * noiseScale + 1000 + speed);

🔬 These two lines make the entire scene spin. What happens if you swap the multipliers—try 0.007 for X and 0.005 for Y instead? Or change one of them to 0 to stop rotation on that axis?

  rotateX(frameCount * 0.005);
  rotateY(frameCount * 0.007);
function draw() {
  background(colors.darkBlue); // Deep blue background
  
  // Rotating camera for a dynamic view
  rotateX(frameCount * 0.005);
  rotateY(frameCount * 0.007);
  
  // Draw generative lines to represent speed and motion
  stroke(colors.yellow);
  strokeWeight(2);
  noFill();
  
  // Use noise to create organic, flowing lines
  let detail = 100; // Number of points per line
  let lineCount = 50; // Number of lines
  let radius = min(width, height) * 0.3; // Max radius for lines

  for (let i = 0; i < lineCount; i++) {
    beginShape();
    let angleOffset = noise(i * 0.1, frameCount * 0.002) * TWO_PI; // Vary start angle
    let noiseScale = 0.01; // Scale for Perlin noise
    let speed = frameCount * 0.01; // Animate over time

    for (let j = 0; j < detail; j++) {
      let angle = map(j, 0, detail - 1, 0, TWO_PI) + angleOffset;
      
      // Calculate noise value for current point
      let xNoise = noise(cos(angle) * noiseScale + speed, sin(angle) * noiseScale + speed);
      let yNoise = noise(cos(angle) * noiseScale + 1000 + speed, sin(angle) * noiseScale + 1000 + speed);
      
      // Map noise to a radius variation
      let r = radius * map(xNoise, 0, 1, 0.5, 1.2);
      
      // Calculate 3D coordinates for the vertex
      let x = r * cos(angle);
      let y = r * sin(angle);
      let z = map(yNoise, 0, 1, -radius * 0.5, radius * 0.5); // Add depth

      vertex(x, y, z);
    }
    endShape();
  }
  
  // Draw the text "MAX VERSTAPPEN"
  fill(colors.white); // White text
  noStroke();
  
  // Position the text slightly in front of the generative art
  // push() and pop() are used to isolate transformations
  push();
  rotateX(-frameCount * 0.005); // Counter-rotate text to keep it somewhat readable
  rotateY(-frameCount * 0.007);
  translate(0, 0, radius * 1.5); // Move text forward
  text("MAX VERSTAPPEN", 0, 0); // Display text
  pop();
}
Line-by-line explanation (22 lines)

🔧 Subcomponents:

calculation Background clear background(colors.darkBlue);

Clears the canvas each frame with the deep blue color, erasing the previous frame's drawing

calculation 3D scene rotation rotateX(frameCount * 0.005); rotateY(frameCount * 0.007);

Rotates the entire scene around X and Y axes based on frameCount, creating continuous spinning motion

calculation Stroke and fill settings stroke(colors.yellow); strokeWeight(2); noFill();

Sets lines to yellow with 2-pixel width and no fill, so shapes draw as outlines only

for-loop Line generation loop for (let i = 0; i < lineCount; i++) {

Iterates 50 times to create 50 separate lines, each with its own starting angle variation

for-loop Point calculation loop for (let j = 0; j < detail; j++) {

Iterates 100 times per line, calculating the 3D position of each point that forms the line

calculation Perlin noise evaluation let xNoise = noise(cos(angle) * noiseScale + speed, sin(angle) * noiseScale + speed); let yNoise = noise(cos(angle) * noiseScale + 1000 + speed, sin(angle) * noiseScale + 1000 + speed);

Samples Perlin noise at different coordinates to create smooth, organic variation in radius and depth

calculation Text transformation isolation push(); rotateX(-frameCount * 0.005); rotateY(-frameCount * 0.007); translate(0, 0, radius * 1.5); text("MAX VERSTAPPEN", 0, 0); pop();

Uses push/pop to apply counter-rotations and forward translation only to the text, keeping it readable while the background spins

background(colors.darkBlue);
Fills the entire canvas with dark blue, clearing the previous frame so there are no trails
rotateX(frameCount * 0.005);
Rotates all subsequent drawing around the X-axis—the angle increases each frame, creating continuous spinning
rotateY(frameCount * 0.007);
Adds a rotation around the Y-axis as well, so the scene spins on two axes simultaneously
stroke(colors.yellow);
Sets the stroke (outline) color to yellow for all shapes drawn after this line
strokeWeight(2);
Sets the line thickness to 2 pixels—increase for thicker lines, decrease for thinner ones
noFill();
Prevents shapes from being filled in, so only their outlines are drawn
for (let i = 0; i < lineCount; i++) {
Starts a loop that runs 50 times, once for each line in the visualization
let angleOffset = noise(i * 0.1, frameCount * 0.002) * TWO_PI;
Uses Perlin noise to create a starting angle for each line that varies smoothly over time—different lines start at different angles
for (let j = 0; j < detail; j++) {
Nested loop that runs 100 times, calculating each point along the current line
let angle = map(j, 0, detail - 1, 0, TWO_PI) + angleOffset;
Maps the point index j (0 to 99) to an angle (0 to 2π radians), then adds the angle offset so each line starts at a different angle
let xNoise = noise(cos(angle) * noiseScale + speed, sin(angle) * noiseScale + speed);
Samples 2D Perlin noise using the point's angle and frame-based speed, producing a smooth random value that evolves over time
let r = radius * map(xNoise, 0, 1, 0.5, 1.2);
Maps the noise value to a radius multiplier between 0.5 and 1.2, so some points are closer and others farther from the center
let x = r * cos(angle);
Converts polar coordinates (angle and radius) to Cartesian X coordinate
let y = r * sin(angle);
Converts polar coordinates (angle and radius) to Cartesian Y coordinate
let z = map(yNoise, 0, 1, -radius * 0.5, radius * 0.5);
Maps another noise sample to a Z depth, placing points at different distances toward and away from the viewer
vertex(x, y, z);
Adds a point at (x, y, z) to the current shape—when endShape() is called, all vertices are connected with lines
push();
Saves the current transformation state (rotations, translations) so the text can be transformed independently
rotateX(-frameCount * 0.005);
Applies the opposite X rotation to the text, canceling out the scene's spin so text stays more readable
rotateY(-frameCount * 0.007);
Applies the opposite Y rotation to the text for the same reason
translate(0, 0, radius * 1.5);
Moves the text forward (toward the viewer) so it appears in front of the spinning lines
text("MAX VERSTAPPEN", 0, 0);
Draws the text string at the origin (after translations), displaying the name at the center of the screen
pop();
Restores the saved transformation state, so subsequent draw commands are not affected by the text's counter-rotations

windowResized()

windowResized() is called automatically by p5.js whenever the browser window changes size. This function ensures your sketch always fills the entire window, even when resized.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the canvas to match the window's new width and height whenever the browser window is resized

📦 Key Variables

racingFont p5.Font

Stores the loaded Oswald font, which is used to render the text "MAX VERSTAPPEN" with a racing-inspired typeface

let racingFont;
colors object

An object storing the Red Bull racing color palette (darkBlue, red, yellow, white) for consistent, reusable styling throughout the sketch

let colors = { darkBlue: color(0, 24, 69), red: color(220, 0, 0), yellow: color(255, 200, 0), white: color(255) };
detail number

The number of points per line—higher values create smoother curves, lower values create angular faceted geometry

let detail = 100;
lineCount number

The total number of separate lines drawn in the visualization—more lines create a denser, fuller effect

let lineCount = 50;
radius number

The base size of the line structure—controls how far from the center the lines extend

let radius = min(width, height) * 0.3;
noiseScale number

A multiplier on the Perlin noise input—smaller values create larger, smoother waves; larger values create tighter detail

let noiseScale = 0.01;
speed number

A time-based variable that animates the noise sampling over time, making the lines shift and morph each frame

let speed = frameCount * 0.01;
angleOffset number

A per-line offset to the starting angle, generated by noise, so each of the 50 lines begins at a slightly different angle

let angleOffset = noise(i * 0.1, frameCount * 0.002) * TWO_PI;
angle number

The angular position around the circle for each point on a line, mapped from 0 to TWO_PI and offset by angleOffset

let angle = map(j, 0, detail - 1, 0, TWO_PI) + angleOffset;
xNoise number

A Perlin noise value sampled based on the point's angle and frame count, used to vary the radius of points

let xNoise = noise(cos(angle) * noiseScale + speed, sin(angle) * noiseScale + speed);
yNoise number

Another Perlin noise value sampled with different offsets, used to vary the Z-depth of points

let yNoise = noise(cos(angle) * noiseScale + 1000 + speed, sin(angle) * noiseScale + 1000 + speed);
r number

The mapped radius for a specific point on a line, scaled by the xNoise value to vary how far it sits from the center

let r = radius * map(xNoise, 0, 1, 0.5, 1.2);
x number

The Cartesian X coordinate of a point, calculated from polar coordinates (angle and radius)

let x = r * cos(angle);
y number

The Cartesian Y coordinate of a point, calculated from polar coordinates (angle and radius)

let y = r * sin(angle);
z number

The Z-depth coordinate of a point in 3D space, mapped from yNoise to place points at different distances from the viewer

let z = map(yNoise, 0, 1, -radius * 0.5, radius * 0.5);

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

PERFORMANCE draw() - outer loop

Recalculating lineCount and detail every frame inside draw() uses extra CPU, even though they never change

💡 Move lineCount and detail to global variables initialized in setup(), so they are only created once

PERFORMANCE draw() - noise sampling

Each point calls noise() twice (xNoise and yNoise), and with 50 lines × 100 points, that's 10,000 noise evaluations per frame—very expensive

💡 Consider pre-computing or caching noise values, or reducing detail/lineCount for smoother performance on slower devices

STYLE setup() - color definition

The colors object uses inconsistent comment formatting: some colors have inline comments, others don't

💡 For consistency, either add comments to all colors or remove them all, making the code easier to scan

FEATURE draw() - text rendering

The text counter-rotations keep it somewhat readable, but it still wobbles and can be hard to read when spinning fast

💡 Add mouseX/mouseY control to let users rotate the scene manually, so they can pause and read the text clearly when desired

BUG preload() - font loading

The Fontsource CDN URL uses @latest, which could break if the CDN restructures—it's not pinned to a specific version

💡 Replace @latest with a specific version number (e.g., @4.5.0) to ensure the font URL remains stable in the future

FEATURE draw() - line generation

All 50 lines use the same noiseScale and speed values, making them uniform and less visually varied

💡 Vary noiseScale or speed per line (based on loop variable i) to create lines with different organic textures and animation speeds

🔄 Code Flow

Code flow showing preload, 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] click setup href "#fn-setup" click draw href "#fn-draw" setup --> canvas-setup[canvas-setup] setup --> text-styling[text-styling] setup --> color-palette[color-palette] click canvas-setup href "#sub-canvas-setup" click text-styling href "#sub-text-styling" click color-palette href "#sub-color-palette" draw --> background-clear[background-clear] draw --> scene-rotation[scene-rotation] draw --> stroke-setup[stroke-setup] draw --> outer-loop[outer-loop] draw --> text-isolation[text-isolation] click background-clear href "#sub-background-clear" click scene-rotation href "#sub-scene-rotation" click stroke-setup href "#sub-stroke-setup" click outer-loop href "#sub-outer-loop" click text-isolation href "#sub-text-isolation" outer-loop --> inner-loop[inner-loop] click inner-loop href "#sub-inner-loop" inner-loop --> noise-sampling[noise-sampling] click noise-sampling href "#sub-noise-sampling" draw --> windowresized[windowresized] click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What visual effects can I expect from the 'tu-tu-du-du max verstappen' sketch?

The sketch creates a dynamic 3D scene with swirling racing-inspired lines against a deep blue background, featuring the name 'MAX VERSTAPPEN' prominently displayed at the center.

Is the 'tu-tu-du-du max verstappen' sketch interactive for users?

The sketch does not include interactive elements; it primarily showcases a rotating 3D visual experience.

What creative coding techniques are demonstrated in this p5.js sketch?

This sketch utilizes 3D rendering with WEBGL, Perlin noise for organic line generation, and dynamic camera rotation to enhance the sense of speed and motion.

Preview

tu-tu-du-du max verstappen - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of tu-tu-du-du max verstappen - Code flow showing preload, setup, draw, windowresized
Code Flow Diagram