Week 9 Coding Assignment

In this project, each vehicle acts as an independent agent with three key behaviors:

  1. Cohesion: Vehicles steer toward the average position of nearby flockmates.
  2. Separation: To avoid collisions, vehicles steer away from those that come too close.
  3. Alignment: Vehicles match their direction and speed with nearby flockmates, creating a collective flow.

These behaviors produce an emergent, cohesive movement across the system as vehicles move together as a group while avoiding overcrowding and maintaining some alignment. We also added a wander function with Perlin noise to generate more organic, smooth changes in direction, which softened the rigidness of purely algorithmic steering.

Challenges and Discoveries

One challenge was balancing the behaviors, as the vehicles could either clump together too tightly or spread out too widely. Adjusting the perception radii and scaling each behavior’s force was key in achieving the desired balance, allowing each vehicle to respond naturally to its neighbors while still participating in a larger, collective pattern.

I also experimented with Perlin noise to influence movement subtly. By introducing this form of randomness, each vehicle gained a more fluid, organic quality, reminiscent of natural systems where movement isn’t perfectly uniform. This approach has inspired further experimentation, such as adding color shifts or trails to visualize the vehicles’ paths.

code

Guest Lecture Reflection

The guest artist lecture this week was enjoyable and inspiring. Their work emphasized that designing with nature often means embracing imperfections and patterns that emerge over time, rather than controlling every element. This philosophy connects well with our coding assignments where we learn that emergent behaviors often arise from simple rules rather than strict control.

SMH

 

This version of Simple Harmonic Motion will feature multiple particles moving in harmonic motion along different axes. Each particle’s motion is governed by sine and cosine functions, and we’ll experiment with adding layers of complexity by adjusting frequency, amplitude, and phase shift over time. The interaction of these particles will create a visually intriguing harmonic system.

Design Approach

  1. Multiple Oscillators: Each oscillator (particle) will move along one or two axes following SHM principles.
  2. Phase Shifting: The oscillators will have slight differences in their phase, creating beautiful and non-repetitive motion patterns.
  3. Layering Frequencies: Multiple sine and cosine waves will be layered to simulate interacting harmonic systems.
  4. Memo Akten’s Influence: Drawing from Memo Akten’s complex motion systems, we’ll make the motion more intricate by introducing randomness to parameters like amplitude and frequency over time.

Code design

  • Particles: Each particle is modeled as a point oscillating based on sine and cosine functions, representing harmonic motion along both the X and Y axes.
  • Amplitude and Frequency: The amplitude controls how far the particle moves, while the frequency controls how fast it oscillates.
  • Phase Shift: Each particle starts with a random phase, ensuring their motion is out of sync, creating an intricate, layered pattern.
let particles = [];

function setup() {
  createCanvas(800, 800);
  for (let i = 0; i < 10; i++) {
    particles.push(new Particle(random(50, 100), random(50, 200), random(0.02, 0.05)));
  }
}

class Particle {
  constructor(amplitudeX, amplitudeY, frequency) {
    this.amplitudeX = amplitudeX; // Amplitude of motion in X
    this.amplitudeY = amplitudeY; // Amplitude of motion in Y
    this.frequency = frequency; // Frequency of oscillation
    this.angle = random(TWO_PI); // Starting phase angle
  }

  update() {
    // Increment angle over time to simulate oscillation
    this.angle += this.frequency;
  }

  display() {
    let x = this.amplitudeX * cos(this.angle); // Simple harmonic motion in X
    let y = this.amplitudeY * sin(this.angle); // Simple harmonic motion in Y
    
    noFill();
    stroke(255, 200);
    ellipse(x, y, 50, 50); // Draw particle at (x, y)
  }
}

View code

Midterm Progress 3

Final Project Refinements

  1. Polished Modes: The three modes (Growth, Random, Stability) now work seamlessly, offering different visual representations of population dynamics.
  2. Final Experimentation: After experimenting with various settings, I settled on a configuration that produces visually appealing population patterns, especially in Stability Mode where populations stabilize.
  3. Plotted Output: The final SVG image generated in Stability Mode resonated with me, as it visually symbolizes population balance. I exported the high-res version and prepared it for printing on A3 sheets.

view code

Midterm Progress 2

