diff --git a/examples/curated/43_Flocking.js b/examples/curated/43_Flocking.js index 832b1d60..f3c87aec 100644 --- a/examples/curated/43_Flocking.js +++ b/examples/curated/43_Flocking.js @@ -1,230 +1,258 @@ -/* +/** * @name Flocking - * @arialabel Groups of little grey triangles moving across a darker grey background - * @description Demonstration of Craig Reynolds' "Flocking" behavior. - * See: http://www.red3d.com/cwr/ - * Rules: Cohesion, Separation, Alignment - * (from natureofcode.com). - * Drag mouse to add boids into the system. + * @description Demonstration of flocking behavior. + * Full discussion of the implementation can be found in the + * + * Nature of Code + * book by Daniel Shiffman. The simulation is based on the research of + * Craig Reynolds, who + * used the term 'boid' to represent a bird-like object. */ - let flock; function setup() { createCanvas(640, 360); - createP("Drag the mouse to generate new boids."); + createP('Drag the mouse to generate new boids.'); flock = new Flock(); + // Add an initial set of boids into the system for (let i = 0; i < 100; i++) { - let b = new Boid(width / 2,height / 2); + let b = new Boid(width / 2, height / 2); flock.addBoid(b); } + + describe( + 'A group of bird-like objects, represented by triangles, moving across the canvas, modeling flocking behavior.' + ); } function draw() { - background(51); + background(0); flock.run(); } -// Add a new boid into the System +// On mouse drag, add a new boid to the flock function mouseDragged() { flock.addBoid(new Boid(mouseX, mouseY)); } -// The Nature of Code -// Daniel Shiffman -// http://natureofcode.com +// Flock class to manage the array of all the boids +class Flock { + constructor() { + // Initialize the array of boids + this.boids = []; + } -// Flock object -// Does very little, simply manages the array of all the boids + run() { + for (let boid of this.boids) { + // Pass the entire list of boids to each boid individually + boid.run(this.boids); + } + } -function Flock() { - // An array for all the boids - this.boids = []; // Initialize the array + addBoid(b) { + this.boids.push(b); + } } -Flock.prototype.run = function() { - for (let i = 0; i < this.boids.length; i++) { - this.boids[i].run(this.boids); // Passing the entire list of boids to each boid individually +class Boid { + constructor(x, y) { + this.acceleration = createVector(0, 0); + this.velocity = createVector(random(-1, 1), random(-1, 1)); + this.position = createVector(x, y); + this.size = 3.0; + + // Maximum speed + this.maxSpeed = 3; + + // Maximum steering force + this.maxForce = 0.05; + colorMode(HSB); + this.color = color(random(256), 255, 255); } -} -Flock.prototype.addBoid = function(b) { - this.boids.push(b); -} + run(boids) { + this.flock(boids); + this.update(); + this.borders(); + this.render(); + } -// The Nature of Code -// Daniel Shiffman -// http://natureofcode.com + applyForce(force) { + // We could add mass here if we want A = F / M + this.acceleration.add(force); + } -// Boid class -// Methods for Separation, Cohesion, Alignment added + // We accumulate a new acceleration each time based on three rules + flock(boids) { + let separation = this.separate(boids); + let alignment = this.align(boids); + let cohesion = this.cohesion(boids); -function Boid(x, y) { - this.acceleration = createVector(0, 0); - this.velocity = createVector(random(-1, 1), random(-1, 1)); - this.position = createVector(x, y); - this.r = 3.0; - this.maxspeed = 3; // Maximum speed - this.maxforce = 0.05; // Maximum steering force -} + // Arbitrarily weight these forces + separation.mult(1.5); + alignment.mult(1.0); + cohesion.mult(1.0); -Boid.prototype.run = function(boids) { - this.flock(boids); - this.update(); - this.borders(); - this.render(); -} + // Add the force vectors to acceleration + this.applyForce(separation); + this.applyForce(alignment); + this.applyForce(cohesion); + } -Boid.prototype.applyForce = function(force) { - // We could add mass here if we want A = F / M - this.acceleration.add(force); -} + // Method to update location + update() { + // Update velocity + this.velocity.add(this.acceleration); -// We accumulate a new acceleration each time based on three rules -Boid.prototype.flock = function(boids) { - let sep = this.separate(boids); // Separation - let ali = this.align(boids); // Alignment - let coh = this.cohesion(boids); // Cohesion - // Arbitrarily weight these forces - sep.mult(1.5); - ali.mult(1.0); - coh.mult(1.0); - // Add the force vectors to acceleration - this.applyForce(sep); - this.applyForce(ali); - this.applyForce(coh); -} + // Limit speed + this.velocity.limit(this.maxSpeed); + this.position.add(this.velocity); -// Method to update location -Boid.prototype.update = function() { - // Update velocity - this.velocity.add(this.acceleration); - // Limit speed - this.velocity.limit(this.maxspeed); - this.position.add(this.velocity); - // Reset accelertion to 0 each cycle - this.acceleration.mult(0); -} + // Reset acceleration to 0 each cycle + this.acceleration.mult(0); + } -// A method that calculates and applies a steering force towards a target -// STEER = DESIRED MINUS VELOCITY -Boid.prototype.seek = function(target) { - let desired = p5.Vector.sub(target,this.position); // A vector pointing from the location to the target - // Normalize desired and scale to maximum speed - desired.normalize(); - desired.mult(this.maxspeed); - // Steering = Desired minus Velocity - let steer = p5.Vector.sub(desired,this.velocity); - steer.limit(this.maxforce); // Limit to maximum steering force - return steer; -} + // A method that calculates and applies a steering force towards a target + // STEER = DESIRED MINUS VELOCITY + seek(target) { + // A vector pointing from the location to the target + let desired = p5.Vector.sub(target, this.position); -Boid.prototype.render = function() { - // Draw a triangle rotated in the direction of velocity - let theta = this.velocity.heading() + radians(90); - fill(127); - stroke(200); - push(); - translate(this.position.x, this.position.y); - rotate(theta); - beginShape(); - vertex(0, -this.r * 2); - vertex(-this.r, this.r * 2); - vertex(this.r, this.r * 2); - endShape(CLOSE); - pop(); -} + // Normalize desired and scale to maximum speed + desired.normalize(); + desired.mult(this.maxSpeed); -// Wraparound -Boid.prototype.borders = function() { - if (this.position.x < -this.r) this.position.x = width + this.r; - if (this.position.y < -this.r) this.position.y = height + this.r; - if (this.position.x > width + this.r) this.position.x = -this.r; - if (this.position.y > height + this.r) this.position.y = -this.r; -} + // Steering = Desired minus Velocity + let steer = p5.Vector.sub(desired, this.velocity); -// Separation -// Method checks for nearby boids and steers away -Boid.prototype.separate = function(boids) { - let desiredseparation = 25.0; - let steer = createVector(0, 0); - let count = 0; - // For every boid in the system, check if it's too close - for (let i = 0; i < boids.length; i++) { - let d = p5.Vector.dist(this.position,boids[i].position); - // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) - if ((d > 0) && (d < desiredseparation)) { - // Calculate vector pointing away from neighbor - let diff = p5.Vector.sub(this.position, boids[i].position); - diff.normalize(); - diff.div(d); // Weight by distance - steer.add(diff); - count++; // Keep track of how many - } - } - // Average -- divide by how many - if (count > 0) { - steer.div(count); + // Limit to maximum steering force + steer.limit(this.maxForce); + return steer; } - // As long as the vector is greater than 0 - if (steer.mag() > 0) { - // Implement Reynolds: Steering = Desired - Velocity - steer.normalize(); - steer.mult(this.maxspeed); - steer.sub(this.velocity); - steer.limit(this.maxforce); + render() { + // Draw a triangle rotated in the direction of velocity + let theta = this.velocity.heading() + radians(90); + fill(this.color); + stroke(255); + push(); + translate(this.position.x, this.position.y); + rotate(theta); + beginShape(); + vertex(0, -this.size * 2); + vertex(-this.size, this.size * 2); + vertex(this.size, this.size * 2); + endShape(CLOSE); + pop(); } - return steer; -} -// Alignment -// For every nearby boid in the system, calculate the average velocity -Boid.prototype.align = function(boids) { - let neighbordist = 50; - let sum = createVector(0,0); - let count = 0; - for (let i = 0; i < boids.length; i++) { - let d = p5.Vector.dist(this.position,boids[i].position); - if ((d > 0) && (d < neighbordist)) { - sum.add(boids[i].velocity); - count++; + // Wraparound + borders() { + if (this.position.x < -this.size) { + this.position.x = width + this.size; + } + + if (this.position.y < -this.size) { + this.position.y = height + this.size; + } + + if (this.position.x > width + this.size) { + this.position.x = -this.size; + } + + if (this.position.y > height + this.size) { + this.position.y = -this.size; } } - if (count > 0) { - sum.div(count); - sum.normalize(); - sum.mult(this.maxspeed); - let steer = p5.Vector.sub(sum, this.velocity); - steer.limit(this.maxforce); + + // Separation + // Method checks for nearby boids and steers away + separate(boids) { + let desiredSeparation = 25.0; + let steer = createVector(0, 0); + let count = 0; + + // For every boid in the system, check if it's too close + for (let boid of boids) { + let distanceToNeighbor = p5.Vector.dist(this.position, boid.position); + + // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) + if (distanceToNeighbor > 0 && distanceToNeighbor < desiredSeparation) { + // Calculate vector pointing away from neighbor + let diff = p5.Vector.sub(this.position, boid.position); + diff.normalize(); + + // Scale by distance + diff.div(distanceToNeighbor); + steer.add(diff); + + // Keep track of how many + count++; + } + } + + // Average -- divide by how many + if (count > 0) { + steer.div(count); + } + + // As long as the vector is greater than 0 + if (steer.mag() > 0) { + // Implement Reynolds: Steering = Desired - Velocity + steer.normalize(); + steer.mult(this.maxSpeed); + steer.sub(this.velocity); + steer.limit(this.maxForce); + } return steer; - } else { - return createVector(0, 0); } -} -// Cohesion -// For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location -Boid.prototype.cohesion = function(boids) { - let neighbordist = 50; - let sum = createVector(0, 0); // Start with empty vector to accumulate all locations - let count = 0; - for (let i = 0; i < boids.length; i++) { - let d = p5.Vector.dist(this.position,boids[i].position); - if ((d > 0) && (d < neighbordist)) { - sum.add(boids[i].position); // Add location - count++; + // Alignment + // For every nearby boid in the system, calculate the average velocity + align(boids) { + let neighborDistance = 50; + let sum = createVector(0, 0); + let count = 0; + for (let i = 0; i < boids.length; i++) { + let d = p5.Vector.dist(this.position, boids[i].position); + if (d > 0 && d < neighborDistance) { + sum.add(boids[i].velocity); + count++; + } + } + if (count > 0) { + sum.div(count); + sum.normalize(); + sum.mult(this.maxSpeed); + let steer = p5.Vector.sub(sum, this.velocity); + steer.limit(this.maxForce); + return steer; + } else { + return createVector(0, 0); } } - if (count > 0) { - sum.div(count); - return this.seek(sum); // Steer towards the location - } else { - return createVector(0, 0); - } -} - + // Cohesion + // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location + cohesion(boids) { + let neighborDistance = 50; + let sum = createVector(0, 0); // Start with empty vector to accumulate all locations + let count = 0; + for (let i = 0; i < boids.length; i++) { + let d = p5.Vector.dist(this.position, boids[i].position); + if (d > 0 && d < neighborDistance) { + sum.add(boids[i].position); // Add location + count++; + } + } + if (count > 0) { + sum.div(count); + return this.seek(sum); // Steer towards the location + } else { + return createVector(0, 0); + } + } +} // class Boid diff --git a/examples/curated/43_Flocking.png b/examples/curated/43_Flocking.png new file mode 100644 index 00000000..ce947e3d Binary files /dev/null and b/examples/curated/43_Flocking.png differ