My awesome sketch

This sketch generates hypnotic, rotating starburst patterns made of radiating lines. Each pattern is controlled by a numeric seed—enter a seed value or generate random ones to instantly explore infinite geometric designs that slowly spin around the canvas center.

🧪 Try This!

Experiment with the code by making these changes:

  1. Slow down the rotation — The pattern spins via rotate(frameCount * 0.01). Decrease this multiplier to make rotation glacially slow and hypnotic.
  2. Make lines much thicker — Increase strokeWeight to bold, visible lines. Try a value like 2 or 3 to see the pattern become dramatic and graphic.
  3. Brighten the lines to bright blue — Change the stroke color from black to a vivid blue by replacing the RGB values in the stroke() call.
  4. Spread lines across only half the circle — Change TWO_PI to PI so lines only radiate outward in a half-circle instead of a full circle—creates a fan-like asymmetry.
  5. Double the number of lines per seed — Change the loop limit from mySeed to mySeed * 2 so each seed value generates twice as many line segments, creating denser patterns.
  6. Add a dark background for more contrast — Change the background color from light gray (240) to near-black (20) so the thin lines stand out with dramatic contrast.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates beautiful, kaleidoscopic starburst patterns that rotate slowly across your screen. The magic comes from combining three p5.js techniques: randomSeed() to ensure reproducible patterns from numeric seeds, trigonometry (sin and cos) to position lines radially around a center point, and the continuous draw loop with rotate() to animate the whole pattern. The result is hypnotic and endlessly explorable.

The code is organized around a key insight: precompute all pattern geometry once (using the seed), then simply rotate and redraw it every frame. This separation of concerns makes the sketch fast and easy to modify. By studying it, you will learn how randomSeed() creates deterministic randomness, how to lay out elements in polar coordinates, and the power of separating data generation from rendering.

⚙️ How It Works

  1. When the sketch loads, setup() creates the canvas, builds input controls (a text field for the seed and a button for random patterns), and calls regeneratePatternData() to compute the initial starburst geometry
  2. regeneratePatternData() uses randomSeed(mySeed) to lock in deterministic randomness, then loops mySeed times to create one line per iteration, spacing them evenly around a circle using trigonometry
  3. For each line, the angle is calculated by mapping the loop counter to a full circle (0 to TWO_PI), and cos/sin convert that angle into x,y endpoints—this creates the radial layout
  4. Every frame, draw() clears the background, translates to the canvas center, rotates by a tiny amount, and draws all precomputed lines, creating the spinning effect
  5. When you change the seed input or press the button, the event handlers call regeneratePatternData() to instantly compute a brand new pattern with different geometry
  6. If the browser window is resized, windowResized() stretches the canvas and recomputes the pattern to fill the new space

🎓 Concepts You'll Learn

randomSeed() and deterministic randomnessTrigonometry: sine, cosine, polar coordinatesThe draw loop and animation with rotate()Event handling: input fields and button pressesPrecomputation: separating data from renderingCanvas transforms: translate() and rotate()

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It is the perfect place to create your canvas, build interactive controls, and set up initial data. The order matters—always create your canvas first, then add other elements.