Project Development

  1. Fertility & Death Rate Integration: By this phase, I fully incorporated fertility and death rates. Each region now grows or shrinks based on these factors, and in Stability Mode, populations stabilize around a 2.1 replacement rate.
  2. Mode Switching: Users can toggle between Growth, Random, and Stability modes using the keyboard.
  3. Visual Feedback: The size of each circle dynamically adjusts based on the population changes, offering a visual representation of how populations are evolving in each region.
  4. Significant Code Progress: Implemented the full logic for handling different population behaviors across modes.

Code Refinements

In this phase, I added full functionality for fertility and death rates, and populations now stabilize in Stability Mode.

let regions = [];
let mode = 0; // Mode selector

function setup() {
  createCanvas(800, 800);
  let gridSize = 4;
  let spacing = width / gridSize;
  
  for (let i = 0; i < gridSize; i++) {
    for (let j = 0; j < gridSize; j++) {
      let x = spacing * i + spacing / 2;
      let y = spacing * j + spacing / 2;
      let population = random(50, 200);
      let fertilityRate = random(1.5, 5);
      let deathRate = random(0.5, 3);
      regions.push(new Region(x, y, population, fertilityRate, deathRate));
    }
  }
}

function draw() {
  background(255);
  switch (mode) {
    case 0:
      growthMode();
      break;
    case 1:
      randomMode();
      break;
    case 2:
      stabilityMode();
      break;
  }
}

function keyPressed() {
  if (key === '1') mode = 0;
  if (key === '2') mode = 1;
  if (key === '3') mode = 2;
}

function growthMode() {
  for (let region of regions) {
    region.grow();
    region.display();
  }
}

function randomMode() {
  for (let region of regions) {
    region.randomizeRates();
    region.grow();
    region.display();
  }
}

function stabilityMode() {
  for (let region of regions) {
    region.stabilize();
    region.grow();
    region.display();
  }
}

Three Variations for Export

  1. Growth Mode: Populations grow or shrink based on initial fertility and death rates.
  2. Random Mode: Random changes in fertility and death rates cause erratic population behaviors.
  3. Stability Mode: Populations gradually stabilize around the replacement rate.

I exported these three variations in SVG format using the save() function in p5.js.

function keyPressed() {
  if (key === 's') {
    save(); // Saves the current state as SVG
  }
}

 

Midterm Progress 1

Project Concept

The project mimics population dynamics in different parts of the world by incorporating fertility rates and death rates, allowing each region to grow, shrink, or stabilize based on these parameters. Over time, population levels converge towards a 2.1 replacement rate (the level needed to maintain a stable population). This concept is inspired by real-world demographic transitions, where regions move from rapid population growth to stabilization.

  • Regions as Grid: A grid of circles represents different global regions. Each circle’s size correlates to the population size, while fertility and death rates drive the growth or shrinkage.
  • Fertility Rate: Controls how quickly a region’s population expands.
  • Death Rate: Controls how quickly populations decline.
  • Replacement Rate: Populations converge towards a stable size (replacement rate of 2.1 children per woman), creating patterns of population stability.

Modes of the System

  1. Growth Mode: Populations grow or shrink based on predefined fertility and death rates.
  2. Random Mode: Random fluctuations in fertility and death rates simulate unpredictable factors like pandemics or booms.
  3. Stability Mode: Regions converge towards the replacement rate, stabilizing population growth.

Design Approach

  1. Grid Layout: A grid layout represents various regions, and each circle symbolizes a region’s population.
  2. Fertility and Death Rates: Each region has different fertility and death rates, randomly assigned initially. These rates determine whether a population grows or declines.
  3. Replacement Rate: In Stability Mode, all populations will stabilize around a fertility rate of 2.1.
  4. Modes: The user can toggle between modes using the keyboard.
  5. Visualization: Circle size visualizes population changes, expanding during growth and shrinking during decline.

Most Complex Part

Handling the stabilization towards the 2.1 replacement rate is the most uncertain part. Populations should gradually approach equilibrium without abrupt changes. I’ll mitigate this risk by implementing gradual transitions in the stabilization mode, where the fertility and death rates are slowly adjusted.

Initial Code and Progress (Growth Logic)

At this phase, I implemented the basic grid and growth logic, focusing on population growth and shrinkage. I designed classes and functions that would later incorporate fertility and death rates.

class Region {
  constructor(x, y, population, fertilityRate, deathRate) {
    this.x = x;
    this.y = y;
    this.population = population;
    this.fertilityRate = fertilityRate;
    this.deathRate = deathRate;
  }

  grow() {
    let growthFactor = this.fertilityRate - this.deathRate;
    this.population += growthFactor;
    this.population = constrain(this.population, 10, 300);
  }

