AI Particle Fountain - Physics Particle System Click to spawn beautiful particle fountains! Particl

This sketch creates a glowing particle fountain simulation where clicking anywhere on the canvas spawns a new fountain that shoots colorful, physics-driven particles into the air. Particles arc upward under gravity, fall back down, and burst into secondary 'splash' particles when they hit the ground, all rendered with additive blending for a soft neon-glow look.

🧪 Try This!

Experiment with the code by making these changes:

  1. Boost gravity — Increasing the starting gravity value makes particles fall back down much faster, creating shorter, snappier arcs.
  2. Denser fountain jet — Raising the emission rate spawns far more particles per frame, making each fountain look thick and full instead of sparse.
  3. Shift the color palette — Remapping the hue range changes particles from blue-to-pink into a green-to-yellow glow scheme.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds an interactive particle fountain: click anywhere and a burst of glowing particles shoots upward, curves under gravity, and splashes into smaller particles when it hits the ground. The visual glow comes from additive blend mode and HSB color mapped to each particle's speed, so fast-moving particles glow hot pink-white while slower ones fade toward blue. Under the hood it relies on p5.Vector for physics, custom Fountain and Particle classes, and live DOM sliders that let you tweak gravity and launch speed in real time.

The code is organized around two custom classes - Fountain, which periodically emits particles, and Particle, which stores position, velocity, and appearance and knows how to update and draw itself - plus a draw() loop that steps through three arrays (fountains, particles, splashes) every frame, applying gravity, checking for ground collisions, and removing dead objects. Studying this sketch teaches you how to structure a full particle system with object-oriented JavaScript, how vector-based physics works frame by frame, and how DOM UI elements (sliders) can control a p5.js sketch's behavior live.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, switches to HSB color mode for easier hue control, builds the gravity and speed sliders, and spawns one starting Fountain near the bottom of the screen.
  2. Every frame, draw() fades the background slightly instead of fully clearing it (creating motion trails), reads the current slider values into gravity and baseInitialSpeed, and draws the ground.
  3. Each active Fountain calls emit() to spawn new Particle objects at an upward angle with randomized speed, and removes itself once its lifeFrames counter runs out.
  4. All particles have gravity applied as a force, then update their position from velocity; when a fountain particle reaches the ground it is deleted and replaced by a shower of smaller 'splash' particles via createSplash().
  5. Splash particles get a small bounce off the ground (velocity flipped and dampened) before fading away, and every particle is drawn as layered, semi-transparent circles under ADD blend mode so overlapping particles glow brighter.
  6. Clicking the canvas at any time calls spawnFountainAtMouse(), pushing a brand new Fountain at the mouse position so you can layer multiple fountains at once.

🎓 Concepts You'll Learn

Particle systems with classesp5.Vector physics (position, velocity, acceleration)Additive blend mode for glow effectsHSB color mode and speed-based color mappingArray management with reverse for-loops and spliceDOM UI elements (sliders) driving sketch parameters

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It's the right place to configure the canvas, color mode, UI, and any starting objects before the animation loop begins.

function setup() {
  const cnv = createCanvas(windowWidth, windowHeight);
  cnv.id('p5canvas');

  // Only spawn fountains when clicking the canvas, not UI
  cnv.mousePressed(spawnFountainAtMouse);

  // Use HSB for easier color control
  // https://p5js.org/reference/#/p5/colorMode
  colorMode(HSB, 360, 100, 100, 100);

  groundY = height * 0.9;

  setupUI();

  // Start with one fountain near the "ground"
  fountains.push(new Fountain(width / 2, groundY));
}
Line-by-line explanation (6 lines)
const cnv = createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window and stores a reference to it in cnv.
cnv.mousePressed(spawnFountainAtMouse);
Attaches a click handler directly to the canvas element so clicking anywhere on it calls spawnFountainAtMouse(), without accidentally triggering when you click the UI sliders.
colorMode(HSB, 360, 100, 100, 100);
Switches p5's color system to Hue-Saturation-Brightness with ranges 0-360 for hue and 0-100 for the rest, which makes it much easier to shift particle color based on speed.
groundY = height * 0.9;
Places an invisible 'ground line' 90% of the way down the canvas, used later for collision detection.
setupUI();
Calls a helper function that builds the gravity and speed sliders as DOM elements overlaid on the canvas.
fountains.push(new Fountain(width / 2, groundY));
Creates the very first Fountain object centered horizontally at the ground line, so the sketch has something happening immediately on load.

draw()

