Flocking#
Adapted from https://p5js.org/examples/hello-p5-flocking.html
Demonstration of Craig Reynolds’ “Flocking” behavior. (Rules: Cohesion, Separation, Alignment.) From natureofcode.com.
from proceso import Sketch
p5 = Sketch()
p5.describe("Twenty gray circles moving like a flock of birds on a gray background.")
boids = []
def setup():
p5.create_canvas(720, 400)
# Add an initial set of boids into the system
for _ in range(20):
boids.append(Boid(p5.random(p5.width), p5.random(p5.height)))
def draw():
p5.background(51)
# Run all the boids
for b in boids:
b.run(boids)
# Boid class
# Methods for Separation, Cohesion, Alignment added
class Boid:
def __init__(self, x, y):
self.acceleration = p5.Vector(0, 0)
self.velocity = p5.Vector.random(2)
self.position = p5.Vector(x, y)
self.r = 3.0
self.maxspeed = 3 # Maximum speed
self.maxforce = 0.05 # Maximum steering force
def run(self, boids):
self.flock(boids)
self.update()
self.borders()
self.render()
# Forces go into acceleration
def apply_force(self, force):
self.acceleration += force
# We accumulate a new acceleration each time based on three rules
def flock(self, boids):
sep = self.separate(boids) # Separation
ali = self.align(boids) # Alignment
coh = self.cohesion(boids) # Cohesion
# Arbitrarily weight these forces
sep *= 2.5
ali *= 1.0
coh *= 1.0
# Add the force vectors to acceleration
self.apply_force(sep)
self.apply_force(ali)
self.apply_force(coh)
# Method to update location
def update(self):
# Update velocity
self.velocity += self.acceleration
# Limit speed
self.velocity.set_limit(self.maxspeed)
self.position += self.velocity
# Reset acceleration to 0 each cycle
self.acceleration *= 0
# A method that calculates and applies a steering force towards a target
# STEER = DESIRED MINUS VELOCITY
def seek(self, target):
desired = (
target - self.position
) # A vector pointing from the location to the target
# Normalize desired and scale to maximum speed
desired.normalize()
desired *= self.maxspeed
# Steering = Desired minus Velocity
steer = desired - self.velocity
steer.set_limit(self.maxforce) # Limit to maximum steering force
return steer
# Draw boid as a circle
def render(self):
p5.fill(127, 127)
p5.stroke(200)
p5.circle(self.position.x, self.position.y, 16)
# Wraparound
def borders(self):
if self.position.x < -self.r:
self.position.x = p5.width + self.r
if self.position.y < -self.r:
self.position.y = p5.height + self.r
if self.position.x > p5.width + self.r:
self.position.x = -self.r
if self.position.y > p5.height + self.r:
self.position.y = -self.r
# Separation
# Method checks for nearby boids and steers away
def separate(self, boids):
desiredseparation = 25.0
steer = p5.Vector(0, 0)
count = 0
# For every boid in the system, check if it's too close
for b in boids:
d = p5.Vector.dist(self.position, b.position)
# If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
if (d > 0) and (d < desiredseparation):
# Calculate vector pointing away from neighbor
diff = self.position - b.position
diff.normalize()
diff /= d # Weight by distance
steer += diff
count += 1 # Keep track of how many
# Average -- divide by how many
if count > 0:
steer /= count
# As long as the vector is greater than 0
if steer.mag > 0:
# Implement Reynolds: Steering = Desired - Velocity
steer.normalize()
steer *= self.maxspeed
steer -= self.velocity
steer.set_limit(self.maxforce)
return steer
# Alignment
# For every nearby boid in the system, calculate the average velocity
def align(self, boids):
neighbordist = 50
sum = p5.Vector(0, 0)
count = 0
for b in boids:
d = p5.Vector.dist(self.position, b.position)
if (d > 0) and (d < neighbordist):
sum += b.velocity
count += 1
if count > 0:
sum /= count
sum.normalize()
sum *= self.maxspeed
steer = sum - self.velocity
steer.set_limit(self.maxforce)
return steer
else:
return p5.Vector(0, 0)
# Cohesion
# For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
def cohesion(self, boids):
neighbordist = 50
sum = p5.Vector(0, 0) # Start with empty vector to accumulate all locations
count = 0
for b in boids:
d = p5.Vector.dist(self.position, b.position)
if (d > 0) and (d < neighbordist):
sum += b.position # Add location
count += 1
if count > 0:
sum /= count
return self.seek(sum) # Steer towards the location
else:
return p5.Vector(0, 0)
p5.run_sketch(setup=setup, draw=draw)