Interaction: Collective Life

Statement of Intent

With this interaction, titled “Collective Life”, Molly and I hoped to illicit an empathic response from our fellow classmates. We wanted their experience, albeit brief, to serve as a reminder to see themselves as part of an interconnected and complicated system. The more we are able to communicate and collaborate, the more likely we are to be able to save the planet from imminent destruction; the more we act in unison, which is represented through the metaphor of connecting up to five hands on our customised interface, the more the boids’ behaviour synchronises. Our overarching goal with Collective Life was to pose to our classmates the question: what role will they play as future designers and leaders in the myriad of systems that surround them?

Details

Collective Life takes place over six stages. Each boid has a ‘home position’, when they are generated they are pixel mapped to the base image of the earth.

  • The first stage, when no users are touching the sensor is the state of chaos. The boids are drifting, with same velocity and same direction, thus appearing to be moving randomly. Processing also acts as a Midi controller which sets the values for the delay and reverb at 100% and controls a band pass filter over the master channel.
  • In stage two, where one user places their hand onto the interface, the boids begin to flock.
  • At stage three, requiring two users, the boids move in the direction of their home position generally, which creates gives the impression of the 2D forms moving (emergence!).
  • Next is stage four, three users, they move closer to the home position. This stage has the biggest jump in audio with the original samples coming through more.
  • At stage five, four users, the boids are at home position with some variation (n.b base image is obvious).
  • Finally, stage six, which is five users, the boids are at home position with a little ‘dancing’ and reverb/delay filters are set to zero value.

For more details on the process behind this animation, see this blog post.

If you wish to get a application form of the design, you can download it here. This one does not have sound, and is controlled via keypress (0-9 for the number of people interacting).

/* "Collective Life" code by Melody Elwood, creative direction by Molly McLennan
 * Based on an implementation of Craig Reynold's Boids program by Daniel Shiffman.  
 * V10 published 28/10/2020
 */
 
//Imports
import gab.opencv.*;
import processing.video.*; //for getting a video from a 
import java.awt.*; //For arraylists
import themidibus.*; //For acting as a midi controler
import processing.serial.*; //For serial connection
import vsync.*; //To sync with the arduino
 
 
//CONSTANTS ********************************
float MIN_BOID_CREATION_DIST = 10; //12, 15 or 9 = smallest
float SEPARATION_WEIGHT = 0;
float ALIGNMENT_WEIGHT = 0;
float COHESION_WEIGHT = 0; 
float COLLABORATION_WEIGHT = 0; 
int IMAGE_SIZE = 900; //420, 540, 600
 
String BASE_IMAGE = "planet.png";
 
float MAX_SPEED = 3;
 
float ROOM = 0.5;
float DAMP = 0.5;
float WET = 0.5;
 
int BOID_SIZE = 10; //10, 12
int SIGHTRANGE = 30; //SIGHTRANGE = "radius" of square sightrange
 
String COM_PORT = "COM6";
 
//Options
boolean USE_BOID_IMAGES = true;
 
String BOID_IMAGE_LOCATIONS = "boids"; //"boids", "cell", or "eCell".
 
boolean USE_CAMERA = false;
boolean SHOW_CAMERA = false;
 
boolean SHOW_BASE_IMAGE = false;
 
boolean ADD_BOIDS_WITH_MOUSE = false;
 
boolean USE_MIDI = true;
 
boolean USE_ARDUINO = false;
 
boolean SHOW_SIGHTRANGE = false;
boolean SHOW_QUADTREE = false;
//CONSTANTS END ***************************
 
 
//Globals
Flock flock;
Quadtree qt;
 
PImage baseImg;
PGraphics base; 
ArrayList<PVector> boidPlaces = new ArrayList<PVector>(); 
PImage[] boidImages;
 
Capture video;
OpenCV opencv;
 
MidiBus myBus;
 
ValueReceiver receiver;
 
int total = 0;
int all = 0;
 