  display() {
    fill(100, 150, 200);
    ellipse(this.x, this.y, this.population);
  }

  randomizeRates() {
    this.fertilityRate = random(1.5, 5); // Random fertility rate
    this.deathRate = random(0.5, 3); // Random death rate
  }

  stabilize() {
    this.fertilityRate = lerp(this.fertilityRate, 2.1, 0.05); // Gradual shift towards replacement rate
    this.deathRate = lerp(this.deathRate, 1.9, 0.05); // Shift death rate to balance fertility
  }
}

 

Week 3 – Chaotic Gravity

This concept is inspired by natural phenomena like planets orbiting stars due to gravity. But instead of perfect, smooth motion, we add turbulence to add a bit of chaos to keep things dynamic and fun to watch.

In the sketch below you’ll see several circular objects (the movers) being pulled towards an invisible point in the middle of the canvas (the attractor). I’ve added some randomness to their movements using turbulence, which shakes things up a bit.

 

Key parts of the code are:

  • Movers: These are the objects that move around the canvas. They’re represented as circles that feel the force of the attractor and the added turbulence.
  • Attractors: There’s only one attractor in this sketch, located in the center of the canvas, but we could add more for a more complex pattern.
  • Forces: The attraction is like gravity, pulling the movers toward the center. The turbulence adds some randomness, so the movers don’t just head straight for the attractor.

The following code mimics gravity: the closer the mover gets to the attractor, the stronger the pull, but the force is constrained so that the mover doesn’t get stuck. Here’s how the attraction works:

function calculateAttraction(moverPos, attractorPos) {
  let force = p5.Vector.sub(attractorPos, moverPos);  // Direction of force
  let distance = constrain(force.mag(), 5, 25);  // Constrain the distance to avoid too strong forces
  force.normalize();
  let strength = (0.4 * 10) / (distance * distance);  // Gravitational pull formula
  force.mult(strength);
  return force;
}

code

Simulating Butterfly Flutters

Butterfly Flutters

In this project, we simulate the fluttering motion of a butterfly by focusing on how acceleration could be used to control the movement.

Rules

  • A butterfly’s movement is influenced by unpredictable changes in its speed and direction, so we’ll randomize the acceleration over time.
  • While acceleration can be random, the butterfly doesn’t move too fast. We’ll use a damping factor to slow the object down.
  • The butterfly has to stay within the canvas, so we’ll implement boundary conditions to keep it in view.
  • By focusing on irregular accelerations and smooth decelerations, we can simulate the light, erratic fluttering of a butterfly.

Code

I’m particularly proud of how adding slight randomness to the acceleration makes the butterfly feel alive, without the need for overly complex design or physics.

let randomAcc = createVector(random(-1, 1), random(-1, 1));
this.acceleration.add(randomAcc);

// Update velocity based on acceleration
this.velocity.add(this.acceleration);

// Limit velocity to simulate fluttering motion, not too fast
this.velocity.limit(this.maxSpeed);

// Dampen the velocity slightly
this.velocity.mult(0.95);

link to code

Future improvements

  • I could improve the butterfly’s visual design by adding more details to the wings and body.
  • Adding mouse or keyboard interactions could let users “guide” the butterfly around the canvas.
  • Introducing environmental elements like wind or obstacles could add complexity and realism to the simulation.

Reading 1

It’s fascinating to contemplate that simple agents, like ants or even molecules, can come together to create complex behaviors that we see in larger systems. This reading emphasized that we can’t always predict how a group will behave just by knowing about the individual parts. It’s like trying to understand a sports team by only looking at the skills of each player rather than how they fit/work together.

I am particularly intrigued by the idea that nature often uses simple rules to create complexity. The role of computers in studying these interactions also excites me, as it opens up new ways to explore and understand complex systems, and places technologists on the frontlines of the quest to understand nature.

Gaussian Random Walk in RGB Space

The program generates a random walk where each step’s size is determined by a Gaussian distribution, causing the walker to move in smooth, less erratic paths. As the walker traverses the canvas, its position influences the RGB values used to create colors. The resulting display features a vibrant color trail that evolves with the walker’s movement.

function drawTrail() {
  for (let i = 0; i < colors.length; i++) {
    stroke(colors[i]);
    point(width / 2 + randomGaussian() * 80, height / 2 + randomGaussian() * 80);
  }
}

The above code draws points on the graph deviating from the center using gaussian distribution for each random color, leading to higher intensity closer to the middle of the canvas.

Full code