Assignment 11: Controller for CM Visualizations

For this week’s assignment, I decided to go back to Assignment 9, and make a custom controller that allows the user to control how the visualization looks like.

I wanted the controller to be easy/ergonomic to use – I used flat cables so that the user does not need to weave their hand through to reach a button; I also bent the resistors down so they would be out of the way.

The nine function buttons are color-coded as much as possible given the limitation of four colors available in the lab. There are three paired controls (region/center region toggle – green, outlines/center outline – red, stats/speed stats toggle – yellow) and three individual buttons (links, person, hitpoints display modes switches – each with a different color).

In addition, the controller has four potentiometers in the upper left corner. These allow the user to change the four dynamic variables that affect the visualization at runtime: number of people, number of colors, speed of simulation, and the amount of “ghosts” (object traces).

The dynamic changing of ghosting is the biggest change from the original program – there used to be four distinct settings with set amounts of transparency applied. Now, the user can use the fourth potentiometer to choose from ten different settings, for a more customized visualization.

It is important to note that the original functionality – using the keyboard to interact with the visualization has been preserved. Thus, one can still use number keys to change settings, and function keys to change the dynamic variables. Importantly, the function of spacebar (refresh the board), and of the letter keys (add a new person at the position of the mouse cursor) have not been replicated on the controller.

The Arduino code is presented below:

long previousMillis = 0;
int inputArray [] = {0,0,0,0,0,0,0,0,0,0,0,0,0};

void setup() {
  // put your setup code here, to run once:
  pinMode(A0, INPUT); // people
  pinMode(A1, INPUT); // colors
  pinMode(A2, INPUT); // speed
  pinMode(A3, INPUT); // ghosts
  
  pinMode(2, INPUT); // regions
  pinMode(3, INPUT); // center region
  pinMode(4, INPUT); // outlines
  pinMode(5, INPUT); // center outline
  pinMode(6, INPUT); // links
  pinMode(7, INPUT); // mode
  pinMode(8, INPUT); // hitpoints
  pinMode(9, INPUT); // stats
  pinMode(10, INPUT); // speed stats
  
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  long currentMillis = millis();
  inputArray[0] = analogRead(A0);
  inputArray[1] = analogRead(A1);
  inputArray[2] = analogRead(A2);
  inputArray[3] = analogRead(A3);
  
  if (digitalRead(2) == HIGH) inputArray[4] = 1;
  if (digitalRead(3) == HIGH) inputArray[5] = 1;
  if (digitalRead(4) == HIGH) inputArray[6] = 1;
  if (digitalRead(5) == HIGH) inputArray[7] = 1;
  if (digitalRead(6) == HIGH) inputArray[8] = 1;
  if (digitalRead(7) == HIGH) inputArray[9] = 1;
  if (digitalRead(8) == HIGH) inputArray[10] = 1;
  if (digitalRead(9) == HIGH) inputArray[11] = 1;
  if (digitalRead(10) == HIGH) inputArray[12] = 1;
  
  if (currentMillis-previousMillis >= 200) {
    for (int i = 0; i < 12; i += 1) {
      Serial.print(inputArray[i]);
      Serial.print(',');
    }
    Serial.println(inputArray[12]);
    
    for (int j = 4; j < 13; j += 1) {
      inputArray[j] = 0;
    }
    
    previousMillis = currentMillis;
  }
}

The pertinent changes in the Processing code are presented below:

import processing.serial.*;
import megamu.mesh.*;

Serial myPort;

//...

boolean regenerateSwarm = false;
int newNumPeople = NUM_PEOPLE;
int newNumColors = NUM_COLORS;
int ghostsMax = 255;
boolean ghostsChanged = false;

// ...

void setup() {
  fullScreen();
  String portname = Serial.list()[1];
  myPort = new Serial(this, portname, 9600);
  myPort.clear();
  background(255);
  rectMode(CENTER);
  fill(0);
  rect(width/2,height/2,2,2);
  peopleSwarm = new Swarm();
}

// ...

void draw() {
  rectMode(CENTER);
  noStroke();
  if (OPT_GHOSTS == 0) fill(255,1);
  else if (OPT_GHOSTS == 1) fill(255,5);
  else if (OPT_GHOSTS == 2) fill(255,10);
  else if (OPT_GHOSTS == 3) fill(255,ghostsMax);
  rect(width/2,height/2,width,height);
  
  // ...
}

// ...

void serialEvent(Serial myPort) {
  String s = myPort.readStringUntil('\n');
  s = trim(s);
  if (s != null) {
    String[] values = split(s,',');
    if (values.length == 13) {
      int people = int(map(int(values[0]),0,1023,1,30));
      int colors = int(map(int(values[1]),0,1023,1,10));
      int speedRaw = int(map(int(values[2]),0,1023,0,9));
      int ghosts = int(map(int(values[3]),0,1023,-1,8));
      
      boolean regionsToggle = boolean(int(values[4]));
      boolean centerRegionToggle = boolean(int(values[5]));
      boolean outlinesToggle = boolean(int(values[6]));
      boolean centerOutlineToggle = boolean(int(values[7]));
      boolean linksToggle = boolean(int(values[8]));
      boolean modeToggle = boolean(int(values[9]));
      boolean hitpointsToggle = boolean(int(values[10]));
      boolean statsToggle = boolean(int(values[11]));
      boolean speedToggle = boolean(int(values[12]));

      if (newNumPeople != people*10) {
        newNumPeople = people*10;
        regenerateSwarm = true;
      }
      
      if (newNumColors != colors) {
        newNumColors = colors;
        regenerateSwarm = true;
      }
      
      SPEED_MODIFIER = 1;
      for (int i = 0; i < speedRaw; i += 1) {
        SPEED_MODIFIER /= 2;
      }
      VAR_HITPOINTS = 100 / (SPEED_MODIFIER);
      
      if (ghosts == -1) {
        ghostsMax = 0;
      }
      else {
        ghostsMax = 1;
        for (int i = 0; i < ghosts; i += 1) {
          ghostsMax *= 2;
        }
        if (ghostsMax > 256) {
          ghostsMax = 255;
        }
      }
      
      if (regionsToggle) OPT_REGIONS = (OPT_REGIONS+1) % 2;
      if (centerRegionToggle) OPT_CENTER_REGION = (OPT_CENTER_REGION+1) % 2;
      if (outlinesToggle) OPT_OUTLINES = (OPT_OUTLINES+1) % 2;
      if (centerOutlineToggle) OPT_CENTER_OUTLINE = (OPT_CENTER_OUTLINE+1) % 2;
      if (linksToggle) OPT_LINKS = (OPT_LINKS+1) % 3;
      if (modeToggle) OPT_MODE = (OPT_MODE+1) % 3;
      if (hitpointsToggle) OPT_HITPOINTS = (OPT_HITPOINTS+1) % 2;
      if (statsToggle) OPT_STATS = (OPT_STATS+1) % 2;
      if (speedToggle) OPT_SPEED = (OPT_SPEED+1) % 2;
    }
  }
}