public int button1, button2, button3, button4, button5;
 
 
void setup() {
  size(2080, 2080);
  //fullScreen();
  textSize(62);
  text("LOADING...", width/2, height/2);
 
  flock = new Flock();
 
  //Load the image
  baseImg = loadImage(BASE_IMAGE);
 
  //Create a list of all possible boid sprites
  File file = new File(sketchPath() + "\\Data\\" + BOID_IMAGE_LOCATIONS);
  String[] files = file.list();
  boidImages = new PImage[files.length];
  for (int i = 0; i < files.length; i++)
  {
    boidImages[i] = loadImage(sketchPath() + "\\Data\\"+ BOID_IMAGE_LOCATIONS + "\\" + files[i]);
    boidImages[i].resize(BOID_SIZE, 0);
  }
 
  //make the image into a fullscreen graphic
  base = createGraphics(width, height);
  base.beginDraw();
  base.clear();
  base.imageMode(CENTER);
  baseImg.resize(IMAGE_SIZE, 0);
  base.image(baseImg, width/2, height/2);
  base.endDraw();
 
  // Add an initial set of boids into the system
  base.loadPixels();
  for (int i = 0; i<base.height; i++) {
    for (int j = 0; j<base.width; j++) {
      all++;
      //get argb values
      color argb = base.pixels[(i*base.width) + j];
      int a = argb >> 24 & 0xFF;
      int r = argb >> 16 & 0xFF;
      int g = argb >> 8 & 0xFF;
      int b = argb & 0xFF;
      if (r == 255 && g == 255 && b == 255)
      {
      } else if (a == 255) //if the alpha value is 255, and it's far enough away from another boid, create a boid at that pixle location with that color
      {
        float closest = width+height;
        for (PVector loc : boidPlaces)
        {
          if (closest > PVector.dist(loc, new PVector(j, i)))
          {
            closest = PVector.dist(loc, new PVector(j, i));
          }
        }
        if (closest > MIN_BOID_CREATION_DIST)
        {
          flock.addBoid(new Boid(random(width), random(height), color(r, g, b), new PVector(j, i)));
          boidPlaces.add(new PVector(j, i));
          total++;
        }
      }
    }
  } 
  println(total);
  println(all);
 
  if (USE_CAMERA)
  {
    video = new Capture(this, 640/2, 480/2);
    opencv = new OpenCV(this, 640/2, 480/2);
    opencv.loadCascade(OpenCV.CASCADE_UPPERBODY);  
 
    video.start();
  }
 
  if (USE_MIDI)
  {
    MidiBus.list();
    myBus = new MidiBus(this, -1, "loopMIDI Port"); // Create a new MidiBus
  }
 
  if (USE_ARDUINO)
  {
    Serial serial = new Serial(this, COM_PORT, 19200);
    receiver = new ValueReceiver(this, serial).observe("button1").observe("button2").observe("button3").observe("button4").observe("button5");
  }
}
 
void draw() {
  background(0); //50
  imageMode(CENTER);
  /*if (USE_CAMERA)
   {
   opencv.loadImage(video);
   if (SHOW_CAMERA)
   {
   tint(0, 0, 0);
   image(video, 50, 50);
   }
    
   Rectangle[] faces = opencv.detect();
   println(faces.length);
   updateWeighting(faces.length);
   noFill();
   stroke(0, 255, 0);
   strokeWeight(3);
   for (int i = 0; i < faces.length; i++) {
   println(faces[i].x + "," + faces[i].y);
   rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height);
   }
   }*/
 
  if (SHOW_BASE_IMAGE)
  {
    image(base, width/2, height/2);
  }
 
  if (USE_ARDUINO)
  {
    int numPeople = 0;
    if (button1 == 1) numPeople++;
    if (button2 == 1) numPeople++;
    if (button3 == 1) numPeople++;
    if (button4 == 1) numPeople++;
    if (button5 == 1) numPeople++;
    if (button1 == 1) println("Button1 pressed");
    if (button2 == 1) println("Button2 pressed");
    if (button3 == 1) println("Button3 pressed");
    if (button4 == 1) println("Button4 pressed");
    if (button5 == 1) println("Button5 pressed");
    updateWeighting(numPeople);
  }
 
  flock.run();
}
 
