Marvyn Bailly

PhD Candidate in Applied Mathematics & Scientific Computation

Polygon

Flocking Particles Create Geometric Art

Polygon is a generative art piece that combines flocking behavior with geometric visualization. Particles drift across the screen following Perlin noise and each other, connecting to nearby neighbors to form translucent polygonal shapes that shift and evolve over time.

A special thanks to Daniel Shiffman! Your incredible Youtube channel and book Nature of Code inspired me to create this piece along side many others that can be found on my old website. You taught me how to code! Thank you!

The Concept

The idea combines two classic algorithms: Perlin noise flow fields and Craig Reynolds' flocking behaviors. Particles are pushed by noise while simultaneously trying to stay together, align with neighbors, and avoid collisions.

The visual effect comes from drawing polygons between nearby particles. As the flock moves, the polygons constantly reshape, creating an organic, almost biological appearance.

Particle Properties

Each particle has a set of properties that control its behavior:

Particle Class

class Particle {
    constructor() {
        // Spawn off-screen to the left
        this.pos = createVector(random(-100, -10), random(height));
        this.vel = createVector(0, 0);
        this.acc = createVector(0, 0);
        
        // Random size affects behavior
        this.size = random(minSize, maxSize);
        
        // Larger particles see further
        this.sight = map(this.size, minSize, maxSize, 50, 100);
        
        this.buddies = [];  // Nearby particles
        this.maxSpeed = 1;
        this.maxForce = 0.5;
    }
}

Notice that sight is derived from size - larger particles can "see" further, giving them more connections and making them more influential in the flock.

Perlin Noise Flow

Each particle is pushed by a force derived from Perlin noise. The noise value is sampled based on the particle's horizontal position, creating bands of similar flow:

Noise-Based Movement

function draw() {
    zOff += 0.5;  // Animate noise over time
    
    for (let particle of particles) {
        // Sample noise based on x position
        let xOff = particle.pos.x / width;
        let xNoise = noise(xOff, zOff);
        
        // Apply horizontal force
        particle.applyForce(createVector(xNoise, 0));
    }
}

The zOff variable changes over time, causing the noise field to evolve and preventing particles from settling into static patterns.

Flocking Behavior

The flocking algorithm has three components, each producing a steering force:

1. Separation

Avoid crowding nearby particles. If another particle is within 2 × size, steer away from it. Closer particles create stronger repulsion.

Separation Force

separate(others) {
    var desiredSeparation = this.size * 2;
    var sum = createVector();
    var count = 0;
    
    for (var other of others) {
        var distance = p5.Vector.dist(this.pos, other.pos);
        
        if (distance > 0 && distance < desiredSeparation) {
            // Vector pointing away from neighbor
            var opposite = p5.Vector.sub(this.pos, other.pos);
            opposite.normalize();
            opposite.div(distance);  // Closer = stronger
            sum.add(opposite);
            count++;
        }
    }
    
    // Average and convert to steering force
    if (count > 0) {
        sum.div(count);
        sum.setMag(this.maxSpeed);
        return p5.Vector.sub(sum, this.vel).limit(this.maxForce);
    }
    return createVector();
}

2. Alignment

Steer toward the average heading of neighbors within sight × 2 range. This keeps the flock moving together.

3. Cohesion

Steer toward the average position of nearby particles within a close range. This pulls stragglers back toward the group.

Combining Forces

flock() {
    var sep = this.separate(this.buddies);
    var ali = this.align(this.buddies);
    var coh = this.cohesion(this.buddies);
    
    // Weight the behaviors
    sep.mult(3);    // Strong separation
    ali.mult(4);    // Strongest alignment
    coh.mult(1.5);  // Moderate cohesion
    
    this.applyForce(sep);
    this.applyForce(ali);
    this.applyForce(coh);
}

Mass and Force

Larger particles have more "mass" and respond slower to forces. This is implemented by scaling forces inversely with size:

Mass-Based Force Application

applyForce(force) {
    let f = force.copy();
    
    // Larger particles are heavier (smaller multiplier)
    let m = map(this.size, minSize, maxSize, 0.005, 0.001);
    f.mult(m);
    
    this.acc.add(f);
}

This creates a natural hierarchy where small particles zip around quickly while large particles drift slowly, anchoring the flock.

Polygon Visualization

The visual magic happens in the show() function. Each particle draws a polygon connecting itself to all nearby "buddies":

Drawing Polygons

show() {
    if (this.buddies.length > 0) {
        stroke(220, 220, 220, 10);   // Very transparent
        fill(220, 220, 220, 15);
        
        beginShape();
        vertex(this.pos.x, this.pos.y);
        for (let bud of this.buddies) {
            vertex(bud.pos.x, bud.pos.y);
        }
        vertex(this.pos.x, this.pos.y);  // Close the shape
        endShape();
    }
}

The extremely low alpha values (10-15 out of 255) create the layered, glowing effect as many overlapping polygons accumulate brightness.

Particle Lifecycle

Particles spawn from the left edge and drift rightward. When they exit the screen, they're removed and replaced:

Spawning and Removal

// Remove particles that exit the screen
if (particle.edge()) {
    particles.splice(i, 1);
}

// Maintain population
if (particles.length < population) {
    let dif = population - particles.length;
    for (let i = 0; i < dif; i++) {
        particles.push(new Particle());
    }
}

This creates a continuous flow from left to right, with the flock constantly reforming as particles enter and exit.

Key Parameters

Parameter Value Effect
population 100 Number of particles on screen
minSize / maxSize 5 / 20 Range of particle sizes
sight 50-100 Connection range (based on size)
maxSpeed 1 Maximum velocity
maxForce 0.5 Maximum steering force

Technical Details

View the Art