function setup() {
  createCanvas(windowWidth, windowHeight);

  // Create input field for seed
  inputSeed = createInput(mySeed);
  inputSeed.attribute('type', 'number'); // Ensure it's a number input
  inputSeed.attribute('min', '1');       // Optional: minimum seed value
  inputSeed.attribute('max', '10000');   // Optional: maximum seed value
  inputSeed.position(10, 10);            // Position controls
  inputSeed.style('width', '100px');
  inputSeed.style('font-size', '16px');
  inputSeed.input(updateSeedAndDraw);    // Call function when input changes

  // Create button to generate new random seed
  buttonGenerate = createButton('Generate Random Pattern');
  buttonGenerate.position(120, 10);      // Position controls
  buttonGenerate.style('font-size', '16px');
  buttonGenerate.mousePressed(generateRandomPattern); // Call function on button press

  // Precompute pattern for the initial seed
  regeneratePatternData();
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

function-call Canvas initialization createCanvas(windowWidth, windowHeight);

Creates a full-screen canvas that stretches to fill the browser window

function-call Seed input field setup inputSeed = createInput(mySeed);

Creates a text input field starting with mySeed value for users to type in a seed number

function-call Random pattern button setup buttonGenerate = createButton('Generate Random Pattern');

Creates an interactive button that triggers random pattern generation when clicked

function-call Initial pattern computation regeneratePatternData();

Computes the line endpoints for the starting seed value so the sketch is not blank on load

createCanvas(windowWidth, windowHeight);
Creates a p5.js canvas the size of the entire browser window, making it responsive to screen size
inputSeed = createInput(mySeed);
Creates an interactive text input field starting with the value of mySeed (initially 1)
inputSeed.attribute('type', 'number');
Tells the browser this input should only accept numbers, giving users a numeric keyboard on mobile
inputSeed.input(updateSeedAndDraw);
Connects the input field to the updateSeedAndDraw() function so it runs every time the user types a new number
buttonGenerate = createButton('Generate Random Pattern');
Creates an interactive button with the label 'Generate Random Pattern'
buttonGenerate.mousePressed(generateRandomPattern);
Connects the button to the generateRandomPattern() function so it runs when clicked
regeneratePatternData();
Precomputes all line endpoints for the starting seed before draw() ever runs, so the canvas shows a pattern immediately

draw()

draw() runs 60 times per second (60 frames per second by default). Every frame, we clear the old picture, apply transforms (translate and rotate), and redraw everything. This sequence creates smooth animation. The key insight is that frameCount increments by 1 every frame, so rotate(frameCount * 0.01) creates steadily increasing rotation.

🔬 The sketch draws two lines per pattern element: one from center outward, one slightly offset. What if you comment out the second line by adding // at the start? What changes visually?

    // Main line from center
    line(0, 0, l.x1, l.y1);
    // Secondary line
    line(l.x1, l.y1, l.x2, l.y2);

🔬 What happens if you comment out the translate() line (add //) so the canvas rotates around the top-left instead of the center? Try it and describe what you see.

  // Move origin to center
  translate(width / 2, height / 2);

  // Simple animation: rotate the whole pattern over time
  rotate(frameCount * 0.01);
function draw() {
  background(240);      // A light background color
  stroke(0, 50);        // Light grey lines with some transparency
  strokeWeight(0.5);    // Thin lines

  // Move origin to center
  translate(width / 2, height / 2);

  // Simple animation: rotate the whole pattern over time
  rotate(frameCount * 0.01); // Adjust this value for faster/slower rotation

  // Draw the precomputed lines
  for (let i = 0; i < linesData.length; i++) {
    const l = linesData[i];
    // Main line from center
    line(0, 0, l.x1, l.y1);
    // Secondary line
    line(l.x1, l.y1, l.x2, l.y2);
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

function-call Frame clearing background(240);

Erases the previous frame by painting the entire canvas a light gray, necessary for animation

function-call Origin repositioning translate(width / 2, height / 2);

Moves the coordinate system origin from top-left to the center of the canvas, so rotation spins around the middle

function-call Pattern rotation rotate(frameCount * 0.01);

Rotates the entire coordinate system a tiny bit each frame, creating the hypnotic spinning effect

for-loop Line rendering loop for (let i = 0; i < linesData.length; i++) {

Iterates through every precomputed line segment and draws it to the canvas

background(240);
Clears the canvas with a light gray color (240 out of 255), erasing the previous frame so animation appears smooth
stroke(0, 50);
Sets line color to black (0) with 50 alpha transparency, making lines semi-transparent and creating visual layering
strokeWeight(0.5);
Makes lines very thin (0.5 pixels), creating delicate, intricate geometry that emphasizes the starburst pattern
translate(width / 2, height / 2);
Shifts the origin point from the top-left corner to the exact center of the canvas, so all coordinates are relative to center
rotate(frameCount * 0.01);
Rotates the entire coordinate system by a tiny amount (0.01 radians) each frame; frameCount increases by 1 every frame, creating continuous rotation
for (let i = 0; i < linesData.length; i++) {
Loops through every line object stored in the linesData array (the count equals the current seed value)
line(0, 0, l.x1, l.y1);
Draws a line from the center (0, 0) to the first endpoint (l.x1, l.y1), creating the main ray of the starburst
line(l.x1, l.y1, l.x2, l.y2);
Draws a second, shorter line from the first endpoint to a slightly offset second endpoint, adding visual complexity

regeneratePatternData()

This function is the heart of pattern generation. randomSeed() is the secret weapon: it locks the random number generator to the seed value, guaranteeing the same seed always produces identical geometry. The loop then uses polar-to-Cartesian conversion (angle + distance → x,y via cos/sin) to place lines radially around the center. This separation of concerns—compute all geometry first, draw it later—keeps the sketch fast and easy to animate.

🔬 This loop creates one line per iteration, with angle evenly spaced around a circle. What happens if you change mySeed to mySeed * 2 so the loop runs twice as many times? Try it—do you get a denser pattern with more lines?

  for (let i = 0; i < mySeed; i++) {
    // Angle evenly spaced around the circle
    let angle = map(i, 0, mySeed, 0, TWO_PI);

🔬 The secondary line is 0.5 times the main line's length and gets a random angle nudge between -0.2 and 0.2 radians. What if you increase this range to random(-0.5, 0.5)? How chaotic does the pattern become?

    // Secondary line: slight random angle variation, shorter length
    let x2 = (length * 0.5) * cos(angle + random(-0.2, 0.2));
    let y2 = (length * 0.5) * sin(angle + random(-0.2, 0.2));
function regeneratePatternData() {
  linesData = [];

  if (mySeed <= 0) return;

  randomSeed(mySeed); // Ensure pattern is consistent for a given seed

  const base = min(width, height);
  const minLen = base * 0.2;
  const maxLen = base * 0.4;

  for (let i = 0; i < mySeed; i++) {
    // Angle evenly spaced around the circle
    let angle = map(i, 0, mySeed, 0, TWO_PI);

    // Random length within a range
    let length = random(minLen, maxLen);

    // End point of the main line
    let x1 = length * cos(angle);
    let y1 = length * sin(angle);

    // Secondary line: slight random angle variation, shorter length
    let x2 = (length * 0.5) * cos(angle + random(-0.2, 0.2));
    let y2 = (length * 0.5) * sin(angle + random(-0.2, 0.2));

    linesData.push({ x1, y1, x2, y2 });
  }
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

statement Clear previous data linesData = [];

Empties the linesData array so old pattern data doesn't mix with the new pattern

conditional Seed validation if (mySeed <= 0) return;

Stops the function early if seed is invalid (non-positive), preventing errors

function-call Deterministic randomness randomSeed(mySeed);

Locks the random number generator to the seed value, so the same seed always produces the same pattern

calculation Scale calculations const base = min(width, height);

Calculates the base size relative to the smaller canvas dimension so the pattern scales responsively

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

Repeats mySeed times, creating one line element per loop—so seed value directly controls pattern complexity

calculation Angle distribution let angle = map(i, 0, mySeed, 0, TWO_PI);

Spreads lines evenly around the circle; map() converts loop counter (0 to mySeed-1) into angles (0 to TWO_PI)

calculation Endpoint conversion to Cartesian let x1 = length * cos(angle); let y1 = length * sin(angle);

Converts polar coordinates (angle, length) into Cartesian (x, y) using trigonometry—this is why lines radiate outward

linesData = [];
Empties the linesData array, clearing any previously stored line data so we start fresh
if (mySeed <= 0) return;
Stops execution early if seed is not positive, preventing bugs from invalid seeds
randomSeed(mySeed);
Locks the random number generator to the seed value; now random() will produce the same sequence every time this seed is used
const base = min(width, height);
Calculates the smaller of width or height, used as a reference size for scaling line lengths responsively
const minLen = base * 0.2;
Sets the minimum line length to 20% of the canvas's smaller dimension, ensuring lines don't get too short
const maxLen = base * 0.4;
Sets the maximum line length to 40% of the canvas's smaller dimension, ensuring lines don't explode off-screen
for (let i = 0; i < mySeed; i++) {
Loops mySeed times—if seed is 50, this creates 50 line elements; if seed is 5, only 5 elements
let angle = map(i, 0, mySeed, 0, TWO_PI);
map() converts the loop counter (0, 1, 2, ... mySeed-1) into evenly spaced angles around a full circle (0 to 6.28 radians)
let length = random(minLen, maxLen);
Picks a random length between minLen and maxLen; because of randomSeed(), the same seed will always pick the same sequence of random lengths
let x1 = length * cos(angle);
Converts angle and length (polar coordinates) into x position using the cosine function, positioning the line endpoint horizontally
let y1 = length * sin(angle);
Converts angle and length into y position using the sine function, positioning the line endpoint vertically
let x2 = (length * 0.5) * cos(angle + random(-0.2, 0.2));
Calculates a second x position: half the original length, at a slightly random angle offset, creating the secondary line segment
let y2 = (length * 0.5) * sin(angle + random(-0.2, 0.2));
Calculates the second y position similarly, completing the offset secondary line segment
linesData.push({ x1, y1, x2, y2 });
Stores the four calculated coordinates as an object and adds it to the linesData array for later drawing

updateSeedAndDraw()

This function connects the input field to the pattern generator. It is called automatically every time the user types a character into the input field. The int() conversion is crucial—it ensures we always have a valid integer, never a decimal or text string. The if-check provides defensive programming: always validate user input before using it.

function updateSeedAndDraw() {
  let newSeed = int(inputSeed.value());
  // Ensure the seed is a positive integer
  if (newSeed > 0) {
    mySeed = newSeed;
    regeneratePatternData();
  } else {
    console.warn("Seed must be a positive integer.");
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

function-call Read input value let newSeed = int(inputSeed.value());

Retrieves the text from the input field and converts it to an integer

conditional Input validation if (newSeed > 0) {

Ensures the seed is positive before using it; rejects invalid values

function-call Pattern regeneration regeneratePatternData();

Recomputes all line geometry for the new seed, instantly changing the visual pattern

let newSeed = int(inputSeed.value());
Gets the text the user typed into the input field (.value()) and converts it to an integer with int()
if (newSeed > 0) {
Checks whether the new seed is positive (greater than 0); only valid seeds proceed
mySeed = newSeed;
Updates the global mySeed variable to the new value entered by the user
regeneratePatternData();
Calls the regeneratePatternData() function to recompute all line endpoints for the new seed—the pattern changes instantly on screen
console.warn("Seed must be a positive integer.");
If the seed is invalid, prints a friendly warning to the browser console instead of crashing

generateRandomPattern()

This function is the button's callback. When the user clicks 'Generate Random Pattern,' this runs: pick a random seed, update the input field so the UI stays consistent, then recompute the pattern. The magic is in the synchronization—by updating inputSeed.value(), we ensure the input field and the mySeed variable are always in agreement.

🔬 The button picks a random seed between 1 and 1000. What if you change the range to random(1, 100)? What if you change it to random(1, 10000)? How does the search space change what patterns you discover?

function generateRandomPattern() {
  mySeed = floor(random(1, 1000)); // Generate a new random integer seed
  inputSeed.value(mySeed);         // Update the input field with the new seed
  regeneratePatternData();
}
function generateRandomPattern() {
  mySeed = floor(random(1, 1000)); // Generate a new random integer seed
  inputSeed.value(mySeed);         // Update the input field with the new seed
  regeneratePatternData();
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Random seed generation mySeed = floor(random(1, 1000));

Picks a random integer between 1 and 999 and stores it as the new seed

function-call Sync input field inputSeed.value(mySeed);

Updates the input field display so it shows the newly generated seed, keeping the UI in sync

function-call Pattern regeneration regeneratePatternData();

Recomputes geometry for the new random seed and draws it on the next frame

mySeed = floor(random(1, 1000));
random(1, 1000) generates a decimal number between 1 and 1000, and floor() rounds it down to an integer
inputSeed.value(mySeed);
Updates the input field to display the newly generated seed, so the user can see which seed just produced the current pattern
regeneratePatternData();
Recomputes all line endpoints for the new seed, triggering an immediate visual update on the next draw() call

windowResized()

windowResized() is a special p5.js callback that runs automatically whenever the browser window is resized. Without it, the canvas would stay the old size and the pattern would be cut off. By calling regeneratePatternData(), we ensure line lengths are recalculated relative to the new canvas dimensions, keeping the starburst properly scaled.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  regeneratePatternData();
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

function-call Canvas resizing resizeCanvas(windowWidth, windowHeight);

Stretches the p5.js canvas to match the new browser window size

function-call Pattern rescaling regeneratePatternData();

Recomputes line lengths relative to the new canvas dimensions so the pattern scales appropriately

resizeCanvas(windowWidth, windowHeight);
Tells p5.js to resize the canvas to match the browser window's new dimensions when the user resizes
regeneratePatternData();
Recalculates all line lengths (which depend on canvas size via the base variable) so the pattern stays proportional in the new window

📦 Key Variables

mySeed number

Stores the current seed value; controls which geometric pattern is generated. Each seed from 1 to 1000+ produces a unique starburst design

let mySeed = 1;
inputSeed object

Holds a reference to the p5.js input field DOM element, allowing the sketch to read and update the seed value from user typing

let inputSeed;
buttonGenerate object

Holds a reference to the p5.js button DOM element, allowing the sketch to respond when the user clicks 'Generate Random Pattern'

let buttonGenerate;
linesData array

Stores an array of line segment objects {x1, y1, x2, y2}. Precomputing all geometry here (instead of generating it each frame) keeps the sketch fast

let linesData = [];
frameCount number

A built-in p5.js variable that increments by 1 every frame; used to drive the rotation animation (rotate(frameCount * 0.01))

rotate(frameCount * 0.01);
width number

A built-in p5.js variable that stores the canvas width in pixels; used for centering and scaling

translate(width / 2, height / 2);
height number

A built-in p5.js variable that stores the canvas height in pixels; used for centering and scaling

translate(width / 2, height / 2);

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG regeneratePatternData()

The loop condition uses 'i < mySeed' but the angle mapping is 'map(i, 0, mySeed, ...)'. If seed changes during animation, this can cause misaligned angles on the frame they regenerate.

💡 Store mySeed's value at the start of regeneratePatternData() to ensure consistent loop bounds and mapping throughout the function execution.

PERFORMANCE draw()

Every frame, the loop iterates through linesData and draws two line() calls per element. For large seeds (e.g., 500+), this can lag on slower devices.

💡 Consider using createGraphics() to precompute the pattern into an off-screen buffer once, then rotate and display that buffer each frame instead of redrawing every line.

STYLE regeneratePatternData() and updateSeedAndDraw()

The functions regeneratePatternData() is called from four different places (setup, draw callbacks, button callback, windowResized). This scattered dependency makes the code harder to follow.

💡 Consider a centralized state management system or a single updatePattern() function that orchestrates all pattern regeneration calls.

FEATURE setup()

The sketch generates patterns from seeds 1-1000, but there's no way to save or share a particularly beautiful pattern.

💡 Add a 'Copy Link' button that generates a URL with the current seed as a query parameter (e.g., ?seed=42), allowing users to bookmark and share specific patterns.

FEATURE draw()

The rotation speed is fixed at 0.01. Users might want to pause or control the animation speed interactively.

💡 Add a slider or checkbox to pause/resume rotation, or a range input to let users control rotate(frameCount * speedFactor) dynamically.

🔄 Code Flow

Code flow showing setup, draw, regeneratepatterndata, updateseedanddraw, generaterandompattern, 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 --> input-creation[input-creation] setup --> button-creation[button-creation] setup --> initial-pattern[initial-pattern] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-creation href "#sub-canvas-creation" click input-creation href "#sub-input-creation" click button-creation href "#sub-button-creation" click initial-pattern href "#sub-initial-pattern" draw --> background-clear[background-clear] draw --> transform-translate[transform-translate] draw --> transform-rotate[transform-rotate] draw --> drawing-loop[drawing-loop] click draw href "#fn-draw" click background-clear href "#sub-background-clear" click transform-translate href "#sub-transform-translate" click transform-rotate href "#sub-transform-rotate" click drawing-loop href "#sub-drawing-loop" drawing-loop --> data-reset[data-reset] drawing-loop --> generation-loop[generation-loop] generation-loop --> angle-mapping[angle-mapping] generation-loop --> endpoint-calculation[endpoint-calculation] click data-reset href "#sub-data-reset" click generation-loop href "#sub-generation-loop" click angle-mapping href "#sub-angle-mapping" click endpoint-calculation href "#sub-endpoint-calculation" windowresized[windowresized] --> canvas-resize[canvas-resize] windowresized --> pattern-rescale[pattern-rescale] windowresized --> pattern-regeneration[pattern-regeneration] click windowresized href "#fn-windowresized" click canvas-resize href "#sub-canvas-resize" click pattern-rescale href "#sub-pattern-rescale" click pattern-regeneration href "#sub-pattern-regeneration" regeneratepatterndata[regeneratepatterndata] --> seed-validation[seed-validation] seed-validation --> seed-lock[seed-lock] seed-validation --> validation-check[validation-check] validation-check --> pattern-update[pattern-update] click regeneratepatterndata href "#fn-regeneratepatterndata" click seed-validation href "#sub-seed-validation" click seed-lock href "#sub-seed-lock" click validation-check href "#sub-validation-check" click pattern-update href "#sub-pattern-update" updateseedanddraw[updateseedanddraw] --> input-read[input-read] input-read --> validation-check validation-check --> pattern-update click updateseedanddraw href "#fn-updateseedanddraw" generaterandompattern[generaterandompattern] --> random-seed-generation[random-seed-generation] random-seed-generation --> input-field-update[input-field-update] input-field-update --> pattern-regeneration click generaterandompattern href "#fn-generaterandompattern"

❓ Frequently Asked Questions

What visual effect does 'My awesome sketch' create?

This sketch generates hypnotic, starburst-like line patterns that rotate around the center of the screen, creating a mesmerizing visual experience.

How can users interact with the sketch to explore different designs?

Users can tweak the numeric seed value or click the 'Generate Random Pattern' button to instantly create new geometric designs.

What creative coding technique is showcased in this p5.js sketch?

The sketch demonstrates the use of precomputed line endpoints and rotation animations to create dynamic and visually captivating patterns.

Preview

My awesome sketch - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of My awesome sketch - Code flow showing setup, draw, regeneratepatterndata, updateseedanddraw, generaterandompattern, windowresized
Code Flow Diagram