Weighted Average can be thought of in the same way that iron filing position themselves in a magnetic field. A grid or field of elements is acted upon by another series of defined elements (let’s call those agents). The attribute we will be controlling based on proximity to those agents is rotation. In this case, you could just query the agents and take into account the closest one, but that would give you defined regions of rotation around each agent. You could average the distance between a point on the grid and all of the agents, but that would give the opposite of discrete regions. You would end up with a filed of rotation around a single point (the positional average of all the agents). A weighted average takes into account the distance from a point on the grid and each agent and averages the effect each agent will have based on that distance. In that case every point on the grid is influenced by each agent, but as an agent gets farther away that influence decreases.

A similar rotation weighted average was used to calculate the rotation of the mirrors for Volume.

Here is a simple example with just one agent, the mouse position. In this case, we don’t need to find a weighted average because we only have one agent. But this example shows how we will calculate the rotation of each grid element based on the distance from the mouse position.

In this example, we have added multiple randomly placed agents and the mouse position. In this case, the code to calculate the weighted average of a series of points has been added. One thing that is necessary to figure out the weighted average is to find the

ArrayList<PVector> grid_pts = new ArrayList<PVector>();

float grid_space = 20;
void setup() {
  size(800, 800);
  
  float xpts = width/grid_space;
  float ypts = height/grid_space;
  for(int i = 0; i < xpts; ++i){
    for(int j = 0; j < ypts; ++j){
      grid_pts.add(new PVector(i*grid_space,j*grid_space,0));
    }
  }
}

void draw() {
   background(0);
   for(int i = 0; i < grid_pts.size(); ++i){
     PVector posV = grid_pts.get(i);
     noStroke();
     fill(100);
     ellipse(posV.x,posV.y,5,5);
   }
   
   float long_dist = dist(0,0,width,height);
   
   for(int i = 0; i < grid_pts.size(); ++i){
     PVector posV = grid_pts.get(i);
     float na = -atan2(mouseX-posV.x, mouseY-posV.y);
     float distance = dist(mouseX,mouseY,posV.x,posV.y);
     stroke(0,255,255);
     pushMatrix();
     translate(posV.x,posV.y);
     rotate(na*(long_dist-distance)/long_dist);
     stroke(255);
     line(-20,0,20,0);
     popMatrix();
   }
}
ArrayList<PVector> grid_pts = new ArrayList<PVector>();
ArrayList<PVector> rand_pts = new ArrayList<PVector>();

float grid_space = 20;
int numpts = 6;

void setup() {
  size(800, 800);
  
  float xpts = width/grid_space;
  float ypts = height/grid_space;
  for(int i = 0; i < xpts; ++i){
    for(int j = 0; j < ypts; ++j){
      grid_pts.add(new PVector(i*grid_space,j*grid_space,0));
    }
  }
  for(int i = 0; i < numpts; ++i){
    PVector pt_pos = grid_pts.get(int(random(grid_pts.size())));
    rand_pts.add(pt_pos);
  }
}


void draw() {
   background(0);
   for(int i = 0; i < grid_pts.size(); ++i){
     PVector posV = grid_pts.get(i);
     noStroke();
     fill(100);
     ellipse(posV.x,posV.y,5,5);
   }
   
   for(int i = 0; i < rand_pts.size(); ++i){
     PVector posV = rand_pts.get(i);
     noStroke();
     fill(255);
     ellipse(posV.x,posV.y,15,15);
   }
   
   ////////////get low and high distance//////////////
   PVector vs0 =  grid_pts.get(0);
   PVector vsn =  rand_pts.get(0);
   float l = 1/dist(vs0.x,vs0.y,vsn.x,vsn.y);
   float h = 1/dist(vs0.x,vs0.y,mouseX,mouseY);
   for(int i = 0; i < grid_pts.size(); i++){
     for(int j = 0; j < numpts; j++){
       vsn =  rand_pts.get(j);
       float d = dist(vs0.x,vs0.y,vsn.x,vsn.y);
       if(d < l){
          l = d;
       }
       if(d > h){
           h = d;
       }
     }
   }
  
   for(int i = 0; i < grid_pts.size(); ++i){
     PVector posV = grid_pts.get(i);
     
    float dv = 0;
    float tx = 0;
    float ty = 0;
    
    for(int j = 0; j < numpts+1; j++){
      if(j < numpts){
        vsn =  rand_pts.get(j);
      }else{
        vsn = (new PVector(mouseX,mouseY,0));
      }
        float d = 1/dist(posV.x,posV.y,vsn.x,vsn.y);
        float rd = map(d,l,h,0.0,1.0);
        tx = tx + vsn.x*rd*rd;
        ty = ty + vsn.y*rd*rd;
        dv = dv + rd*rd;
    }
    
    
    tx = tx/dv;
    ty = ty/dv;
    float na = -atan2(tx-posV.x, ty-posV.y);
    
    stroke(0,255,255);
    pushMatrix();
    translate(posV.x,posV.y);
    rotate(na);
    stroke(255);
    line(-20,0,20,0);
    popMatrix();
  }
}

