Animated Wave Circle - xelsed.ai

This sketch grows a recursive fractal tree using p5.js recursion and Perlin noise, complete with wind-swayed branches, autumn leaf colors, falling leaves, drifting clouds, a day/night sun-and-moon cycle, and wavy terrain. Every frame the tree is redrawn from scratch with slightly different noise-driven angles, so it never looks perfectly still even though nothing is truly 'animating' a fixed shape.

🧪 Try This!

Experiment with the code by making these changes:

  1. Grow a sparser, faster tree — Lowering maxDepth reduces how many times branches split, producing a smaller tree that renders much faster.
  2. Trigger an instant leaf storm — Raising leafFallRate dramatically increases the chance every frame that a branch tip drops a leaf, quickly filling the screen with falling leaves.
  3. Speed up the wind — Increasing windBaseSpeed makes the sine-wave sway calculations advance faster each frame, so branches and leaves whip back and forth much more energetically.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch procedurally grows a fractal tree by calling a drawBranch() function that calls itself over and over, each time drawing a shorter, thinner branch at a slightly different angle. On top of that recursive skeleton, the sketch layers Perlin noise (the noise() function) to make branches sway like they're in real wind, lerpColor() to fade leaves from green through orange and red to yellow over time, and a full day/night cycle that recolors the sky, sun/moon, clouds, and terrain using sine waves and map().

The code is organized around one big draw() loop that updates several independent systems each frame - sky gradient, particles, sun/moon, clouds, the tree itself, falling leaves, and terrain - and a separate LeafFall class that gives each falling leaf its own position, velocity, and rotation. Studying this sketch teaches recursion with a real visual payoff, how to fake organic motion with noise() instead of pure randomness, and how object-oriented classes keep track of many independent particles (leaves, clouds, dust) at once.

⚙️ How It Works

  1. On load, setup() creates a full-window canvas, defines the color palettes (leaf colors, trunk color, sky colors), and pre-fills arrays of cloud objects and background particle objects with random starting positions and sizes.
  2. Every frame, draw() advances a few slow-moving 'clocks': timeOfDay creeps forward to cycle through dawn/day/dusk/night, autumnProgress creeps forward to shift leaf colors, and two noise offsets drift to create gusty wind speed and a subtle wind direction bias.
  3. draw() then paints the background layers in order - sky gradient, dust/star particles, sun or moon, and clouds - each recolored based on the current timeOfDay so the whole scene visibly shifts from warm daylight to a dark, starry night and back.
  4. The tree itself is drawn by calling drawBranch() once from the trunk; that function calculates its own endpoint with trigonometry (cos/sin and an angle), nudges that angle with noise-based wind sway, draws itself as a jittered line, and then calls itself again 1-3 times to grow child branches with slightly shorter length and a randomized angle - stopping once depth or length gets too small.
  5. At the tips of the smallest branches, clusters of leaf circles are drawn, and occasionally a new LeafFall object is spawned that will fall, sway, and rotate under gravity and wind until it drifts off-screen and is removed.
  6. Finally drawTerrain() draws a wavy ground line using noise() so the horizon gently undulates, and windowResized() rebuilds the canvas, clouds, and particles whenever the browser window changes size.

🎓 Concepts You'll Learn

RecursionPerlin noise (noise())Trigonometry for angles (sin/cos)Color interpolation (lerpColor, map)ES6 classes for particle systemspush()/pop() transformationsVectors (createVector)Procedural/organic animation vs. randomness

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It's the right place to build up your starting data - here that means pre-filling the clouds and particles arrays with plain objects so draw() can just update and reuse them every frame instead of recreating them.

🔬 This loop fills the particles array before the sketch even starts drawing. What happens visually if you change size: random(1, 3) to size: random(1, 8) - do the dust motes start to look more like snow?

  // Initialize background particles
  for (let i = 0; i < numParticles; i++) {
    particles.push({
      x: random(width),
      y: random(height),
      size: random(1, 3),
      speed: random(0.1, 0.5),
      color: color(255, random(50, 150)), // Semi-transparent white
      direction: random(TWO_PI)
    });
  }