// Add a new boid into the System
void mousePressed() {
  if (ADD_BOIDS_WITH_MOUSE)
  {
    flock.addBoid(new Boid(mouseX, mouseY, color(random(255), random(255), random(255)), new PVector(mouseX, mouseY)));
  }
}
 
//***************************************************
void keyPressed()
{
  if (key == '0')
  {
    updateWeighting(0);
  }
  if (key == '1')
  {
    updateWeighting(1);
  }
  if (key == '2')
  {
    updateWeighting(2);
  }
  if (key == '3')
  {
    updateWeighting(3);
  }
  if (key == '4')
  {
    updateWeighting(4);
  }
  if (key == '5')
  {
    updateWeighting(5);
  }
  if (key == '6')
  {
    updateWeighting(6);
  }
  if (key == '7')
  {
    updateWeighting(7);
  }
  if (key == '8')
  {
    updateWeighting(8);
  }
  if (key == '9')
  {
    updateWeighting(9);
  }
   
  if (key == '-')
  {
    SHOW_QUADTREE = !SHOW_QUADTREE;
  }
  if (key == '=')
  {
    SHOW_SIGHTRANGE = !SHOW_SIGHTRANGE;
  }
}
 
void captureEvent(Capture c) {
  c.read();
}
 
 
//***************************************************************************
void updateWeighting(int numPeople)
{
  if (numPeople == 0)
  {
    SEPARATION_WEIGHT = 0;
    ALIGNMENT_WEIGHT = 0;
    COHESION_WEIGHT = 0; 
    COLLABORATION_WEIGHT = 0; 
    if (USE_MIDI)
    {
      myBus.sendControllerChange(0, 0, 127);
      myBus.sendControllerChange(2, 1, 10);
      myBus.sendControllerChange(3, 2, 127);
    }
    println("Zero People");
  } else if (numPeople == 1)
  {
    SEPARATION_WEIGHT = 1.5;
    ALIGNMENT_WEIGHT = 1;
    COHESION_WEIGHT = 1; 
    COLLABORATION_WEIGHT = 0; 
    if (USE_MIDI)
    {
      myBus.sendControllerChange(0, 0, 117);
      myBus.sendControllerChange(2, 1, 50);
      myBus.sendControllerChange(3, 2, 127);
    }
    println("One People");
  } else if (numPeople == 2)
  {
    SEPARATION_WEIGHT = 1.5;
    ALIGNMENT_WEIGHT = 1;
    COHESION_WEIGHT = 1; 
    COLLABORATION_WEIGHT = 1; 
    if (USE_MIDI)
    {
      myBus.sendControllerChange(0, 0, 100);
      myBus.sendControllerChange(2, 1, 75);
      myBus.sendControllerChange(3, 2, 127);
    }
    println("Two People");
  } else if (numPeople == 3)
  {
    SEPARATION_WEIGHT = 1.5;
    ALIGNMENT_WEIGHT = 1;
    COHESION_WEIGHT = 1; 
    COLLABORATION_WEIGHT = 1.5; 
    if (USE_MIDI)
    {
      myBus.sendControllerChange(0, 0, 50);
      myBus.sendControllerChange(2, 1, 75);
      myBus.sendControllerChange(3, 2, 0);
    }
    println("Three People");
  } else if (numPeople == 4)
  {
    SEPARATION_WEIGHT = 1;
    ALIGNMENT_WEIGHT = 1;
    COHESION_WEIGHT = 1; 
    COLLABORATION_WEIGHT = 4; 
    if (USE_MIDI)
    {
      myBus.sendControllerChange(0, 0, 0);
      myBus.sendControllerChange(2, 1, 127);
      myBus.sendControllerChange(3, 2, 0);
    }
    println("Four People");
  } else
  {
    SEPARATION_WEIGHT = 0;
    ALIGNMENT_WEIGHT = 0;
    COHESION_WEIGHT = 0; 
    COLLABORATION_WEIGHT = numPeople*5; 
    if (USE_MIDI)
    {
      myBus.sendControllerChange(0, 0, 0);
      myBus.sendControllerChange(2, 1, 127);
      myBus.sendControllerChange(3, 2, 0);
    }
    println("More People");
    println("numPeople: ", numPeople);
  }
}