The example below is a modification of the previous weighted average example. Rather than rotate a line the rotation is as a vector for a moving point. Each frame the point moves along that vector and the weighted average is then recalculated the next frame with that new position. As the particles move their next position is calculated based on the weighted average of the agents. This sketch was the basis for the interactive elements of Currents in IBM’s Atlanta headquarters. The interface and lighting used a similar algorithm to evoke a sense of airflow while conveying real-time air quality data.

ArrayList<PVector> grid_pts = new ArrayList<PVector>();
ArrayList<PVector> rand_pts = new ArrayList<PVector>();
ArrayList<PVector> flow_pts = new ArrayList<PVector>();

float grid_space = 20;
int numrpts = 6;

void setup() {
  size(1400, 800);
  
  float xpts = width/grid_space;
  float ypts = height/grid_space;
  for(int i = 0; i < xpts; ++i){
    for(int j = 0; j < ypts; ++j){
      grid_pts.add(new PVector(i*grid_space,j*grid_space,0));
      flow_pts.add(new PVector(i*grid_space,j*grid_space,0));
    }
  }
  for(int i = 0; i < numrpts; ++i){
    PVector pt_pos = grid_pts.get(int(random(grid_pts.size())));
    rand_pts.add(pt_pos);
  }
}

void draw() {
   //background(0);
    fill(0,0,0,5);

    noStroke();
    rect(0,0,width,height);
   for(int i = 0; i < grid_pts.size(); ++i){
     PVector posV = grid_pts.get(i);
     noStroke();
     fill(100);
     ellipse(posV.x,posV.y,5,5);
     if(dist(mouseX,mouseY,posV.x,posV.y) < 9){
      fill(0,255,255);
       ellipse(posV.x,posV.y,15,15);
     }
   }
   
   for(int i = 0; i < rand_pts.size(); ++i){
     PVector posV = rand_pts.get(i);
     noStroke();
     fill(255);
     ellipse(posV.x,posV.y,15,15);
   }
   
   ////////////get low and high distance//////////////
   PVector vs0 =  flow_pts.get(0);
   PVector vsn =  rand_pts.get(0);
   float l = 1/dist(vs0.x,vs0.y,vsn.x,vsn.y);
   float h = 1/dist(vs0.x,vs0.y,vsn.x,vsn.x);
   for(int i = 0; i < flow_pts.size(); i++){
     for(int j = 0; j < numrpts; j++){
       vsn =  rand_pts.get(j);
       float d = dist(vs0.x,vs0.y,vsn.x,vsn.y);
       if(d < l){
          l = d;
       }
       if(d > h){
           h = d;
       }
     }
   }
  
   for(int i = 0; i < flow_pts.size(); ++i){
    PVector posV =  flow_pts.get(i);
    float dv = 0;
    float tx = 0;
    float ty = 0;
    
    float dd = 99999;
    for(int j = 0; j < numrpts; j++){
        vsn =  rand_pts.get(j);
        float nd = dist(posV.x,posV.y,vsn.x,vsn.y);
        if(nd < dd){
          dd = nd;
        }
        float d = 1/dist(posV.x,posV.y,vsn.x,vsn.y);
        float rd = map(d,l,h,0.0,1.0);
        tx = tx + vsn.x*rd*rd;
        ty = ty + vsn.y*rd*rd;
        dv = dv + rd*rd;
    }
    
    
    tx = tx/dv;
    ty = ty/dv;
    float na = -atan2(tx-posV.x, ty-posV.y);
    stroke(255);
    strokeWeight(2);
    point(posV.x,posV.y);
    float nx = posV.x;
    float ny = posV.y;
    
    nx = nx + 1 * cos(na);
    ny = ny + 1 *sin(na);
    flow_pts.set(i,new PVector(nx,ny,0));
  }
}

void mouseClicked() {
  for(int i = 0; i < (grid_pts.size()); ++i){
    PVector vs =  grid_pts.get(i);
    if(dist(mouseX,mouseY,vs.x,vs.y) < 9){
      rand_pts.remove(0);
      rand_pts.add(new PVector(vs.x,vs.y,0));
      println("XXX");
    }
  }
}

void keyReleased() {
  if (key == ' '){
    for(int i = 0; i < flow_pts.size(); ++i){
      PVector vs =  grid_pts.get(i);
      flow_pts.set(i, new PVector(vs.x,vs.y,0));
    }
  }
}