draw() is p5's animation loop, running ~60 times per second. This sketch uses it to step physics forward, manage three separate arrays of objects, and handle blend modes for visual style.

🔬 This makes splash particles bounce softly off the ground. What happens if you change -0.3 to -0.8 (bouncier) or to 0 (particles just stop dead on impact)?

    if (s.pos.y >= groundY && s.vel.y > 0) {
      s.pos.y = groundY;
      s.vel.y *= -0.3; // small bounce
    }
function draw() {
  // Motion-blur / trail effect: semi-transparent background
  // https://p5js.org/reference/#/p5/background
  background(0, 0, 0, 20);

  // Update parameters from sliders
  gravity = gravitySlider.value();
  baseInitialSpeed = speedSlider.value();
  gravityValueSpan.html(gravity.toFixed(2));
  speedValueSpan.html(baseInitialSpeed.toFixed(1));

  const gravityForce = createVector(0, gravity);

  drawGround();

  // Emit particles from all active fountains
  for (let i = fountains.length - 1; i >= 0; i--) {
    const f = fountains[i];
    f.emit();
    if (f.isDead()) {
      fountains.splice(i, 1);
    }
  }

  // Additive blending for glowing particles
  // https://p5js.org/reference/#/p5/blendMode
  blendMode(ADD);

  // Main fountain particles
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];

    p.applyForce(gravityForce);
    p.update();

    // Collision with ground -> create splash particles
    if (p.pos.y >= groundY && p.vel.y > 0) {
      createSplash(p);
      particles.splice(i, 1);
      continue;
    }

    p.display();

    if (p.isDead()) {
      particles.splice(i, 1);
    }
  }

  // Splash (secondary) particles
  for (let i = splashes.length - 1; i >= 0; i--) {
    const s = splashes[i];

    s.applyForce(gravityForce);
    s.update();
    s.display();

    // Let splash particles bounce very slightly off the ground
    if (s.pos.y >= groundY && s.vel.y > 0) {
      s.pos.y = groundY;
      s.vel.y *= -0.3; // small bounce
    }

    if (s.isDead()) {
      splashes.splice(i, 1);
    }
  }

  // Return to normal blending (for anything else that might be drawn)
  blendMode(BLEND);
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