function setup() {
  createCanvas(windowWidth, windowHeight);
  angleMode(RADIANS); // Ensure angles are in radians for trigonometric functions

  // Define leaf colors for the autumn transition
  leafColor1 = color(0, 150, 0);   // Initial Green
  leafColor2 = color(255, 165, 0); // Orange
  leafColor3 = color(255, 0, 0);   // Red
  leafColor4 = color(255, 255, 0); // Yellow

  // Define trunk color (brown)
  trunkColor = color(100, 60, 30);

  // Define initial sky colors (will be dynamically updated)
  skyColor1 = color(173, 216, 230); // Light Blue
  skyColor2 = color(255, 192, 203); // Pink

  // Define sun/moon color (yellow for sun, light grey for moon - based on autumn progress)
  sunMoonColor = color(255, 200, 0); // Default to yellow sun

  // Randomize the initial angle offset for a more organic tree lean
  treeInitialAngleOffset = random(-PI / 20, PI / 20); // +/- 9 degrees

  // Initialize wind direction noise offset
  windDirectionNoiseOffset = random(1000);

  // Initialize branch growth noise offset
  branchNoiseOffset = random(1000);

  // Initialize clouds
  for (let i = 0; i < numClouds; i++) {
    clouds.push({
      x: random(width),
      y: random(height * 0.1, height * 0.4),
      size: random(width * 0.1, width * 0.25),
      detail: floor(random(5, 10))
    });
  }

  // Initialize background particles
  for (let i = 0; i < numParticles; i++) {
    particles.push({
      x: random(width),
      y: random(height),
      size: random(1, 3),
      speed: random(0.1, 0.5),
      color: color(255, random(50, 150)), // Semi-transparent white
      direction: random(TWO_PI)
    });
  }
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

for-loop Cloud Initialization Loop for (let i = 0; i < numClouds; i++) {

Creates numClouds cloud objects with random position, size, and detail, stored in the clouds array

for-loop Particle Initialization Loop for (let i = 0; i < numParticles; i++) {

Creates numParticles dust/star objects with random position, speed, and direction

createCanvas(windowWidth, windowHeight);
Makes the canvas fill the entire browser window instead of a fixed size.
angleMode(RADIANS);
Tells p5 that all angle arguments (to sin, cos, rotate, etc.) are in radians rather than degrees - the default, but stated explicitly here for clarity.
leafColor1 = color(0, 150, 0); // Initial Green
Stores the starting leaf color as a p5.Color object so it can be reused and interpolated later.
treeInitialAngleOffset = random(-PI / 20, PI / 20); // +/- 9 degrees
Picks a small random tilt for the whole tree so it doesn't grow perfectly straight up every time.
windDirectionNoiseOffset = random(1000);
Picks a random starting point inside Perlin noise space, so every reload produces a different but still smooth wind pattern.
clouds.push({
Adds a new plain JavaScript object (not a class) to the clouds array, each with its own x, y, size, and detail level.
particles.push({
Adds a new dust/star object to the particles array with a random speed and direction of drift.

draw()

draw() is the animation heartbeat that p5 calls automatically ~60 times per second. Notice how it doesn't draw anything complicated itself - instead it updates a handful of slow-moving state variables (timeOfDay, autumnProgress, wind noise) and then delegates the actual drawing to helper functions like drawGradient(), drawBranch(), and drawTerrain(). This 'update state, then call helpers' pattern keeps a big sketch organized and readable.

🔬 This chain of if/else blends between four fixed colors as autumnProgress rises. What happens if you swap leafColor2 and leafColor4 in setup() so the tree turns yellow before it turns orange?

  let currentLeafColor;
  if (autumnProgress < 0.33) {
    currentLeafColor = lerpColor(leafColor1, leafColor2, autumnProgress * 3);
  } else if (autumnProgress < 0.66) {
    currentLeafColor = lerpColor(leafColor2, leafColor3, (autumnProgress - 0.33) * 3);
  } else {
    currentLeafColor = lerpColor(leafColor3, leafColor4, (autumnProgress - 0.66) * 3);
  }
function draw() {
  // Update time of day
  timeOfDay = (timeOfDay + timeOfDaySpeed) % 1; // Cycle from 0 to 1

  // Update branch growth noise offset
  branchNoiseOffset += 0.001;

  // Draw the gradient sky background, dynamically adjusted for time of day
  updateSkyColors();
  drawGradient(skyColor1, skyColor2);

  // Draw background particles (dust/stars)
  drawParticles();

  // Draw sun or moon, dynamically positioned
  drawSunMoon();

  // Draw clouds
  drawClouds();

  // Update wind gust offset and wind direction bias offset
  windGustOffset += 0.005; // Adjust to change the frequency of wind gusts
  windDirectionNoiseOffset += 0.002; // Adjust to change wind direction bias frequency

  // Modulate wind speed and direction bias using noise
  let currentWindSpeed = windBaseSpeed * map(noise(windGustOffset), 0, 1, 0.7, 1.3); // Wind speed varies
  let windDirectionBias = map(noise(windDirectionNoiseOffset), 0, 1, -0.05, 0.05); // Subtle wind lean bias

  // Update autumn progress slowly over time
  autumnProgress += 0.0005; // Adjust this value to speed up or slow down autumn
  if (autumnProgress > 1) {
    autumnProgress = 1; // Stop at full autumn colors
  }

  // Calculate the current leaf color based on autumn progress
  let currentLeafColor;
  if (autumnProgress < 0.33) {
    currentLeafColor = lerpColor(leafColor1, leafColor2, autumnProgress * 3);
  } else if (autumnProgress < 0.66) {
    currentLeafColor = lerpColor(leafColor2, leafColor3, (autumnProgress - 0.33) * 3);
  } else {
    currentLeafColor = lerpColor(leafColor3, leafColor4, (autumnProgress - 0.66) * 3);
  }

  // Modulate leaf color brightness based on time of day
  let dayBrightness = map(sin(timeOfDay * TWO_PI), -1, 1, 0.5, 1); // Darker at night, brighter at day
  currentLeafColor = color(red(currentLeafColor) * dayBrightness, green(currentLeafColor) * dayBrightness, blue(currentLeafColor) * dayBrightness);

  // Modulate trunk color brightness based on time of day
  let currentTrunkColor = color(red(trunkColor) * dayBrightness, green(trunkColor) * dayBrightness, blue(trunkColor) * dayBrightness);

  // Start drawing the fractal tree from the wavy terrain baseline.
  let initialLength = height * 0.25; // Adjust for taller/shorter tree
  let initialAngle = -PI / 2 + treeInitialAngleOffset; // -PI/2 radians is straight up
  drawBranch(width / 2, height - terrainHeight, initialLength, initialAngle, maxDepth, 0, currentLeafColor, currentTrunkColor, currentWindSpeed, windDirectionBias);

  // Update and draw falling leaves
  for (let i = leafFalls.length - 1; i >= 0; i--) {
    let leaf = leafFalls[i];
    leaf.update(currentWindSpeed, windDirectionBias);
    leaf.display(dayBrightness); // Pass dayBrightness to leaves
    if (leaf.isOffScreen()) {
      leafFalls.splice(i, 1); // Remove leaves that fall off-screen
    }
  }

  // Draw the wavy terrain
  drawTerrain();
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Autumn Color Selection if (autumnProgress < 0.33) {

Picks which pair of leaf colors to blend between (green->orange, orange->red, or red->yellow) based on how far autumn has progressed

for-loop Falling Leaves Update Loop for (let i = leafFalls.length - 1; i >= 0; i--) {

Updates, draws, and removes every currently-falling leaf, looping backwards so splice() doesn't skip elements

timeOfDay = (timeOfDay + timeOfDaySpeed) % 1; // Cycle from 0 to 1
Advances the day/night clock and wraps it back to 0 using modulo, so it loops forever between 0 (midnight) and 1 (next midnight).
let currentWindSpeed = windBaseSpeed * map(noise(windGustOffset), 0, 1, 0.7, 1.3); // Wind speed varies
Uses Perlin noise to smoothly speed up and slow down the wind over time, instead of jumping around like random() would.
autumnProgress += 0.0005; // Adjust this value to speed up or slow down autumn
Very slowly nudges the season forward every frame - after enough frames this accumulates from 0 to 1.
let dayBrightness = map(sin(timeOfDay * TWO_PI), -1, 1, 0.5, 1); // Darker at night, brighter at day
Converts the 0-1 time-of-day value into a smooth brightness multiplier using a sine wave, so brightness rises and falls gradually rather than switching abruptly.
drawBranch(width / 2, height - terrainHeight, initialLength, initialAngle, maxDepth, 0, currentLeafColor, currentTrunkColor, currentWindSpeed, windDirectionBias);
Kicks off the whole recursive tree, starting from the ground at the horizontal center of the canvas.
for (let i = leafFalls.length - 1; i >= 0; i--) {
Loops backward through the falling leaves array so that removing an item with splice() doesn't cause the next item to be skipped.
leafFalls.splice(i, 1); // Remove leaves that fall off-screen
Deletes a leaf from the array once it has drifted off the bottom or sides of the canvas, keeping the array from growing forever.

updateSkyColors()

This function is a good example of state-machine style logic implemented with if/else if chains: instead of one continuous formula, the day is split into five named phases, each blending between two colors with map() and lerpColor(). It shows how you can fake a complex, continuous-looking effect from a handful of discrete color pairs.

function updateSkyColors() {
  let dawnSky1 = color(255, 150, 100);
  let dawnSky2 = color(255, 100, 150);
  let daySky1 = color(173, 216, 230);
  let daySky2 = color(255, 192, 203);
  let duskSky1 = color(255, 100, 150);
  let duskSky2 = color(255, 150, 100);
  let nightSky1 = color(20, 30, 60);
  let nightSky2 = color(60, 80, 120);

  if (timeOfDay < 0.15) { // Dawn
    let inter = map(timeOfDay, 0, 0.15, 0, 1);
    skyColor1 = lerpColor(nightSky2, dawnSky1, inter);
    skyColor2 = lerpColor(nightSky1, dawnSky2, inter);
  } else if (timeOfDay < 0.35) { // Day
    let inter = map(timeOfDay, 0.15, 0.35, 0, 1);
    skyColor1 = lerpColor(dawnSky1, daySky1, inter);
    skyColor2 = lerpColor(dawnSky2, daySky2, inter);
  } else if (timeOfDay < 0.5) { // Late Day / Early Dusk
    let inter = map(timeOfDay, 0.35, 0.5, 0, 1);
    skyColor1 = lerpColor(daySky1, duskSky1, inter);
    skyColor2 = lerpColor(daySky2, duskSky2, inter);
  } else if (timeOfDay < 0.65) { // Dusk
    let inter = map(timeOfDay, 0.5, 0.65, 0, 1);
    skyColor1 = lerpColor(duskSky1, nightSky1, inter);
    skyColor2 = lerpColor(duskSky2, nightSky2, inter);
  } else { // Night
    let inter = map(timeOfDay, 0.65, 1, 0, 1);
    skyColor1 = lerpColor(nightSky1, nightSky2, inter);
    skyColor2 = lerpColor(nightSky2, nightSky1, inter); // Loop back slightly
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

conditional Time-of-day Phase Chain if (timeOfDay < 0.15) { // Dawn

Divides the 0-1 day cycle into five phases (dawn, day, late day, dusk, night) and blends between two hard-coded colors within each phase

let dawnSky1 = color(255, 150, 100);
Defines a local, temporary color used only inside this function - it doesn't need to be a global variable since it's recalculated every call.
if (timeOfDay < 0.15) { // Dawn
Checks which slice of the day/night cycle we're currently in by comparing the 0-1 timeOfDay value against fixed thresholds.
let inter = map(timeOfDay, 0, 0.15, 0, 1);
Converts the current position within this phase into a fresh 0-1 blend value using map(), so lerpColor() can smoothly interpolate.
skyColor1 = lerpColor(nightSky2, dawnSky1, inter);
Overwrites the global skyColor1 variable so the next call to drawGradient() paints the newly blended sky.

drawGradient(c1, c2)

p5.js has no built-in gradient shape, so this function fakes one by drawing hundreds of thin horizontal lines, each a slightly different interpolated color. This 'draw many thin slices' technique is a common trick for gradients, and it's a good place to learn about the cost of per-pixel-row loops versus faster alternatives like drawingContext (the raw HTML5 canvas gradient API).

🔬 This loop steps through every single row of pixels. What happens if you change 'i++' to 'i += 4' so it only draws every 4th row - does the gradient still look smooth, and how much faster does the sketch run?

  for (let i = 0; i <= height; i++) {
    // Calculate interpolation amount (0 at top, 1 at bottom)
    let inter = map(i, 0, height, 0, 1);
function drawGradient(c1, c2) {
  noStroke(); // No outline for the gradient lines
  for (let i = 0; i <= height; i++) {
    // Calculate interpolation amount (0 at top, 1 at bottom)
    let inter = map(i, 0, height, 0, 1);
    // Interpolate between the two colors
    let c = lerpColor(c1, c2, inter);
    stroke(c); // Set the interpolated color for the line
    line(0, i, width, i); // Draw a thin horizontal line
  }
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

for-loop Horizontal Gradient Line Loop for (let i = 0; i <= height; i++) {

Draws one single-pixel-tall horizontal line per row of the canvas, each colored slightly differently to fake a smooth vertical gradient

let inter = map(i, 0, height, 0, 1);
Turns the current pixel row (0 to height) into a 0-1 fraction representing how far down the canvas we are.
let c = lerpColor(c1, c2, inter);
Blends between the top color (c1) and bottom color (c2) using that fraction, so row 0 is pure c1 and the last row is pure c2.
line(0, i, width, i); // Draw a thin horizontal line
Draws a full-width line at that row using the blended color, one row at a time, building up the gradient row by row.

drawClouds()

Each cloud is a plain object stored in the clouds array (not a class), with its own x, y, size, and detail properties. This function shows how to build a soft, organic-looking shape (a cloud) out of many simple primitives (circles) layered with transparency - a technique that works for smoke, foliage, and explosions too.

function drawClouds() {
  noStroke();
  let cloudBrightness = map(sin(timeOfDay * TWO_PI), -1, 1, 100, 255); // Brighter during day, fainter at night
  fill(255, cloudBrightness * 0.8); // White with some transparency

  for (let cloud of clouds) {
    // Update cloud position
    cloud.x += cloudSpeed;
    if (cloud.x > width + cloud.size / 2) {
      cloud.x = -cloud.size / 2; // Wrap around
      cloud.y = random(height * 0.1, height * 0.4); // New random height
      cloud.size = random(width * 0.1, width * 0.25);
      cloud.detail = floor(random(5, 10));
    }

    // Draw cloud shape using multiple overlapping circles
    let baseSize = cloud.size;
    let numCircles = cloud.detail;
    for (let i = 0; i < numCircles; i++) {
      let circleSize = baseSize * random(0.3, 0.8);
      let offsetX = random(-baseSize * 0.3, baseSize * 0.3);
      let offsetY = random(-baseSize * 0.15, baseSize * 0.15);
      circle(cloud.x + offsetX, cloud.y + offsetY, circleSize);
    }
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

conditional Cloud Wraparound Check if (cloud.x > width + cloud.size / 2) {

Resets a cloud back to the left edge with a new random size/height once it drifts fully off the right side

for-loop Cloud Puff Circles Loop for (let i = 0; i < numCircles; i++) {

Draws several overlapping semi-transparent circles per cloud to build up a soft, fluffy shape

let cloudBrightness = map(sin(timeOfDay * TWO_PI), -1, 1, 100, 255); // Brighter during day, fainter at night
Reuses the same sine-based day/night trick as other functions so clouds fade toward invisible at night.
cloud.x += cloudSpeed;
Moves each cloud object rightward by a constant speed every frame - simple linear motion.
cloud.x = -cloud.size / 2; // Wrap around
Once a cloud drifts fully off the right edge, teleports it just off the left edge so it can drift across again.
let circleSize = baseSize * random(0.3, 0.8);
Picks a random size for each individual puff circle, relative to the cloud's overall size, to vary the cloud's silhouette.

drawParticles()

This function demonstrates angle-and-speed movement: instead of storing separate vx/vy velocities, each particle stores a single 'direction' angle, and cos(direction)/sin(direction) convert that angle into x and y movement each frame - a compact alternative to p5.Vector.

🔬 Each particle moves at a fixed angle forever, using cos()/sin() of its stored direction. What happens if you add '+ frameCount * 0.001' inside the cos/sin calls so the direction slowly rotates over time?

    particle.x += cos(particle.direction) * particle.speed;
    particle.y += sin(particle.direction) * particle.speed;
function drawParticles() {
  noStroke();
  let particleAlpha = map(sin(timeOfDay * TWO_PI), -1, 1, 20, 150); // Fainter during day, brighter at night
  let particleColor = color(255, particleAlpha);

  for (let particle of particles) {
    // Update position
    particle.x += cos(particle.direction) * particle.speed;
    particle.y += sin(particle.direction) * particle.speed;

    // Wrap around canvas
    if (particle.x > width) particle.x = 0;
    if (particle.x < 0) particle.x = width;
    if (particle.y > height) particle.y = 0;
    if (particle.y < 0) particle.y = height;

    fill(particleColor);
    circle(particle.x, particle.y, particle.size);
  }
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

for-loop Particle Update & Draw Loop for (let particle of particles) {

Moves every dust/star particle along its fixed direction and speed, wraps it around the canvas edges, and draws it

let particleAlpha = map(sin(timeOfDay * TWO_PI), -1, 1, 20, 150); // Fainter during day, brighter at night
Makes the particles act like faint daytime dust motes that become brighter, more star-like specks at night.
particle.x += cos(particle.direction) * particle.speed;
Uses the particle's stored direction (an angle) and cos() to move it horizontally - this is standard vector-style movement using an angle and speed.
if (particle.x > width) particle.x = 0;
Wraps the particle back to the opposite edge once it drifts off screen, so particles loop forever instead of disappearing.

drawSunMoon()

This function is a compact example of using sin() and cos() of the same angle together to trace a circular or elliptical path - here representing the sun/moon's arc across the sky over one full day/night cycle.

function drawSunMoon() {
  noStroke();
  // Calculate sun/moon position dynamically
  let sunMoonX = width * 0.5 + sin(timeOfDay * TWO_PI) * width * 0.4;
  let sunMoonY = height * 0.3 + cos(timeOfDay * TWO_PI) * height * 0.2;

  // Change sun/moon color based on time of day
  // This smoothly transitions from sun-like colors to moon-like colors
  if (timeOfDay < 0.5) { // Sun phase
    let inter = map(timeOfDay, 0, 0.5, 0, 1);
    sunMoonColor = lerpColor(color(255, 200, 0), color(255, 255, 200), inter); // Yellow to lighter yellow
  } else { // Moon phase
    let inter = map(timeOfDay, 0.5, 1, 0, 1);
    sunMoonColor = lerpColor(color(255, 255, 200), color(220, 220, 220), inter); // Lighter yellow to light grey
  }

  // Adjust brightness based on how high it is in the sky
  let brightnessFactor = map(cos(timeOfDay * TWO_PI), -1, 1, 0.5, 1);
  fill(red(sunMoonColor) * brightnessFactor, green(sunMoonColor) * brightnessFactor, blue(sunMoonColor) * brightnessFactor);
  circle(sunMoonX, sunMoonY, sunMoonSize);
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

conditional Sun vs Moon Phase Check if (timeOfDay < 0.5) { // Sun phase

Chooses whether to render a sun-colored or moon-colored circle depending on which half of the day/night cycle we're in

let sunMoonX = width * 0.5 + sin(timeOfDay * TWO_PI) * width * 0.4;
Uses sin() of the day progress to sweep the sun/moon's horizontal position back and forth across the sky over the full cycle.
let sunMoonY = height * 0.3 + cos(timeOfDay * TWO_PI) * height * 0.2;
Uses cos() (offset 90 degrees from sin) so the vertical position traces an arc, rising and falling as x moves - together sin/cos trace an elliptical path across the sky.
let brightnessFactor = map(cos(timeOfDay * TWO_PI), -1, 1, 0.5, 1);
Dims the sun/moon when it's near the horizon (low in the sky) and brightens it near the top, similar to how real sunlight looks weaker at sunrise/sunset.

drawTerrain()

This function is a classic Perlin-noise-terrain pattern: sample noise() along the x axis and use the result to displace each vertex's y position, producing smooth, organic-looking hills instead of jagged random ones.

🔬 This loop samples noise() every 10 pixels along x. What happens visually if you change 'x += 10' to 'x += 40' - do the hills get blockier?

  for (let x = 0; x <= width; x += 10) {
    let y = height - terrainHeight + noise(noiseOffset + x * terrainDetail) * terrainHeight;
    vertex(x, y);
  }
function drawTerrain() {
  noStroke();
  let dayBrightness = map(sin(timeOfDay * TWO_PI), -1, 1, 0.5, 1); // Darker at night, brighter at day
  fill(50 * dayBrightness, 100 * dayBrightness, 50 * dayBrightness); // Dark green for terrain, modulated by time of day

  beginShape();
  vertex(0, height); // Start at bottom-left
  let noiseOffset = frameCount * terrainDetail;
  for (let x = 0; x <= width; x += 10) {
    let y = height - terrainHeight + noise(noiseOffset + x * terrainDetail) * terrainHeight;
    vertex(x, y);
  }
  vertex(width, height); // Go to bottom-right
  endShape(CLOSE);
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

for-loop Terrain Vertex Loop for (let x = 0; x <= width; x += 10) {

Samples Perlin noise across the width of the canvas to place each vertex of the wavy hill shape

beginShape();
Starts defining a custom polygon whose corners will be added one at a time with vertex() calls.
let noiseOffset = frameCount * terrainDetail;
Shifts where we sample the noise field based on the current frame, so the terrain very slowly ripples/shifts over time instead of staying perfectly static.
let y = height - terrainHeight + noise(noiseOffset + x * terrainDetail) * terrainHeight;
For each x position, looks up a smooth noise value and uses it to push the terrain's height up or down, creating rolling hills instead of a straight line.
endShape(CLOSE);
Closes the shape back to its starting point and fills it in, turning the list of vertices into a solid filled hill silhouette.

drawBranch(x1, y1, len, angle, depth, windOffset, leafColor, currentTrunkColor, windSpeed, windDirectionBias)

This is the heart of the sketch and a textbook example of recursion in creative coding: a function that calls itself with a smaller version of the problem (shorter branch, lower depth) until it hits a base case. Along the way it layers in Perlin noise for smooth wind sway, trigonometry (cos/sin) to place each branch's endpoint, and lerpColor() to shade branches from brown to green by depth - showing how several simple techniques combine into one complex, organic-looking structure.

🔬 This decides how many child branches sprout from each split. What happens if you force numBranches = 2 always, removing the randomness - does the tree look more symmetrical and less wild?

  let numBranches = floor(random(1, 4)); // 1, 2, or 3 branches at any level
  if (depth < 3 && numBranches > 1) {
    numBranches = floor(random(1, 3)); // Less likely to have 3 branches at very low depths
  }

🔬 This base case fires when depth < 2. What happens if you change it to depth < 4 so branches stop splitting earlier - does the tree get noticeably shorter and blockier?

  if (depth < 2 || len < 5) {
    // Draw leaves at the tips of the thinnest branches
    noStroke();
    let numLeaves = floor(random(3, 7)); // Cluster of 3 to 7 leaves
function drawBranch(x1, y1, len, angle, depth, windOffset, leafColor, currentTrunkColor, windSpeed, windDirectionBias) {
  // Base case: Stop recursion if depth is too low or branch length is too small.
  if (depth < 2 || len < 5) {
    // Draw leaves at the tips of the thinnest branches
    noStroke();
    let numLeaves = floor(random(3, 7)); // Cluster of 3 to 7 leaves
    for (let i = 0; i < numLeaves; i++) {
      let leafSize = random(4, 10); // Individual leaf size

      // Leaf rustling: Apply a small, dynamic offset
      let rustleOffsetX = sin(frameCount * 0.1 + windOffset + i * 0.5) * 2;
      let rustleOffsetY = cos(frameCount * 0.15 + windOffset + i * 0.7) * 2;

      let leafOffsetX = random(-leafSize, leafSize) + rustleOffsetX; // Random offset for cluster effect
      let leafOffsetY = random(-leafSize, leafSize) + rustleOffsetY;

      // Add slight transparency for a more organic look
      fill(leafColor.levels[0], leafColor.levels[1], leafColor.levels[2], random(150, 255));
      circle(x1 + leafOffsetX, y1 + leafOffsetY, leafSize);
    }

    // Leaf fall simulation: Add leaves to the falling array
    if (random(1) < leafFallRate * autumnProgress && leafFalls.length < maxFallingLeaves) {
      leafFalls.push(new LeafFall(x1, y1, leafColor));
    }
    return;
  }

  // Apply enhanced wind sway to the branch's angle.
  // Sway amplitude now depends on branch length (longer branches sway more).
  let swayAmplitude = map(len, 0, height * 0.25, 0.05, 0.15); // Scale amplitude based on initial length
  swayAmplitude *= map(noise(frameCount * windSpeed + windOffset * 2), 0, 1, 0.7, 1.3); // Modulate sway intensity
  let swayAngle = sin(frameCount * windSpeed + windOffset) * swayAmplitude;
  // Add a second, slower wave for more organic sway
  swayAngle += sin(frameCount * windSpeed * 0.5 + windOffset * 1.5) * swayAmplitude * 0.5;
  angle += swayAngle;

  // Add subtle wind direction bias
  angle += windDirectionBias;

  // Calculate the end point (x2, y2) of the current branch using trigonometry.
  let x2 = x1 + cos(angle) * len;
  let y2 = y1 + sin(angle) * len;

  // Draw the current branch as a series of jittered line segments for an organic, wavy look.
  noFill(); // We are using beginShape/endShape for the branch outline
  let branchWeight = map(depth, 0, maxDepth, 0.5, 8);
  strokeWeight(branchWeight);

  // Branch color: lerp from dynamic trunkColor (brown) to a greener color based on depth.
  // Deeper branches (closer to the trunk) are browner, shallower branches are greener.
  let branchGreenness = map(depth, maxDepth, 0, 0, 1.2); // Allow for slightly greener tips than pure green
  let currentBranchColor = lerpColor(currentTrunkColor, color(50, 100, 50), branchGreenness); // Brown to dark green
  stroke(currentBranchColor);

  let segmentCount = floor(map(len, 5, height * 0.25, 2, 8)); // More segments for longer branches
  let baseJitter = map(depth, 0, maxDepth, len * 0.1, len * 0.03); // Base jitter amount scales with branch length and depth
  let dynamicJitter = baseJitter * map(noise(frameCount * windSpeed * 0.2 + windOffset * 3 + depth * 0.2 + random(100)), 0, 1, 0.8, 1.2); // Further modulate jitter with noise

  beginShape();
  vertex(x1, y1); // Start point of the branch
  for (let i = 1; i <= segmentCount; i++) {
    let segmentProgress = i / segmentCount;
    let targetX = lerp(x1, x2, segmentProgress);
    let targetY = lerp(y1, y2, segmentProgress);

    // Apply random perpendicular jitter to the segment endpoint
    let jitterX = random(-dynamicJitter, dynamicJitter);
    let jitterY = random(-dynamicJitter, dynamicJitter);

    // Calculate perpendicular offset
    let perpAngle = angle + PI / 2;
    let offsetX = cos(perpAngle) * jitterX;
    let offsetY = sin(perpAngle) * jitterY;

    vertex(targetX + offsetX, targetY + offsetY);
  }
  vertex(x2, y2); // End point of the branch
  endShape();

  // Recursive calls for sub-branches.
  // Determine the number of sub-branches (1, 2, or 3) for variety.
  let numBranches = floor(random(1, 4)); // 1, 2, or 3 branches at any level
  if (depth < 3 && numBranches > 1) {
    numBranches = floor(random(1, 3)); // Less likely to have 3 branches at very low depths
  }

  for (let i = 0; i < numBranches; i++) {
    // New branch length is randomly shorter than the parent branch
    let newLen = len * random(0.6, 0.8);
    // New branch angle deviates randomly from the parent branch's angle, biased by Perlin noise
    let newAngle = angle + map(noise(branchNoiseOffset + len * 0.01 + depth * 0.1 + i * 0.5), 0, 1, -PI / 6, PI / 6);
    // Generate a unique wind offset for each sub-branch by adding a random value.
    // This creates a staggered, more natural wind sway effect across the tree.
    let newWindOffset = windOffset + random(-PI, PI);

    // Recursively call drawBranch for the new sub-branch
    drawBranch(x2, y2, newLen, newAngle, depth - 1, newWindOffset, leafColor, currentTrunkColor, windSpeed, windDirectionBias);
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

conditional Recursion Base Case if (depth < 2 || len < 5) {

Stops the recursion and draws a leaf cluster (and maybe spawns a falling leaf) once a branch is too short or too deep

for-loop Jittered Segment Loop for (let i = 1; i <= segmentCount; i++) {

Builds the branch as several small jittered line segments instead of one straight line, giving it an organic, hand-drawn look

for-loop Sub-branch Recursion Loop for (let i = 0; i < numBranches; i++) {

Calls drawBranch() again 1-3 times per branch to grow child branches, which is what makes the whole tree recursive

if (depth < 2 || len < 5) {
This is the recursion's base case - without it the function would call itself forever. It stops branching once the branch is either too deep (thin) or too short.
let swayAngle = sin(frameCount * windSpeed + windOffset) * swayAmplitude;
Uses a sine wave driven by frameCount (time) to smoothly oscillate the branch's angle back and forth - this is what makes the tree look like it's swaying in wind.
let x2 = x1 + cos(angle) * len;
Classic trigonometry: given a starting point, an angle, and a length, cos(angle)*len gives the horizontal distance to the branch's endpoint.
let y2 = y1 + sin(angle) * len;
Same idea as above but sin(angle) gives the vertical distance, together placing (x2, y2) at the tip of this branch.
let currentBranchColor = lerpColor(currentTrunkColor, color(50, 100, 50), branchGreenness);
Blends from brown (trunk) toward green based on depth, so branches nearer the trunk look woody and branches nearer the leaves look green.
let newLen = len * random(0.6, 0.8);
Each child branch is randomly 60-80% as long as its parent - this shrinking is what eventually stops the recursion (branches get shorter than 5 pixels).
let newAngle = angle + map(noise(branchNoiseOffset + len * 0.01 + depth * 0.1 + i * 0.5), 0, 1, -PI / 6, PI / 6);
Picks a new angle for the child branch that deviates from the parent's angle by up to 30 degrees (PI/6 radians), using noise() instead of random() so nearby branches curve smoothly rather than jumping erratically.
drawBranch(x2, y2, newLen, newAngle, depth - 1, newWindOffset, leafColor, currentTrunkColor, windSpeed, windDirectionBias);
The recursive call: it starts the child branch where the parent ended (x2, y2) and decreases depth by 1, guaranteeing the recursion eventually reaches the base case.

class LeafFall

LeafFall is a small self-contained class - a great pattern for anything you need many independent copies of (leaves, particles, enemies, bullets). Each instance stores its own position, velocity vector, rotation, and per-leaf randomness (windOffset), and exposes update()/display()/isOffScreen() methods so draw() can manage a whole array of leaves with a simple loop.

🔬 Gravity adds a fixed 0.05 to vertical velocity every frame. What happens if you change it to 0.15 - do leaves fall noticeably faster and straighter, with less time for the wind sway to show?

  update(windSpeed, windDirectionBias) {
    // Apply gravity
    this.velocity.y += 0.05;

    // Apply wind influence (wave-like movement)
    let windInfluence = sin(frameCount * windSpeed * 0.5 + this.windOffset) * 2;
    this.velocity.x = windInfluence + windDirectionBias * 10;
class LeafFall {
  constructor(x, y, color) {
    this.x = x;
    this.y = y;
    this.color = color;
    this.size = random(5, 12);
    this.velocity = createVector(0, random(0.5, 1.5)); // Initial downward velocity
    this.windOffset = random(1000); // Unique offset for wind influence
    this.rotation = random(TWO_PI);
    this.rotationSpeed = random(-0.05, 0.05);
  }

  update(windSpeed, windDirectionBias) {
    // Apply gravity
    this.velocity.y += 0.05;

    // Apply wind influence (wave-like movement)
    let windInfluence = sin(frameCount * windSpeed * 0.5 + this.windOffset) * 2;
    this.velocity.x = windInfluence + windDirectionBias * 10;

    // Update position
    this.x += this.velocity.x;
    this.y += this.velocity.y;

    // Update rotation
    this.rotation += this.rotationSpeed;
  }

  display(dayBrightness) {
    push();
    translate(this.x, this.y);
    rotate(this.rotation);
    noStroke();
    // Modulate leaf color brightness based on dayBrightness
    fill(red(this.color) * dayBrightness, green(this.color) * dayBrightness, blue(this.color) * dayBrightness, random(150, 255));
    circle(0, 0, this.size);
    pop();
  }

  isOffScreen() {
    return this.y > height || this.x < -this.size || this.x > width + this.size;
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Gravity Accumulation this.velocity.y += 0.05;

Continuously speeds up the leaf's downward fall each frame, simulating gravity

conditional Off-screen Check return this.y > height || this.x < -this.size || this.x > width + this.size;

Reports whether the leaf has drifted past any canvas edge so draw() knows to remove it

this.velocity = createVector(0, random(0.5, 1.5)); // Initial downward velocity
Uses p5's Vector helper to store x/y velocity as a single object, starting each leaf falling straight down at a random speed.
this.velocity.y += 0.05;
Adds a small constant acceleration to the vertical velocity every frame - this is a simple simulation of gravity, making leaves fall faster over time.
let windInfluence = sin(frameCount * windSpeed * 0.5 + this.windOffset) * 2;
Uses a sine wave (each leaf offset by its own random windOffset) to make the leaf sway side to side as it falls, rather than falling in a straight line.
push();
Saves the current drawing transformation state so the translate/rotate below only affects this one leaf.
translate(this.x, this.y);
Moves the drawing origin to the leaf's position, so the leaf can then be rotated around its own center.
rotate(this.rotation);
Spins the coordinate system by the leaf's current rotation angle, making the leaf appear to tumble as it falls.
pop();
Restores the saved transformation state, undoing the translate/rotate so it doesn't affect anything drawn after this leaf.
return this.y > height || this.x < -this.size || this.x > width + this.size;
Returns true if the leaf has moved below the bottom edge or past either side edge, signaling that draw() should delete it from the leafFalls array.

windowResized()

windowResized() is a special p5.js function that's automatically called whenever the browser window changes size. Here it's used not just to resize the canvas but to fully rebuild every size-dependent array (clouds, particles) so nothing looks stretched or misplaced after a resize.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Recalculate initial tree angle offset for a new tree on resize
  treeInitialAngleOffset = random(-PI / 20, PI / 20);
  windDirectionNoiseOffset = random(1000); // Reset wind direction noise
  branchNoiseOffset = random(1000); // Reset branch growth noise

  // Reinitialize clouds for the new canvas size
  clouds = [];
  for (let i = 0; i < numClouds; i++) {
    clouds.push({
      x: random(width),
      y: random(height * 0.1, height * 0.4),
      size: random(width * 0.1, width * 0.25),
      detail: floor(random(5, 10))
    });
  }

  // Reinitialize background particles
  particles = [];
  for (let i = 0; i < numParticles; i++) {
    particles.push({
      x: random(width),
      y: random(height),
      size: random(1, 3),
      speed: random(0.1, 0.5),
      color: color(255, random(50, 150)), // Semi-transparent white
      direction: random(TWO_PI)
    });
  }

  // Clear falling leaves on resize
  leafFalls = [];
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

for-loop Cloud Rebuild Loop for (let i = 0; i < numClouds; i++) {

Recreates the clouds array from scratch so cloud positions make sense on the new canvas size

resizeCanvas(windowWidth, windowHeight);
Changes the actual canvas dimensions to match the browser window's new size.
clouds = [];
Empties the existing clouds array before rebuilding it, since old cloud positions/sizes were based on the previous canvas dimensions.
leafFalls = [];
Clears any leaves currently falling, avoiding leaves left positioned relative to the old canvas size.

📦 Key Variables

maxDepth number

Maximum recursion depth for drawBranch() - controls how many times the tree splits and therefore how detailed/bushy it looks

let maxDepth = 8;
leafColor1, leafColor2, leafColor3, leafColor4 object (p5.Color)

The four fixed colors (green, orange, red, yellow) that leaves blend between as autumnProgress advances

leafColor1 = color(0, 150, 0);
trunkColor object (p5.Color)

Base brown color used for the tree's branches before day/night brightness and depth-based greening are applied

trunkColor = color(100, 60, 30);
windBaseSpeed number

Base multiplier for how quickly the noise-driven wind sway animates branches and leaves

let windBaseSpeed = 0.02;
windGustOffset number

A slowly-increasing counter used as input to noise() to generate smoothly varying wind gust speed

let windGustOffset = 0;
windDirectionNoiseOffset number

A slowly-increasing counter used as input to noise() to generate a subtle, smoothly-changing wind direction bias

let windDirectionNoiseOffset = 0;
autumnProgress number

Tracks how far the season has progressed from 0 (green) to 1 (full autumn yellow), increasing very slowly each frame

let autumnProgress = 0;
skyColor1, skyColor2 object (p5.Color)

The top and bottom colors of the sky gradient, recalculated every frame based on timeOfDay

skyColor1 = color(173, 216, 230);
treeInitialAngleOffset number

A small random tilt applied to the trunk's starting angle so the tree doesn't grow perfectly vertical

let treeInitialAngleOffset = 0;
clouds array

Holds one plain object per cloud, each with x, y, size, and detail properties used by drawClouds()

let clouds = [];
numClouds number

How many clouds exist in the sky at once

const numClouds = 5;
cloudSpeed number

How many pixels per frame each cloud drifts to the right

const cloudSpeed = 0.5;
leafFalls array

Holds active LeafFall instances currently falling from the tree; leaves are added by drawBranch() and removed once off-screen

let leafFalls = [];
maxFallingLeaves number

Caps how many leaves can be falling at once, preventing the array (and frame rate) from growing unbounded

const maxFallingLeaves = 50;
leafFallRate number

The per-frame probability that a branch tip spawns a new falling leaf, scaled by autumnProgress

const leafFallRate = 0.02;
terrainHeight number

Maximum vertical height of the wavy hill drawn at the bottom of the canvas

const terrainHeight = 50;
terrainDetail number

Noise sampling frequency used to shape the terrain - smaller values create broader, gentler hills

const terrainDetail = 0.005;
sunMoonSize number

Diameter in pixels of the sun/moon circle drawn in the sky

let sunMoonSize = 70;
sunMoonColor object (p5.Color)

Current color of the sun or moon, recalculated each frame based on timeOfDay

sunMoonColor = color(255, 200, 0);
timeOfDay number

Cycles from 0 to 1 to represent progress through a full day/night cycle, driving sky, sun/moon, and lighting

let timeOfDay = 0;
timeOfDaySpeed number

How quickly timeOfDay advances each frame - controls the overall speed of the day/night cycle

let timeOfDaySpeed = 0.0001;
branchNoiseOffset number

A slowly-increasing counter fed into noise() to determine each new sub-branch's angle deviation

let branchNoiseOffset = 0;
particles array

Holds one plain object per background dust/star particle, each with position, size, speed, color, and direction

let particles = [];
numParticles number

How many background dust/star particles are drawn

const numParticles = 200;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG drawBranch() - branch structure re-randomized every frame

Values like numBranches, newLen, newAngle, and newWindOffset are re-rolled with random() every single frame in draw(), since the entire tree is recomputed from scratch each call. This means the tree's actual branch layout subtly reshuffles every frame rather than smoothly growing or swaying a fixed structure - only the leaf clusters and sway are meant to feel organic, but the branch skeleton itself flickers.

💡 Build the tree's structure (angles, lengths, branch counts) once in setup() into a data structure (e.g., a tree of branch objects), storing only the wind sway as a per-frame calculation applied on top of that fixed structure in draw(). This keeps the skeleton stable while still swaying naturally.

PERFORMANCE drawGradient()

drawGradient() calls stroke() and line() once per row of the canvas height (potentially 800+ calls) every single frame, which is expensive compared to drawing the gradient once and reusing it.

💡 Render the sky gradient once into an offscreen buffer with createGraphics() whenever timeOfDay's colors change meaningfully (or every few frames), then just image() that buffer each frame instead of redrawing hundreds of lines every draw() call.

PERFORMANCE drawClouds()

Each cloud redraws its 5-10 puff circles with brand new random offsets and sizes every single frame, causing clouds to flicker and 'boil' instead of holding a stable fluffy shape.

💡 Generate each cloud's puff circle offsets/sizes once when the cloud object is created (or when it wraps around) and store them in the cloud object, then reuse those fixed offsets every frame for a calmer, more consistent cloud shape.

STYLE Global scope

There are over 20 loosely related global variables (wind, autumn, sky, clouds, particles, terrain, sun/moon) declared at the top of the file, making it hard to tell which variables belong together.

💡 Group related variables into plain objects, e.g. `let wind = { baseSpeed: 0.02, gustOffset: 0, directionOffset: 0 };`, which makes the code's structure easier to scan and reduces the risk of naming collisions.

FEATURE Overall sketch

The sketch runs entirely on its own with no way for a viewer to interact with it - no mouse or keyboard input is used anywhere.

💡 Add mousePressed() or keyPressed() handlers to let users nudge the wind speed, skip forward in the day/night cycle, or trigger an instant leaf-drop - small interactive touches that make the sketch feel alive rather than purely ambient.

🔄 Code Flow

Code flow showing setup, draw, updateskycolors, drawgradient, drawclouds, drawparticles, drawsunmoon, drawterrain, drawbranch, leaffall, windowresized

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

graph TD start[Start] --> setup[setup] setup --> setup-cloud-loop[Cloud Initialization Loop] setup-cloud-loop --> setup-particle-loop[Particle Initialization Loop] setup --> draw[draw loop] draw --> updateskycolors[updateskycolors] updateskycolors --> skycolor-phase-chain[Time-of-day Phase Chain] skycolor-phase-chain --> drawgradient[drawgradient] draw --> drawclouds[drawclouds] drawclouds --> cloud-wrap-check[Cloud Wraparound Check] cloud-wrap-check --> cloud-puff-loop[Cloud Puff Circles Loop] draw --> drawparticles[drawparticles] drawparticles --> particle-update-loop[Particle Update & Draw Loop] draw --> drawsunmoon[drawsunmoon] draw --> drawterrain[drawterrain] drawterrain --> terrain-vertex-loop[Terrain Vertex Loop] draw --> drawbranch[drawbranch] drawbranch --> branch-base-case[Recursion Base Case] branch-base-case --> branch-segment-loop[Jittered Segment Loop] branch-segment-loop --> branch-recursive-loop[Sub-branch Recursion Loop] draw --> leaffall[leaffall] leaffall --> leaffall-update-loop[Falling Leaves Update Loop] leaffall-update-loop --> leaffall-gravity[Gravity Accumulation] leaffall-gravity --> leaffall-offscreen-check[Off-screen Check] windowresized[windowresized] --> resize-cloud-rebuild[Cloud Rebuild Loop] click setup href "#fn-setup" click draw href "#fn-draw" click updateskycolors href "#fn-updateskycolors" click drawgradient href "#fn-drawgradient" click drawclouds href "#fn-drawclouds" click drawparticles href "#fn-drawparticles" click drawsunmoon href "#fn-drawsunmoon" click drawterrain href "#fn-drawterrain" click drawbranch href "#fn-drawbranch" click leaffall href "#fn-leaffall" click windowresized href "#fn-windowresized" click setup-cloud-loop href "#sub-setup-cloud-loop" click setup-particle-loop href "#sub-setup-particle-loop" click skycolor-phase-chain href "#sub-skycolor-phase-chain" click cloud-wrap-check href "#sub-cloud-wrap-check" click cloud-puff-loop href "#sub-cloud-puff-loop" click particle-update-loop href "#sub-particle-update-loop" click terrain-vertex-loop href "#sub-terrain-vertex-loop" click branch-base-case href "#sub-branch-base-case" click branch-segment-loop href "#sub-branch-segment-loop" click branch-recursive-loop href "#sub-branch-recursive-loop" click leaffall-update-loop href "#sub-leaffall-update-loop" click leaffall-gravity href "#sub-leaffall-gravity" click leaffall-offscreen-check href "#sub-leaffall-offscreen-check" click resize-cloud-rebuild href "#sub-resize-cloud-rebuild"

❓ Frequently Asked Questions

What visual effects does the Animated Wave Circle - XeLseDai sketch produce?

This sketch creates a visually captivating recursive fractal tree that sways organically, enhanced by dynamic autumn leaf colors and animated clouds against a gradient sky.

Is there any user interaction in the Animated Wave Circle - XeLseDai sketch?

The sketch currently does not include user interaction; it operates autonomously, showcasing the natural growth and changes of the tree and environment.

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

The sketch demonstrates recursive drawing techniques for tree growth, noise-based animation for wind effects, and color transitions for seasonal changes.

Preview

Animated Wave Circle - xelsed.ai - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Animated Wave Circle - xelsed.ai - Code flow showing setup, draw, updateskycolors, drawgradient, drawclouds, drawparticles, drawsunmoon, drawterrain, drawbranch, leaffall, windowresized
Code Flow Diagram