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.