Week 9 | Webbings

Look here hehe
Concept

Initially, I wanted to make a simple tower defense game where players have to block the oncoming flocks of enemies. However, the execution was more difficult than I intended. My next idea then came from the behavior of birds themselves.

Enormous flocks of Snow Geese form during migration, yet thanks to almost a 6th sense individuals do not collide. Photo by Ray Hennessy via Birdshare.

birds in a flock pay close attention to the birds around them—particularly their closest neighbors.

But they might collide with birds from other species or different groups. Hence, my concept becomes as so: What if I make a flock of two or more different bird groups? A) They must group up with the same group B) Avoid different group.

Sketch Prototype

Here, what I did was to represent the boids in two colors by introducing a color property. This then allows me to modify the other behaviors and forces such that they interact with this color property. Particularly, the separation, alignment, and cohesion are affected by this change.

let emoji = this.color === "#C96868" ? "" : "";
separate(boids) {
  let desiredSeparation = 25;
  let steer = createVector(0, 0);
  let count = 0;
  for (let i = 0; i < boids.length; i++) {
    let other = boids[i];
    let d = p5.Vector.dist(this.position, other.position);
    if (d > 0 && d < desiredSeparation && other.color === this.color) {
      // Only same color
      let diff = p5.Vector.sub(this.position, other.position);
      diff.normalize();
      diff.div(d);
      steer.add(diff);
      count++;
    }
  }

But what if, and what happens when we connect a string between each boids? What visuals would it look like, and how would it behave?

I followed this intuition and attempted to add strings that attach to each boid. Of course, they are also vector which means these strings are attracted and affected by external forces.

//Connect
  //Function to connect a string between each boids
  connect(boids) {
    for (let other of boids) {
      let d = p5.Vector.dist(this.position, other.position);
      let maxDistance = 200; // Maximum distance for drawing a line

      // Only draw line if within maxDistance and the other boid is not itself
      if (d > 0 && d < maxDistance) {
        stroke(200, 50);
        strokeWeight(0.5); 
        line(
          this.position.x,
          this.position.y,
          other.position.x,
          other.position.y
        );
      }
    }

Alas, then it suddenly happened. But what a minute–what is this?


Aha! I have just visualized the connection between each boids. Instead of drawing lines on the current information, these lines draw from previous (a frame delay) information of the boids. In essence, they are the boids, birds, and flocks themselves, but represented in lines instead! It seems that the lines are following particularly the white boid but acts just like both red and white boid. I am unsure why.

Challenges & Improvements

➡️During the red-white test, I wanted the boids to return towards the screen again whenever it touches the edge of the canvas. I tried many implementations but found that by multiplying it to -1, or the negative vector does the job best.

➡️For the color checking between each boid, initially I used an if statement. While it worked, I also learned that ternary conditional statement worked better for this implementation where it just pairs up the color code and emoji.

🛠️Performance is quite an issue right now because the sketch is doing three calculations: One red, one white, and for the strings. In the future, I hope to bring more optimizations especially lifetimes for each vector to make sure that they don’t hog the machine.

Resources

All about birds – Cornellab

Ternary Operators – JavaScript

Week 9 Assignment

Concept

In this code, I merged a few ideas to create a cohesive set of actions I envisioned before executing it:

  • Flowers appearing at random places.
  • Flowers opening and closing.
  • A bee hive.
  • Bees follw to track the flowers (only opened ones and avoid the closed one)..
  • After passing all the open flowers bees return back to the hive.
  • Clicking a bee hive that follows the other swarm of bees (boid flock).
Highlight I’m proud of
class BeeBoid {
  constructor(x, y) {
    this.position = createVector(x, y);
    this.velocity = createVector(random(-1, 1), random(-1, 1));
    this.acceleration = createVector(0, 0);
    this.maxForce = 0.05; // Maximum steering force
    this.maxSpeed = 3; // Maximum speed
    this.size = 15; // Size of the bee body
    this.wingSize = 8; // Size of the wings
    this.xmove = random(-0.5, 0.5); // Horizontal movement speed
    this.ymove = random(-0.5, 0.5); // Vertical movement speed
  }

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

  seek(target) {
    let desired = p5.Vector.sub(target, this.position);
    desired.setMag(this.maxSpeed);
    let steer = p5.Vector.sub(desired, this.velocity);
    steer.limit(this.maxForce);
    this.applyForce(steer);
  }

  update() {
    this.velocity.add(this.acceleration);
    this.velocity.limit(this.maxSpeed);
    this.position.add(this.velocity);
    this.acceleration.mult(0); // Reset acceleration
  }

  borders() {
    if (this.position.x > width) this.position.x = 0;
    else if (this.position.x < 0) this.position.x = width;
    if (this.position.y > height) this.position.y = 0;
    else if (this.position.y < 0) this.position.y = height;
  }

  display() {
    // Draw wings
    ellipseMode(RADIUS);
    noStroke();
    fill(171, 240, 255);
    // Left wings
    ellipse(this.position.x - 10, this.position.y - 5, this.wingSize, this.wingSize);
    ellipse(this.position.x - 10, this.position.y + 5, this.wingSize, this.wingSize);
    // Right wings
    ellipse(this.position.x + 10, this.position.y - 5, this.wingSize, this.wingSize);
    ellipse(this.position.x + 10, this.position.y + 5, this.wingSize, this.wingSize);

    // Draw body
    fill(255, 244, 61);
    angleMode(DEGREES);
    ellipse(this.position.x, this.position.y, this.size, this.size * 1.5);
    stroke(0);
    strokeWeight(5);
    line(this.position.x - 10, this.position.y - 5, this.position.x + 10, this.position.y - 5);
    line(this.position.x - 10, this.position.y + 5, this.position.x + 10, this.position.y + 5);
  }

  move() {
    this.position.x += this.xmove;
    this.position.y += this.ymove;

    this.barrier();
  }

  barrier() {
    if (this.position.x >= width || this.position.x <= 0) {
      this.xmove = -this.xmove;
    }
    if (this.position.y >= height || this.position.y <= 0) {
      this.ymove = -this.ymove;
    }
  }
}
class Flock {
  constructor() {
    this.boids = [];
  }

  addBoid(boid) {
    this.boids.push(boid);
  }

  run() {
    for (let boid of this.boids) {
      let closestFlower = null;
      let closestDist = Infinity;

      // Check for the closest flower
      for (let flower of flowerArray) {
        if (flower.isAlive) {
          let d = p5.Vector.dist(boid.position, createVector(flower.centreX, flower.centreY));
          if (d < closestDist) {
            closestDist = d;
            closestFlower = flower;
          }
        }
      }

      if (closestFlower) {
        boid.seek(createVector(closestFlower.centreX, closestFlower.centreY));
      }
      
      boid.update();
      boid.borders();
boid.display();    }
  }
}
  • Arrays are used to store flowers, petals, and stem dots, allowing dynamic addition, deletion, and access to individual elements for updating their states.
  • For loops iterate through arrays and draw multiple flowers and petals, while if conditions handle flower removal, withering, and the periodic addition of new flowers.
  • The code uses trigonometric functions like Math.atan2 to calculate angles for petal positioning, creating uniqu-looking curved  flower shapes.
  • The frameCount variable is used to create periodic actions and smooth animations, such as blooming and movement of petals over time.
Embedded sketch

Edit Sketch: https://editor.p5js.org/mariamalkhoori/sketches/exMNOF-7n

Reflection and ideas for future work or improvements
    • I wanted to add sound to this sketch but did not have the time.
    • Playing around to develop some sort of story.
    • Work on better graphics; I wanted the bee wings to flutter and be animated and to create a better-shaped beehive.
    • Create my own unique-looking flowers using trigonometric functions.

Refrences:
https://editor.p5js.org/HelenaCui/sketches/qwgXBW6Da

Week 9: School of Fish

Concept:

For this week’s assignment, my dynamic flocking system is a school of fish. I added some oscillation to their weights by using sin(). I also randomized their maxSpeed and maxForce to make it seem more natural.

.

Code Snippet & Challenges:

function mousePressed() {
  let attractionPoint = createVector(mouseX, mouseY);
  for (let boid of flock.boids) {
    let attraction = p5.Vector.sub(attractionPoint, boid.position);
    attraction.setMag(0.6);
    boid.applyForce(attraction);
  }
  
  for (let i = 0; i < 20; i++) {
    particles.push(new Particle(mouseX, mouseY));
  }
}

In this part of the code, I added a mousePressed() function in which the fish get attracted to wherever the mouse is at the time when it is clicked on the canvas. I also added some food particles for the fish, which is what they attracted to. I would say the most challenging part was figuring out what the formula is for the attraction part.

Future Improvements:

  • I would definitely add a more colorful background and some sounds, for now I wanted to focus on more of the technical aspect of the school of fish.

Week #9 – Following uncertainty

Concept

I had a curious inspiration while I was going in a bus to campus. I was staring at the window, looking silently at the night, and this silent appreciation was then followed by the next music suggestion in my music app: DIIV – Taker.

It was a weirdly melancholic song, the type of beat I like: melancholic, but not to excessive levels to induce sadness, but rather, a peace calmness. And this is the type of feeling I got from listening to this music, calmness. Thus, for this week assignment, I wanted to share what I felt with a music visualization using flock systems.

Embedded sketch

Note: If the p5.js version is slow, it is suggested to try either this version or make a copy of the GitHub’s repo and run it locally on the computer.

Make sure to click on the Canva to start with the visualization!

Full-screen: Full-screen version

Brief explanation of the code

The flock system is divided on multiple parts, which are activated according to the time of the song; it is easily done with the value returned from the function song.currentTime(). Here is a quick example:

Let’s say that I want to generate the wave effect that appears at the beginning, for this, I create the following parameters inside a custom function called startPart(value):

The wave code is located in the second if condition (value == 2).

function startPart(value) {
  if (value == 1) {
    for (let boid of flock) {
      //Reset value.
      boid.velocity.mult(0);
      boid.maxForce = 0.0;
      //Avoid boids from going out from the borders of the screen.
      boid.transparency = 1;
      boid.acceleration.add(random(-2, 2), random(-0.5, 0.5));
      boid.update_values(1); //Updates the boundaries to the first phase values.
      part = 1;
    }
  }

  //THIS IS THE WAVE CODE
  if (value == 2) {
    for (let boid of flock) {
      //Reset value.
      boid.velocity.mult(0);

      //Assign maxForce
      boid.maxForce = 0.01;

      //Avoid boids from going out from the borders of the screen.
      boid.transparency = 10;
      boid.acceleration.add(random(1, 3), random(-1, 1));
      boid.update_values(2); //Updates the boundaries to the first phase values.
      part = 2;
    }
  }

And then, the specific part is called with the following custom function checkAudioTime() on the second 14 of the song:

function checkAudioTime() {
  if (round(audio.currentTime(), 0) == 6) {
    startPart(1);
  }

  if (round(audio.currentTime(), 0) == 14) {
    startPart(2);
  }

It is important to note that checkAudioTime()is called continuously under draw().

Highlight of some code that I am particularly proud of

I am mostly proud of the patterns I created for this visualization. In total, there are 11 variations which are divided between two flocking systems. I will not paste all the variations here, since it is almost 300 lines of code, but I will share three examples of it:

if (value == 9) {
  for (let boid of flock_outside) {
    //Reset value.
    boid.velocity.mult(0);

    //Assign maxForce
    boid.maxForce = 0.01;
    boid.maxSpeed = 1;
    boid.perception_radius = 50;

    //Avoid boids from going out from the borders of the screen.
    boid.transparency = 50;
    boid.acceleration.add(random(-1, 1), random(-1, 1));
    boid.update_values(6); //Updates the boundaries to the first phase values.
    part = 3;
  }
}

if (value == 10) {
  for (let boid of flock_outside) {
    //Reset value.
    boid.velocity.mult(0);
    boid.maxForce = 1;
    boid.transparency = 5;
    boid.acceleration.add(random(0), random(10));
  }

  for (let boid of flock) {
    //Reset value.
    boid.velocity.mult(0);
    boid.maxForce = 1;
    boid.transparency = 5;
    boid.acceleration.add(random(0), random(10));
  }
}

if (value == 11) {
  for (let boid of flock_outside) {
    //Reset value.
    boid.update_values(7);
  }

  for (let boid of flock) {
    //Reset value.
    boid.update_values(7);
  }
  part = 6;
}

Reflection and ideas for future work or improvements

I am mostly proud of this work. It is by far the assignment I have spent the most time on (or probably the second one). It was an enjoyable challenge, which I felt really curious about what kind of results I could get with the flocking systems. Although, I felt that some patterns in the visualization are rather simplistic and should have been reacting to the sound.

Not to imply that I did not try to implement more reactions that are based on the frequencies of the song, but after some attempts, it was best to disregard the idea since it would consume a lot of time.

Used resources

17.11: Sound Visualization: Frequency Analysis with FFT – p5.js Sound Tutorial

Coding Challenge 124: Flocking Simulation

DIIV // Taker (Official Audio)

Underwater Dance: A School of Fish in Motion – Week 9

Concept 

This week’s sketch is all about the movement of a school of fish. I wanted to show how they can swim in different patterns and scatter around, just like real fish do in the ocean. The main idea is to create a fun and lively underwater scene where the fish either swim together in formations or move freely, giving a sense of both community and individuality.

Code Highlight

One cool part of my code is the generateFormationPoints function. This function creates different shapes for the fish to follow, allowing for variety in their movement. Here’s a snippet of how it works:

function generateFormationPoints() {
  targetPoints = []; // Reset the target points array
  let shape = random(['doubleSpiral', 'wave', 'school', 'dna', 'vortex', 'shoal']); // Randomly select a formation shape
  let centerX = width / 2; // Calculate the center x position
  let centerY = height / 2; // Calculate the center y position
  let size = min(width, height) * 0.3; // Determine the size of the formation

  switch(shape) {
    case 'doubleSpiral':
      // Generate points for a double spiral formation
      // Logic for creating double spiral points...
      break;
    case 'wave':
      // Generate points for a wave formation
      // Logic for creating wave points...
      break;
    // Additional cases for other shapes...
  }
}

In this function, the fish can follow different patterns, like spirals, waves, or even formations resembling a DNA strand. Each time the function runs, it randomly picks a shape for the fish to follow. This keeps the scene dynamic and adds a layer of excitement to the simulation, as the fish move together in beautiful formations.

Embedded Sketch

Reflection and Future Ideas
I really like how this sketch turned out! The way the fish swim together creates a beautiful underwater scene that feels lively and realistic. The switch between swimming in formation and scattering around adds a fun element to the animation, making it engaging for viewers. I especially enjoyed seeing how the fish react to the different target points and how smoothly they transition between formations.

For the future, I see many ways to improve this project. I’d love to add more complex behaviors, like introducing a predator that chases the fish. This would add tension and excitement to the simulation, showcasing how the fish react to threats and change their behavior to survive.

Additionally, I could enhance the visuals by adding more details to the fish, creating different types and colors to represent various species. Improving the background with coral reefs or underwater plants could also make the scene more immersive. Sound effects, like bubbling water or gentle waves, could enhance the experience, making it feel like you’re truly underwater.

 

Week 9- Dynamic Firework Simulation

Concept:
The idea was to create a dynamic simulation where fireworks launch, explode, and slowly fade away, all while maintaining a sense of development over time. I wanted it to feel like a live performance in the sky, starting from calm darkness to sudden bursts of light and color and back to calm again.

Embedded Sketch:

Code Logic:

Here’s how the flocking concept is applied in my code:

Cohesion (Launching): The initial firework is like a single leader particle that starts from the bottom of the screen and moves upward. This particle influences the others once it bursts.
Separation (Explosion): When the main firework particle reaches its peak and “explodes,” it releases smaller particles that spread out in random directions. The particles act like a flock momentarily repelling from the center, creating that explosion effect.
Alignment (Falling and Fading): As the particles start to slow down and fall due to gravity, they align more with each other to create the visual of embers gently dispersing and fading away.

Code I’m Proud Of:

One part of the code I’m particularly proud of is how the flocking system’s logic was adapted to control the explosion and fading of particles:

explode() {
  for (let i = 0; i < 100; i++) {
    let p = new Particle(this.firework.pos.x, this.firework.pos.y, false);
    this.particles.push(p);
  }
}

When the firework reaches its highest point, this explode() function kicks in. It creates 100 new Particle objects, each with its own direction and speed. This mimics the behavior of a flock splitting apart. Instead of moving together like traditional flocks, these particles use a random2D() vector to scatter and simulate an explosion.

Additionally, each particle follows a simple rule:

Velocity Adjustment: Each particle’s velocity is affected by gravity, making them fall and spread out more realistically.
Lifespan Control: Particles gradually lose opacity (this.lifespan -= 4) to simulate them fading as they fall, creating that “embers in the sky” look.
This adaptation of flocking logic is what makes the explosion feel dynamic and natural, allowing the particles to behave independently yet still appear as part of a cohesive firework.

Challenges I Faced:

One of the main challenges was making the explosion look natural. Initially, the particles either looked too rigid or didn’t spread out enough. To solve this, I played around with p5.Vector.random2D() and tweaked the speed multipliers until it felt right. Another challenge was getting the fading effect correct. It took some trial and error to adjust the lifespan so that particles would fade smoothly and not disappear too abruptly.

Future Improvements:

While I’m happy with how the project turned out, there’s always room for more! Here are some ideas I’m thinking about:

Sound Integration: Adding sound effects would make the fireworks feel even more real. I’d like to use the p5.sound library to sync explosions with audio.
Mouse Interaction: Letting users click to create their own fireworks would add an interactive element to the project.
Different Shapes and Trails: Experimenting with different particle shapes and trail effects could make each firework more unique and visually interesting.
Color Themes: Adding color gradients or changing themes over time to simulate different types of fireworks shows would enhance the overall look.

Week 9 – Khalifa Alshamsi

Inspiration & Concept

The inspiration for this project came from Robert Hodgin and Aditya Anantharaman. Robert Hodgin inspired the concept of creating a tree and birds flocking around it, while Aditya Anantharaman provided insights on how to implement a flocking mechanism.

Sketch

Code

let seed;
let flock = [];
let centerTree;

function setup() {
  createCanvas(windowWidth, windowHeight);
  angleMode(DEGREES);
  seed = Date.now();
  randomSeed(seed);
  noStroke();

  // Initialize the tree
  centerTree = new Tree(width / 2, height - 100);

  // Initialize the flock
  for (let i = 0; i < 200; i++) {
    flock.push(new Boid(random(width), random(height)));
  }
}

function draw() {
  background(255);
  centerTree.display();

    // Update and display each boid in the flock
  for (let boid of flock) {
    boid.flock(flock);
    boid.update();
    boid.edges();
    boid.show();
  }
}

// Tree class
class Tree {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  display() {
    push();
    translate(this.x, this.y);
    // Start drawing the branches of the tree with an initial length of 25
    branch(25);
    pop();

  }
}
function branch(len) {
  push();
  // Recursive branching if the length is greater than 5
  if (len > 5) {
    let newLen = len * 0.8;
    // Set the stroke weight relative to the branch length to create a tapering effect
    strokeWeight(map(len, 10, 120, 1, 8));
    stroke(0);
    line(0, 0, 0, -len);
    // Move to the end of the current branch to create the next branch
    translate(0, -len);
    
    rotate(20); branch(newLen);
    rotate(-40); branch(newLen);
  } 
  pop();
}

// Boid class for flocking behavior
class Boid {
  constructor(x, y) {
    // Set the initial position of the boid
    this.position = createVector(x, y);
    // Assign a random initial velocity to each boid
    this.velocity = p5.Vector.random2D();
    // Set the magnitude of the velocity to control the initial speed
    this.velocity.setMag(random(2, 4));
    // Initialize acceleration with zero vector
    this.acceleration = createVector();
    // Maximum steering force to apply, controls agility
    this.maxForce = 0.2;
    // Maximum speed of the boid
    this.maxSpeed = 4;
    // Size of the boid, represented as a dot
    this.size = random(2, 6);
  }

  // Handles wrapping of boids across the edges of the canvas to keep them in view
  edges() {
    if (this.position.x > width) {
      this.position.x = 0;
    } else if (this.position.x < 0) {
      this.position.x = width;
    }
    if (this.position.y > height) {
      this.position.y = height;
    } else if (this.position.y < 0) {
      this.position.y = height;
    }
  }

  // Alignment behavior: steer towards the average heading of nearby boids
  align(boids) {
    let perceptionRadius = 75;
    let steering = createVector();
    let total = 0;
    for (let other of boids) {
      let d = dist(
        this.position.x,
        this.position.y,
        other.position.x,
        other.position.y
      );
      if (other != this && d < perceptionRadius) {
        steering.add(other.velocity);
        total++;
      }
    }
    if (total > 0) {
      steering.div(total);
      steering.setMag(this.maxSpeed);
      steering.sub(this.velocity);
      steering.limit(this.maxForce);
    }
    return steering;
  }

  // Cohesion behavior: steer towards the average position of nearby boids
  cohesion(boids) {
    let perceptionRadius = 75;
    let steering = createVector();
    let total = 0;
    for (let other of boids) {
      let d = dist(
        this.position.x,
        this.position.y,
        other.position.x,
        other.position.y
      );
      if (other != this && d < perceptionRadius) {
        steering.add(other.position);
        total++;
      }
    }
    if (total > 0) {
      steering.div(total);
      steering.sub(this.position);
      steering.setMag(this.maxSpeed);
      steering.sub(this.velocity);
      steering.limit(this.maxForce);
    }
    return steering;
  }

  // Separation behavior: steer away from nearby boids to avoid crowding
  separation(boids) {
    let perceptionRadius = 24;
    let steering = createVector();
    let total = 0;
    for (let other of boids) {
      let d = dist(
        this.position.x,
        this.position.y,
        other.position.x,
        other.position.y
      );
      if (other != this && d < perceptionRadius) {
        let diff = p5.Vector.sub(this.position, other.position);
        diff.div(d * d);
        steering.add(diff);
        total++;
      }
    }
    if (total > 0) {
      steering.div(total);
      steering.setMag(this.maxSpeed);
      steering.sub(this.velocity);
      steering.limit(this.maxForce);
    }
    return steering;
  }

  // Apply the three flocking rules to determine the boid's acceleration
  flock(boids) {
    let alignment = this.align(boids);
    let cohesion = this.cohesion(boids);
    let separation = this.separation(boids);

    // Apply the three simple flocking rules
    alignment.mult(1.0); // Alignment: move in the average direction of nearby boids
    cohesion.mult(1.0);  // Cohesion: move towards the average position of nearby boids and occasionally towards the tree
    separation.mult(1.5); // Repulsion: move away if too close to neighbors

    this.acceleration.add(alignment);
    this.acceleration.add(cohesion);
    this.acceleration.add(separation);

    // Dynamic change: Modify size to create tension and release effect
    this.size = map(sin(frameCount * 0.05), -1, 1, 1, 4);
  }

  update() {
    this.position.add(this.velocity);
    this.velocity.add(this.acceleration);
    this.velocity.limit(this.maxSpeed);
    // Reset acceleration to 0 for the next frame to avoid compounding effects
    this.acceleration.mult(0);
  }

  show() {
    fill(0);
    ellipse(this.position.x, this.position.y, this.size);
  }
}

Setting the Scene

The project begins by defining a canvas that spans the entire window. A central tree is positioned at the bottom center of the canvas, and a flock of 200 birds (represented as dots) is initialized at random positions. The code uses randomSeed(seed) to ensure the system’s behavior remains consistent across different runs by fixing the random values generated for the tree and flock.

function setup() {
  createCanvas(windowWidth, windowHeight);
  angleMode(DEGREES);
  seed = Date.now();
  randomSeed(seed);
  noStroke();

  centerTree = new Tree(width / 2, height - 100);

  for (let i = 0; i < 200; i++) {
    flock.push(new Boid(random(width), random(height)));
  }
}

The setup() function also initializes the tree and the flock. The tree will remain fixed, while the birds will exhibit a behavior similar to a flock moving around and reacting to each other and their surroundings.

Flocking Behavior of Birds

The flock of birds is represented by instances of the Boid class. Each bird follows three simple rules that are inspired by Aditya Anantharaman work:

  1. Alignment: Birds attempt to move in the average direction of their neighbors within a certain perception radius. This helps ensure the flock is cohesive.
  2. Cohesion: Birds steer towards the average position of nearby birds to maintain the flock’s unity.
  3. Separation: Birds steer away if they come too close to their neighbors, preventing clustering and collisions.

Each bird’s behavior is implemented using three methods: align(), cohesion(), and separation(). These methods collectively determine the bird’s acceleration.

flock(boids) {
  let alignment = this.align(boids);
  let cohesion = this.cohesion(boids);
  let separation = this.separation(boids);

  alignment.mult(1.0);
  cohesion.mult(1.0);
  separation.mult(1.5);

  this.acceleration.add(alignment);
  this.acceleration.add(cohesion);
  this.acceleration.add(separation);
}

In the flock() method, the calculated steering forces for alignment, cohesion, and separation are combined and applied to each bird’s acceleration. The mult() function is used to control the influence of each behavior, making separation slightly more influential to avoid overcrowding.

Dynamic Movement and Visual Appeal

One of the features that makes this flocking system visually appealing is the dynamic change in the size of each bird. The size varies sinusoidally, creating a rhythmic pulsation that adds a sense of tension and release to the movement of the flock:

this.size = map(sin(frameCount * 0.05), -1, 1, 1, 4);

The sin() function is used to create a smooth oscillation effect, making the birds appear to “breathe” as they move. This subtle addition contributes to the natural, lifelike quality of the simulation.

Future Improvements

Moving forward, there are several potential improvements to enhance this project further. One idea is to add environmental changes, such as wind or obstacles, that the flock must navigate around. Adding interactivity, like allowing users to click and influence the direction of the birds or the growth of the tree, would also make the experience more immersive.

Week 9 – Assignment

CONCEPT

This project visualizes the development and evolution of a dynamic flocking system, inspired by the tension and release seen in the works of artists like Ryoichi Kurokawa and Robert Hodgin. The flock of boids represents the fragmentation and cohesion of elements in a system, evolving through three distinct phases: fragmentation, merging, and dissolution. Each phase builds visual and motion-based tension, leading to moments of release as the boids transition from chaotic fragmentation to harmonious merging and, finally, the serene dissolution of the system. Through the use of interactive elements like the shards, the system explores the balance between autonomy and influence, creating a dynamic visual narrative.

 CODE HIGHLIGHT

A part of the code I’m particularly proud of is how I managed the phases of transformation within the system. The boids’ behavior is influenced by the shards scattered across the canvas, which attract them during the fragmentation phase and guide their motion as they merge and dissolve. The `attractedByShards` method allows the boids to interact with these shards, adding layers of complexity to their motion. This method introduces variability and visual tension, as the boids are both influenced by the flocking rules and the external force of the shards. As the system evolves, the shards merge and disappear, leading to the dissolution phase, where boids slowly scatter and fade.

 

Here’s a snippet of the code that handles boid interaction with the shards and phase transitions:

flock(boids, shards) {
let alignment = this.align(boids);
let separation = this.separate(boids);
let shardPull = this.attractedByShards(shards);

this.acceleration.add(alignment);
this.acceleration.add(separation);
this.acceleration.add(shardPull); // Attracted to nearby shards
}

attractedByShards(shards) {
let steering = createVector();
for (let shard of shards) {
let d = dist(this.position.x, this.position.y, shard.position.x, shard.position.y);
if (d < shard.radius) {
let force = p5.Vector.sub(shard.position, this.position);
force.setMag(0.1);
steering.add(force);
}
}
return steering;
}

This part of the code ensures that boids not only follow flocking behaviors like alignment and separation but also respond to the external influence of shards, simulating the tension between independent and collective motion.

You can see the embedded sketch of the project here. It shows the flock of boids interacting with the shards and transitioning through phases of fragmentation, merging, and dissolution. The colors and behaviors of the boids change over time, reflecting the evolving state of the system.

 FUTURE ITERATIONS

In future iterations, I’d love to explore how external user input could influence the phase transitions. Perhaps users could trigger the start of a new phase or manipulate the positions of the shards. Another direction is to introduce sound to accompany each phase, further enhancing the experience of tension and release.

Additionally, I could refine the motion of the boids to create more fluid transitions, adding more intricacies to their behavior as they respond to the system’s changing environment. Another idea is to allow the flock to represent different emotional states or stories, visualized through movement and interaction with external forces like the shards.

Week 9- Morphed

Concept

For my Week 9 assignment, I developed a dynamic flocking system that illustrates the principles of autonomous agents through morphing shapes. This project was inspired by fond memories of teaching my younger siblings about shapes, using games where they would fit various geometric shapes into corresponding holes, like cubes with different cutouts. It’s interesting how my journey started with flocking and boids, but the morphing aspect allowed me to create something truly engaging. While it’s playful, it’s also educational, as I was able to design fundamental shapes like circles, squares, rectangles, and triangles, making it a fun way to learn about geometry!

Embedded Sketch

 Key Features

  •  Engaging Flocking Behavior: The particles demonstrate natural flocking behavior, moving cohesively as they navigate towards their target shapes, which creates an immersive experience that captivates the audience.
  • Dynamic Shape Morphing: As the particles morph into various geometric formations—such as circles, squares, rectangles, and triangles—they provide a visually stunning display that keeps viewers intrigued and encourages exploration of fundamental shapes.
  •  Interactive Learning Experience: Each shape transition introduces an element of surprise, making learning about geometry enjoyable and interactive, as viewers can appreciate the beauty of shapes while observing the playful interactions of the particles.

 Piece of Code That I’m Proud Of

While the concept is simple, implementing this code has been challenging due to the complex interactions between particles that mimic flocking behavior, requiring a solid understanding of vector mathematics. It’s tricky to ensure that the particles move toward their target points and morph seamlessly between shapes, and managing the timing for shape transitions while keeping everything smooth, especially with many particles, adds another layer of complexity.

One aspect I’m particularly proud of is the code for the shape transformations, which handles transitions smoothly and captivates my siblings’ attention. This showcases my ability to blend creativity with programming logic, making it a standout feature of my project. Their joy in watching the shapes dance across the screen highlights the impact of this code and its educational value.

let particles = [];
let numParticles = 100;
let shapeIndex = 0; // Current shape index
let shapePoints = []; // Array to hold shape points
let morphingDuration = 3000; // 3 seconds for morphing
let lastUpdateTime = 0; // Last time the shape was updated

function setup() {
  createCanvas(windowWidth, windowHeight);
  
  // Initialize particles
  for (let i = 0; i < numParticles; i++) {
    particles.push(new Particle(random(width), random(height)));
  }

  // Define shape points for morphing shapes
  shapePoints.push(getCirclePoints());
  shapePoints.push(getSquarePoints());
  shapePoints.push(getTrianglePoints());
  shapePoints.push(getRectanglePoints());
}

function draw() {
  background(51);

  // Get the target shape points based on the shapeIndex
  let targetPoints = shapePoints[shapeIndex];

  // Update each particle to move toward the target points with flocking behavior
  for (let i = 0; i < particles.length; i++) {
    particles[i].update(targetPoints[i]);
    particles[i].show();
  }

  // Check if the time elapsed is greater than the morphing duration
  if (millis() - lastUpdateTime > morphingDuration) {
    if (areParticlesClose(targetPoints)) {
      shapeIndex = (shapeIndex + 1) % shapePoints.length; // Cycle through shapes
      lastUpdateTime = millis(); // Reset the timer
    }
  }
}

function areParticlesClose(targetPoints) {
  for (let i = 0; i < particles.length; i++) {
    let d = dist(particles[i].position.x, particles[i].position.y, targetPoints[i].x, targetPoints[i].y);
    if (d > 10) return false; // Allow some tolerance
  }
  return true; // All particles are close to their target points
}

// Functions to generate shape points omitted for brevity...

class Particle {
  constructor(x, y) {
    this.position = createVector(x, y);
    this.velocity = createVector(random(-1, 1), random(-1, 1));
    this.acceleration = createVector();
    this.size = 8;
    this.colors = [color(255, 0, 0), color(0, 255, 0), color(0, 0, 255), color(255, 255, 0)]; // Different colors
    this.color = this.colors[shapeIndex]; // Set color based on shape index
  }

  update(target) {
    // Calculate flocking forces
    let cohesionForce = this.cohesion();
    let separationForce = this.separation();
    
    this.acceleration.add(cohesionForce);
    this.acceleration.add(separationForce);
    
    // Move towards target shape point
    let desired = p5.Vector.sub(target, this.position);
    desired.setMag(2); // Set maximum speed
    let steering = p5.Vector.sub(desired, this.velocity);
    steering.limit(0.5); // Increase steering limit for faster response
    
    this.acceleration.add(steering);
    
    // Update velocity and position
    this.velocity.add(this.acceleration);
    this.velocity.limit(4); // Limit maximum velocity
    this.position.add(this.velocity);
    this.acceleration.mult(0); // Reset acceleration

    // Update color based on shape index
    this.color = this.colors[shapeIndex];
  }

  show() {
    fill(this.color);
    noStroke();
    ellipse(this.position.x, this.position.y, this.size);
  }

  // Cohesion and separation functions omitted for brevity...
}

Future Work and Improvements

Moving forward, I plan to introduce more complex geometric shapes to deepen the learning experience and challenge understanding. Additionally, I aim to enhance interactivity by incorporating elements that allow users to manipulate shapes and observe immediate changes, fostering a hands-on learning approach. To further reinforce their learning in an engaging manner, I will implement educational features such as quizzes or prompts that encourage users to identify shapes or predict transformations.

Week 9 – Elison by Dachi

Sketch: p5.js Web Editor | Brindle butterkase

Inspiration

The project emerges from a fascination with Avatar: The Last Airbender’s representation of the four elements and their unique bending styles. Craig Reynolds’ Boids algorithm provided the perfect foundation to bring these elements to life through code. Each element in Avatar demonstrates distinct movement patterns that could be translated into flocking behaviors: water’s flowing movements, fire’s aggressive bursts, earth’s solid formations, and air’s spiral patterns.
The four elements offered different ways to explore collective motion: water’s fluid cohesion, fire’s upward turbulence, earth’s gravitational clustering, and air’s connected patterns. While the original Boids algorithm focused on simulating flocks of birds, adapting it to represent these elemental movements created an interesting technical challenge that pushed the boundaries of what the algorithm could achieve.

Process

The development started by building the core Boids algorithm and gradually shaping it to capture each element’s unique characteristics. Water proved to be the ideal starting point, as its flowing nature aligned well with traditional flocking behavior. I experimented with different parameter combinations for cohesion, alignment, and separation until the movement felt naturally fluid.
Fire came next, requiring significant modifications to the base algorithm. Adding upward forces and increasing separation helped create the energetic, spreading behavior characteristic of flames. The particle system was developed during this phase, as additional visual elements were needed to capture fire’s dynamic nature.
Earth presented an interesting challenge in making the movement feel solid and deliberate. This led to implementing stronger cohesion forces and slower movement speeds, making the boids cluster together like moving stones. Air was perhaps the most technically challenging, requiring the implementation of Perlin noise to create unpredictable yet connected movement patterns.
The transition system was the final major challenge, which would allow smooth morphing between elements. This involved careful consideration of how parameters should interpolate and how visual elements should blend. Through iterative testing and refinement, I managed to find a somewhat balanced visuals with unique patterns.

How It Works

The system operates on two main components: the boid behavior system and the particle effects system. Each boid follows three basic rules – alignment, cohesion, and separation – but the strength of these rules varies depending on the current element. For example, water boids maintain moderate values across all three rules, creating smooth, coordinated movement. Fire boids have high separation and low cohesion, causing them to spread out while moving upward.
The particle system adds visual richness to each element. Water particles drift downward with slight horizontal movement, while fire particles rise with random flickering. Earth particles maintain longer lifespans and move more predictably, and air particles follow noise-based patterns that create swirling effects.
The transition system smoothly blends between elements by interpolating parameters and visual properties. This includes not just the boid behavior parameters, but also particle characteristics, colors, and shapes. The system uses linear interpolation to gradually shift from one element’s properties to another, ensuring smooth visual and behavioral transitions.

 Code I’m Proud Of

switch(this.element) {
  case elementParams.fire:
    this.pos.y -= 1;
    this.vel.x += random(-0.1, 0.1);
    break;
  case elementParams.air:
    let time = (frameCount + this.offset) * 0.01;
    let noiseX = smoothNoise(this.pos.x * 0.006, this.pos.y * 0.006, time);
    let noiseY = smoothNoise(this.pos.x * 0.006, this.pos.y * 0.006, time + 100);
    this.vel.add(createVector(noiseX * 0.15, noiseY * 0.15));
    this.vel.limit(1.5);
    break;
}

This code efficiently handles the unique behavior of each element’s particles while remaining clean and maintainable. The fire particles rise and flicker naturally, while air particles follow smooth, noise-based patterns that create convincing wind-like movements.

Challenges

Performance optimization proved to be one of the biggest challenges. With hundreds of boids and particles active at once, maintaining smooth animation required careful optimization of the force calculations and particle management. I implemented efficient distance calculations and particle lifecycle management to keep the system running smoothly.
Creating convincing transitions between elements was another significant challenge. Moving from the rapid, dispersed movement of air to the slow, clustered movement of earth initially created jarring transitions. The solution involved creating a multi-layered transition system that handled both behavioral and visual properties gradually.
Balancing the elements’ distinct characteristics while maintaining a cohesive feel required extensive experimentation with parameters. Each element needed to feel unique while still being part of the same system. This involved finding the right parameter ranges that could create distinct behaviors without breaking the overall unity of the visualization.

Reflections and Future Considerations

The project successfully captures the essence of each element while maintaining smooth transitions between them. The combination of flocking behavior and particle effects creates an engaging visualization that responds well to user interaction. However, there’s still room for improvement and expansion.
Future technical improvements could include implementing spatial partitioning for better performance with larger boid counts, adding WebGL rendering for improved graphics, and creating more complex particle effects. The behavior system could be enhanced with influence mechanics where fire and water cancel out each other and other elements interact in various ways.
Adding procedural audio based on boid behavior could create a more immersive experience. The modular design of the current system makes these expansions feasible while maintaining the core aesthetic that makes the visualization engaging.
The project has taught me valuable lessons about optimizing particle systems, managing complex transitions, and creating natural-looking movement through code.
Throughout the development process, I gained a deeper appreciation for both the complexity of natural phenomena and the elegance of the algorithms we use to simulate them.