Concept:
For this project, I decided to make a visualization inspired by tension and release with an interactive element as the mouse hovers around the canvas. Initially, the flock is strictly constructed around an attractor in a circular shape, but over time, it changes and moves around the canvas. The mouse interaction enables the Boids to transform shape and size, adding layers to the sketch. The shape of the Boide is inspired by kite-like crystal shapes in the shade of light transparent blue symbolizing shattered glass. Resembling the sketch this way makes sense to me because it shows fragility and the Boids collective movement while remaining individual. This mirrors the sensitivity of how one Boids movement influences others.
Highlight of Code:
For this project, I reviewed the presentation and watched the videos to understand the concept of flocking. I wanted to bring into the sketch things we learned prior to the class, and I decided that having an attractor would be interesting. I initially started playing around with the code we did in class. I played a lot with different classes and ways to make it work. I added three main classes: an attractor, a Boid, and a flock. Even though I had so much debugging to do, I think I am close enough to the visualization I had in my head.
The attractor class pushed the Boids towards it through a force similar to orbiting. It was hard to figure out how to make it work, but as I played with the numbers, it started making sense. The orbiting force is simply rotating the force by 2. The Boid class is affected by flocking and attraction. This helps the body move in groups and respond to the attraction force. The methods used for the Boid class are froce, flock, attraction, update, borders, and the show functions.
for (let i = 0; i < 400; i++) { //changing this part effects how the overall visulaization is let angle = map(i, 0, 400, 0, TWO_PI); let boid = new Boid( width/1.7 + radious * cos(angle), height/1.7 + radious * sin(angle) ); flock.addBoid(boid); }
The above code is the main effector of the visualization. It took me some time to create a visualization I though was interesting, and it was inspired by the midterm for this semester.
class Boid { constructor(x, y) { this.acceleration = createVector(0, 0); this.velocity = createVector(random(-0.01, 0.001), random(-0.01, 0.01)); this.position = createVector(x, y); this.r = 3; //mass this.mass = 2; // Maximum speed this.maxSpeed = 0.7; // Maximum steering force this.maxForce = 5; } applyForce(force) { let f = p5.Vector.div(force, this.mass); //changed how it moves looks nice this.acceleration.sub(f); this.acceleration.mult(10); this.acceleration.mult(f); } run(boids, attractor) { this.flock(boids); this.applyAttraction(attractor); this.update(); this.borders(); this.show(); } applyAttraction(attractor) { //pull boids attractor.attract(this); } // accumulate acceleration each time based on three rules flock(boids) { let sep = this.separate(boids); // Separation let ali = this.align(boids); // Alignment let coh = this.cohere(boids); // Cohesion // Arbitrarily weight these forces sep.mult(1); ali.mult(1); coh.mult(1); // Add the force vectors to acceleration this.applyForce(sep); this.applyForce(ali); this.applyForce(coh); } // update location 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); } // calculate and apply 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); // 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; } show() { // draw a kite shape let angle = this.velocity.heading(); fill(random(90, 127), random(150, 200), random(180, 255), 150); stroke(random(100, 127), random(100, 200), random(200, 255)); push(); translate(this.position.x, this.position.y); rotate(angle); let freqX = map(mouseX, 0, width, 1, 15); let freqY = map(mouseY, 0, height, 1, 15); beginShape(); let x = this.r * cos(10 * this.r) * freqX; let y = this.r * cos(10 * this.r) * freqY; //crystal like shape //right vertex(x * 2, 0); //top right vertex(x * 0.7, -y); //top left vertex(-x * 0.7, -y); //left vertex(-x * 2, 0); //bottom vertex(0, y); endShape(CLOSE); pop(); } // wraparound borders() { 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; } // separation itchecks for nearby boids and steers away separate(boids) { let desiredSeparation = 15; 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); // ff 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(); // weight by distance diff.div(d); 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; } // Alignment // For every nearby boid in the system, calculate the average velocity align(boids) { let neighborDistance = 40; 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); } } // Cohesion // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location cohere(boids) { let neighborDistance = 40; 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.position); // Add location count++; } } if (count > 0) { sum.div(count); return this.seek(sum); // Steer towards the location } else { return createVector(0, 0); } } }
The Boid class was the most complicated. I had to do a lot of debugging to add the attractor into action so that the Boids are affected by it.
Embedded Sketch:
Future work:
I am satisfied with how the sketch turned out to be. Things I want to experiment further with is how the flock would move around the canvas by creating a path for them to create different shapes and interactions that are strictly influenced by tension and release. Further, I think having an audio sync into the work is also interesting to see. The audio could perhaps be in relation to the acceleration and speed of the flock’s movement. Further, if this could translate into an interactive project, perhaps I can add a motion sensor so that the audience can influence the behavior of the flock through that data.
Resources:
The Coding Train. “Coding Challenge 124: Flocking Simulation.” YouTube, 11 Dec. 2018, www.youtube.com/watch?v=mhjuuHl6qHM.
Arthur Facredyn. “Path Finding in p5.js # 1.” YouTube, 11 June 2018, www.youtube.com/watch?v=Ohcrstxcci4.
Murmuration – Robert Hodgin. roberthodgin.com/project/murmuration.
5. Autonomous Agents. nature-of-code-2nd-edition.netlify.app/autonomous-agents/#vehicles-and-steering.