for-loop Fountain Update Loop for (let i = fountains.length - 1; i >= 0; i--) {

Calls emit() on every active fountain and removes fountains whose lifeFrames have run out.

for-loop Main Particle Loop for (let i = particles.length - 1; i >= 0; i--) {

Applies gravity, updates position, checks for ground collisions, draws, and removes dead particles.

conditional Ground Collision Check if (p.pos.y >= groundY && p.vel.y > 0) {

Detects when a rising-then-falling particle reaches the ground line and converts it into a splash burst instead of drawing it.

for-loop Splash Particle Loop for (let i = splashes.length - 1; i >= 0; i--) {

Updates and draws secondary splash particles and gives them a small bounce off the ground.

background(0, 0, 0, 20);
Draws a nearly-transparent black rectangle over the whole canvas instead of fully clearing it, which leaves faint trails behind moving particles.
gravity = gravitySlider.value();
Reads the current position of the gravity slider each frame so dragging it live-updates the physics.
const gravityForce = createVector(0, gravity);
Builds a downward-pointing vector representing gravity, ready to be applied to every particle this frame.
for (let i = fountains.length - 1; i >= 0; i--) {
Loops backward through the fountains array so that removing an item with splice() doesn't skip the next one.
blendMode(ADD);
Switches to additive color blending so overlapping particle glows brighten each other, creating a neon look.
if (p.pos.y >= groundY && p.vel.y > 0) {
Checks whether the particle has reached the ground AND is moving downward (not still rising), preventing a false trigger while it launches from ground level.
createSplash(p);
Turns the impact into a burst of smaller splash particles at the point of contact.
s.vel.y *= -0.3; // small bounce
Reverses the splash particle's vertical velocity and shrinks it to 30%, simulating a soft, energy-losing bounce.
blendMode(BLEND);
Restores normal (non-additive) blending at the end of the frame so it doesn't affect anything drawn outside this loop.

windowResized()

windowResized() is a built-in p5 callback that fires automatically whenever the browser window changes size, letting you keep full-window sketches responsive.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  groundY = height * 0.9;
}
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the p5 canvas to match the new browser window dimensions whenever the window is resized.
groundY = height * 0.9;
Recalculates the ground line position so it stays proportionally placed after a resize.

setupUI()

setupUI() shows how p5's DOM functions (createDiv, createSpan, createSlider) let you build ordinary HTML interface elements that live alongside the canvas and can be styled with CSS-like .style() calls.

function setupUI() {
  // Using p5 DOM functions (bundled in core)
  // https://p5js.org/reference/#/p5/createDiv
  const ui = createDiv();
  ui.id('ui');
  ui.style('position', 'fixed');
  ui.style('top', '16px');
  ui.style('left', '16px');
  ui.style('padding', '10px 12px');
  ui.style('border-radius', '8px');
  ui.style('background', 'rgba(0, 0, 0, 0.45)');
  ui.style('color', '#fff');
  ui.style('font-family', 'system-ui, sans-serif');
  ui.style('font-size', '13px');
  ui.style('backdrop-filter', 'blur(8px)');
  ui.style('-webkit-backdrop-filter', 'blur(8px)');
  ui.style('z-index', '10');
  ui.style('user-select', 'none');

  // Gravity row
  const gRow = createDiv();
  gRow.parent(ui);
  gRow.style('margin-bottom', '6px');

  const gLabel = createSpan('Gravity:');
  gLabel.parent(gRow);
  gLabel.style('margin-right', '6px');

  // https://p5js.org/reference/#/p5/createSlider
  gravitySlider = createSlider(0, 0.6, gravity, 0.01);
  gravitySlider.parent(gRow);
  gravitySlider.style('width', '120px');
  gravitySlider.style('vertical-align', 'middle');

  gravityValueSpan = createSpan(gravity.toFixed(2));
  gravityValueSpan.parent(gRow);
  gravityValueSpan.style('margin-left', '6px');
  gravityValueSpan.style('opacity', '0.8');

  // Speed / initial velocity row
  const sRow = createDiv();
  sRow.parent(ui);

  const sLabel = createSpan('Initial speed:');
  sLabel.parent(sRow);
  sLabel.style('margin-right', '6px');

  speedSlider = createSlider(2, 20, baseInitialSpeed, 0.1);
  speedSlider.parent(sRow);
  speedSlider.style('width', '120px');
  speedSlider.style('vertical-align', 'middle');

  speedValueSpan = createSpan(baseInitialSpeed.toFixed(1));
  speedValueSpan.parent(sRow);
  speedValueSpan.style('margin-left', '6px');
  speedValueSpan.style('opacity', '0.8');
}
Line-by-line explanation (5 lines)
const ui = createDiv();
Creates a new HTML <div> element that will hold all the slider controls, using p5's DOM API.
ui.style('background', 'rgba(0, 0, 0, 0.45)');
Gives the UI panel a semi-transparent dark background so it's readable over the animated canvas.
gravitySlider = createSlider(0, 0.6, gravity, 0.01);
Creates an HTML range slider for gravity with min 0, max 0.6, starting value equal to the current gravity, and a step of 0.01.
gravityValueSpan = createSpan(gravity.toFixed(2));
Creates a small text label next to the slider showing the current numeric gravity value, formatted to 2 decimal places.
speedSlider = createSlider(2, 20, baseInitialSpeed, 0.1);
Creates the second slider controlling how fast particles launch, ranging from 2 to 20.

Fountain (class)

The Fountain class is a spawner/emitter object - a common particle-system pattern where one lightweight object is responsible for periodically creating many short-lived Particle objects until its own lifespan ends.

🔬 The angle is randomized across the full upward hemisphere (-PI to 0). What happens if you narrow it to random(-PI * 0.6, -PI * 0.4) so the fountain shoots almost straight up?

    for (let i = 0; i < this.emissionRate; i++) {
      // Random angle in upward hemisphere
      const angle = random(-PI, 0);
      const speed = baseInitialSpeed * random(0.6, 1.2);
class Fountain {
  constructor(x, y) {
    this.origin = createVector(x, y);
    this.emissionRate = 6;        // particles per frame
    this.lifeFrames = 60 * 4;     // fountain emits for ~4 seconds
  }

  emit() {
    if (this.lifeFrames <= 0) return;

    for (let i = 0; i < this.emissionRate; i++) {
      // Random angle in upward hemisphere
      const angle = random(-PI, 0);
      const speed = baseInitialSpeed * random(0.6, 1.2);

      // Narrow cone: reduce horizontal component slightly
      const vx = cos(angle) * speed * 0.5;
      const vy = sin(angle) * speed;

      particles.push(new Particle(this.origin.x, this.origin.y, vx, vy, false));
    }

    this.lifeFrames--;
  }

  isDead() {
    return this.lifeFrames <= 0;
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

conditional Lifetime Guard if (this.lifeFrames <= 0) return;

Stops the fountain from emitting any more particles once its lifespan has expired.

for-loop Particle Spawn Loop for (let i = 0; i < this.emissionRate; i++) {

Spawns emissionRate new Particle objects each frame with randomized upward angle and speed.

this.origin = createVector(x, y);
Stores the fountain's fixed spawn point as a p5.Vector so every particle it emits starts from the same location.
this.emissionRate = 6; // particles per frame
Sets how many particles are created every single frame while the fountain is alive.
this.lifeFrames = 60 * 4; // fountain emits for ~4 seconds
Gives the fountain a countdown timer (in frames) of roughly 4 seconds at 60fps before it stops emitting and is removed.
const angle = random(-PI, 0);
Picks a random angle between straight left (-PI radians) and straight right (0), which in screen coordinates covers the whole upward hemisphere.
const speed = baseInitialSpeed * random(0.6, 1.2);
Randomizes each particle's launch speed around the slider-controlled base speed, so particles don't all move identically.
const vx = cos(angle) * speed * 0.5;
Converts the angle into a horizontal velocity component, shrinking it by half to keep the fountain narrow rather than wide.
particles.push(new Particle(this.origin.x, this.origin.y, vx, vy, false));
Creates a brand-new fountain particle (isSplash = false) and adds it to the global particles array so draw() will update and display it.
this.lifeFrames--;
Counts down the fountain's remaining lifetime by one frame each time emit() runs.

Particle (class)

Particle is the workhorse class of the system - every dot you see, whether from a fountain or a splash, is one instance of this class. It bundles physics (vectors), lifespan management, and its own drawing logic together, a classic object-oriented approach to particle systems.

🔬 This loop draws 3 glow layers. What happens visually if you start the loop at i = 6 instead of i = 3, drawing six layers of glow?

    for (let i = 3; i >= 1; i--) {
      const r = this.size * i;
      const a = baseAlpha / (i * i); // fade outer halos
      fill(hue, 80, brightness, a);
      circle(this.pos.x, this.pos.y, r);
    }
class Particle {
  constructor(x, y, vx, vy, isSplash) {
    // https://p5js.org/reference/#/p5.Vector
    this.pos = createVector(x, y);
    this.vel = createVector(vx, vy);
    this.acc = createVector(0, 0);
    this.isSplash = isSplash;

    this.size = isSplash ? random(2, 5) : random(4, 8);
    this.lifespan = isSplash ? 180 : 255; // frames
  }

  applyForce(force) {
    this.acc.add(force);
  }

  update() {
    this.vel.add(this.acc);
    this.pos.add(this.vel);
    this.acc.mult(0);

    this.lifespan -= this.isSplash ? 5 : 3;
  }

  isDead() {
    return this.lifespan <= 0 ||
           this.pos.y < -50 ||
           this.pos.x < -50 ||
           this.pos.x > width + 50;
  }

  display() {
    const speed = this.vel.mag();
    const maxSpeed = 18;

    // Map speed to color (slow = blue, fast = pink/white)
    const hue = map(speed, 0, maxSpeed, 180, 330, true);
    const brightness = map(speed, 0, maxSpeed, 60, 100, true);
    const baseAlpha = map(this.lifespan, 0, 255, 0, 100, true);

    noStroke();

    // Draw multiple circles for a soft glow
    // https://p5js.org/reference/#/p5/circle
    for (let i = 3; i >= 1; i--) {
      const r = this.size * i;
      const a = baseAlpha / (i * i); // fade outer halos
      fill(hue, 80, brightness, a);
      circle(this.pos.x, this.pos.y, r);
    }
  }
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

for-loop Layered Glow Circles for (let i = 3; i >= 1; i--) {

Draws three overlapping circles of decreasing opacity and increasing radius to fake a soft glow halo around each particle.

this.pos = createVector(x, y);
Stores the particle's current position as a 2D vector.
this.vel = createVector(vx, vy);
Stores the particle's velocity (speed and direction) as a vector, set once at creation from the launch angle and speed.
this.acc = createVector(0, 0);
Stores acceleration, starting at zero; forces like gravity get added to this each frame.
this.size = isSplash ? random(2, 5) : random(4, 8);
Gives splash particles a smaller random size than main fountain particles, using a ternary to pick the range.
this.vel.add(this.acc);
Applies the accumulated acceleration (like gravity) to velocity - this is the core of the physics simulation, following v = v + a.
this.pos.add(this.vel);
Moves the particle by its current velocity, updating its on-screen position.
this.acc.mult(0);
Resets acceleration to zero after it's been used, so forces don't accidentally stack up across frames.
this.lifespan -= this.isSplash ? 5 : 3;
Shrinks the particle's remaining life each frame; splash particles fade faster (5) than main particles (3).
const hue = map(speed, 0, maxSpeed, 180, 330, true);
Converts the particle's current speed into a hue value between 180 (cyan/blue) and 330 (pink), so fast particles look hot and slow ones look cool.
for (let i = 3; i >= 1; i--) {
Loops 3 times to draw three stacked circles of shrinking size and fading opacity, creating a soft glow effect instead of a hard-edged dot.

drawGround()

drawGround() is a small drawing helper that keeps setup/draw code clean by isolating all the ground-rendering logic in one place - a good example of breaking a sketch into focused helper functions.

function drawGround() {
  // Soft ground area
  noStroke();
  fill(210, 20, 15, 40);
  rect(0, groundY, width, height - groundY);

  // Ground line
  stroke(210, 10, 60, 70);
  strokeWeight(2);
  line(0, groundY, width, groundY);
}
Line-by-line explanation (3 lines)
fill(210, 20, 15, 40);
Sets a dim bluish, low-brightness, semi-transparent fill (in HSB) for the ground area rectangle.
rect(0, groundY, width, height - groundY);
Draws a rectangle spanning the full width of the canvas from the ground line down to the bottom edge, suggesting a floor.
line(0, groundY, width, groundY);
Draws a horizontal line across the canvas at groundY to visually mark where particles will splash.

createSplash()

createSplash() demonstrates how one event (a collision) can programmatically spawn a whole new batch of objects, a core technique for particle effects like explosions, sparks, or splashes in any creative coding project.

🔬 What happens if you increase the upper bound of random(0.2, 0.7) to random(0.2, 1.5), letting some splash particles fly faster than the original impact?

  for (let i = 0; i < count; i++) {
    // Splash angles mostly upwards & sideways
    const angle = random(-PI, 0);
    const speed = impactSpeed * random(0.2, 0.7);
function createSplash(particle) {
  const count = floor(random(8, 16));
  const impactSpeed = particle.vel.mag();

  for (let i = 0; i < count; i++) {
    // Splash angles mostly upwards & sideways
    const angle = random(-PI, 0);
    const speed = impactSpeed * random(0.2, 0.7);

    const vx = cos(angle) * speed;
    const vy = sin(angle) * speed * 0.7;

    splashes.push(
      new Particle(
        particle.pos.x,
        groundY - 1, // slightly above ground
        vx,
        vy,
        true
      )
    );
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

for-loop Splash Burst Loop for (let i = 0; i < count; i++) {

Creates a random number of small splash Particle objects at the impact point, each with velocity derived from the original particle's impact speed.

const count = floor(random(8, 16));
Randomly picks a whole number between 8 and 15 splash particles to create for this impact, so every splash looks slightly different.
const impactSpeed = particle.vel.mag();
Measures how fast the original particle was moving when it hit the ground, using .mag() to get the vector's length - faster impacts make bigger splashes.
const speed = impactSpeed * random(0.2, 0.7);
Gives each splash particle a fraction of the original impact speed, so splash particles are gentler than the particle that caused them.
groundY - 1, // slightly above ground
Spawns splash particles just 1 pixel above the ground line so they don't immediately re-trigger a ground collision.

spawnFountainAtMouse()

This function is registered as the canvas's mousePressed callback in setup(), showing how p5's event system connects user interaction (clicking) to creating new objects in a running simulation.

function spawnFountainAtMouse() {
  fountains.push(new Fountain(mouseX, mouseY));
}
Line-by-line explanation (1 lines)
fountains.push(new Fountain(mouseX, mouseY));
Creates a brand-new Fountain object at the current mouse position and adds it to the fountains array so draw() will start emitting particles from it.

📦 Key Variables

fountains array

Holds all active Fountain objects, each of which periodically emits new particles until its lifespan ends.

let fountains = [];
particles array

Holds all active main fountain Particle objects currently flying through the air.

let particles = [];
splashes array

Holds all secondary splash Particle objects created when a main particle hits the ground.

let splashes = [];
gravity number

The downward acceleration applied to every particle each frame, controlled live by the gravity slider.

let gravity = 0.18;
baseInitialSpeed number

The base launch speed used when fountains create new particles, controlled live by the speed slider.

let baseInitialSpeed = 8.0;
gravitySlider object

Reference to the HTML range slider DOM element used to adjust gravity in real time.

gravitySlider = createSlider(0, 0.6, gravity, 0.01);
speedSlider object

Reference to the HTML range slider DOM element used to adjust launch speed in real time.

speedSlider = createSlider(2, 20, baseInitialSpeed, 0.1);
gravityValueSpan object

A text element displaying the current numeric gravity value next to its slider.

gravityValueSpan = createSpan(gravity.toFixed(2));
speedValueSpan object

A text element displaying the current numeric speed value next to its slider.

speedValueSpan = createSpan(baseInitialSpeed.toFixed(1));
groundY number

The y-coordinate representing the ground line, used for collision detection and drawing the ground.

groundY = height * 0.9;

🔧 Potential Improvements (3)

Here are some ways this code could be enhanced:

BUG draw() ground collision check

The collision test `p.pos.y >= groundY && p.vel.y > 0` only checks position after the particle has already moved, so at high speeds (large gravity or velocity) a particle can jump past groundY in one frame without ever satisfying the check exactly, or overshoot visibly before splashing (tunneling).

💡 Clamp the position to groundY before spawning the splash, e.g. `p.pos.y = groundY;` right before calling createSplash(p), so the splash always originates exactly at the ground line regardless of step size.

PERFORMANCE Particle.display()

Every particle draws 3 separate circle() calls with a fresh fill() each frame, and with many particles/splashes on screen this multiplies draw calls quickly.

💡 Consider drawing a single circle per particle using a radial gradient sprite (via a pre-rendered p5.Graphics image) drawn with image(), which can look just as glowy with a single draw call instead of three.

STYLE Particle class

Magic numbers like 3 and 5 (lifespan decay), 2-5 and 4-8 (size ranges), and 60*4 (fountain life) are scattered through the code without explanation of why those specific values were chosen.

💡 Pull these into named constants near the top of the file (e.g. SPLASH_DECAY = 5, PARTICLE_DECAY = 3) so their purpose is clear and they're easy to tune from one place.

🔄 Code Flow

Code flow showing setup, draw, windowresized, setupui, fountain, particle, drawground, createsplash, spawnfountainatmouse

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> fountainsloop[fountains-loop] draw --> particlesloop[particles-loop] draw --> splashesloop[splashes-loop] fountainsloop --> emitguard[emit-guard] emitguard -->|if not expired| emitloop[emit-loop] emitguard -->|if expired| endfountain[End Fountain] emitloop --> particle[Particle] emitloop -->|spawns| particle particlesloop --> groundcollision[ground-collision] groundcollision -->|if collision| createsplash[createsplash] groundcollision -->|if no collision| drawparticle[Draw Particle] drawparticle --> glowloop[glow-loop] glowloop --> draw splashesloop --> splashspawnloop[splash-spawn-loop] splashspawnloop -->|creates| particle click setup href "#fn-setup" click draw href "#fn-draw" click fountainsloop href "#sub-fountains-loop" click emitguard href "#sub-emit-guard" click emitloop href "#sub-emit-loop" click particlesloop href "#sub-particles-loop" click groundcollision href "#sub-ground-collision" click createsplash href "#fn-createsplash" click drawparticle href "#sub-draw-particle" click glowloop href "#sub-glow-loop" click splashesloop href "#sub-splashes-loop" click splashspawnloop href "#sub-splash-spawn-loop"

❓ Frequently Asked Questions

What visual effects can I expect from the AI Particle Fountain sketch?

The sketch creates stunning visual displays of particles shooting upward like fountains, with beautiful color effects and motion blur that enhance the overall aesthetic.

How can I interact with the AI Particle Fountain sketch?

Users can click anywhere on the canvas to spawn new particle fountains, and adjust the gravity and speed of the particles using sliders.

What creative coding concepts does the AI Particle Fountain sketch demonstrate?

This sketch showcases a physics-based particle system, utilizing concepts like force application, collision detection, and color blending to create dynamic visual effects.

Preview

AI Particle Fountain - Physics Particle System Click to spawn beautiful particle fountains! Particl - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Particle Fountain - Physics Particle System Click to spawn beautiful particle fountains! Particl - Code flow showing setup, draw, windowresized, setupui, fountain, particle, drawground, createsplash, spawnfountainatmouse
Code Flow Diagram