Jaqaur – MoCap

PIXEL DANCER

For this project, I knew from the start I wanted the motion capture data for my project to be my friend Katia dancing ballet. We actually recorded that data before I had coded much of anything for this project. Hopefully I can record some more different types of motion and apply this animation to them in the future.

Anyhow, for this project, I wanted something a little more abstract looking than shapes attached to a skeleton. So I decided to make an animation in which nothing actually moves. There is a 3D grid of “pixels” (which can be any shape, color, or size) that choose their size and/or opacity based on whether or not they occupy the space where the dancer is. They appear and disappear, and collectively this creates the figure of a person and the illusion of movement.

I decided to work in Processing, because I had the most experience in it, but 3D was still new to me. Initially, I had my pixels calculate their distance from each joint and decide how big to be based on that. It worked, but was just a series of sphere-ish clumps of pixels moving around, and I wanted it to look less default-y and more like a real person. So, I looked up how to calculate the distance from a point to a line segment, and used that for my distance formula instead (making line segments out of the connections between joints). This resulted in a sort of 3D stick figure that I was pretty happy with.

I played around a lot with different shapes, sizes, and colors for the pixels. I also tried to find the best speed for them to appear and disappear, but this was hard to do. Different people I showed it to had different opinions on how long the pixels should last. Some really liked it when they lasted a long time, because it looked more interesting and abstract, but others liked the pixels to disappear quickly so that the dancer’s figure was not obscured. Deciding how quickly the pixels should appear was less difficult. While I initially wanted them to fade in somewhat slowly, this did not look good at all. The skeleton simply moved too fast for the pixels ever to reach full size/opacity, so it was hard to tell what was going on. As a result, I made the pixels pop into existence, and I think that looks as good as it could. The motion capture data still looks a bit jumpy in places, but I think that’s the data and not the animation.

Since there was such a wide variety in the types of pixels I could use for this project, I decided to make a whole bunch of them. Here are how some of my favorites look.

The original pink cube pixels:
dance_mocap

Like the original, but with spheres instead of cubes (and they’re blue!):
teal_mocap

Back to cubes, but this time, they fade out instead of shrinking out. I think it looks sort of flame-like:
fire_mocap

Back to shrinking out, but the cubes’ colors change. I know rainbows are sort of obnoxious, but I thought it was worth a shot. I also played with some extreme camera angles on this one:
rainbow_mocap

One final example, pretty much the opposite of the last one. Spheres, with a fixed color, that fade out. I think it looks kind of like smoke, especially from a distance. But I like how it looks up close, too:
white_mocap

I didn’t really know how to sketch this concept, so I didn’t (and I’m kind of hoping that all of my variations above can make up for my somewhat lacking documentation of the process). In general, I’m happy with how this turned out, but I wish I had had the time to code this before we recorded any motion, so I could really tailor the movement to the animation. Like I said, I hope to do more with this project in the future, because I am happy with how it turned out. Maybe I can make a little music video…

Here is a link to my code on github (the pink cube version): https://github.com/JacquiwithaQ/Interactivity-and-Computation/tree/master/Pixel_Dancer

And here is my code. I am only embedding the files I edited, which do not include the parser.

//Adapted by Jacqui Fashimpaur from in-class example

BvhParser parserA = new BvhParser();
PBvh bvh1, bvh2, bvh3;
final int maxSide = 200;

ArrayList allPieces;
	
public void setup()
{
  size( 1280, 720, P3D );
  background(0);
  noStroke();
  frameRate( 70 );
  //noSmooth();
  
  bvh1 = new PBvh( loadStrings( "Katia_Dance_1_body1.bvh" ) );
  allPieces = new ArrayList();
  for (int x=-400; x<100; x+=8){
    for (int y=-50; y<500; y+=8){
       for (int z=-400; z<100; z+=8){
         Piece myPiece = new Piece(x,y,z,bvh1);
         allPieces.add(myPiece);
       }
    }
  }
  loop();
}

public void draw()
{
  background(0);
  float t = millis()/5000.0f;
  float xCenter = width/2.0 + 150;
  float zCenter = 300;
  float camX = (xCenter - 200);// + 400*cos(t));
  float camZ = (zCenter + 400 + 300*sin(t));
  //moving camera
  camera(camX, height/2.0 - 200, camZ, width/2.0 + 150, height/2.0 - 200, 300, 0, 1, 0);
  //still camera
  //camera(xCenter, height/2.0 - 300, -300, width/2.0 + 150, height/2.0 - 200, 300, 0, 1, 0);
  
  pushMatrix();
  translate( width/2, height/2-10, 0);
  scale(-1, -1, -1);
 
  ambientLight(250, 250, 250);
  bvh1.update( millis() );
  //bvh1.draw();
  for (int i=0; i<allPieces.size(); i++){
    Piece p = allPieces.get(i);
    p.draw();
  }
  popMatrix();
}
//This code by Jacqui Fashimpaur for Golan Levin's class
//November 2016

public class Piece {
  float xPos;
  float yPos;
  float zPos;
  float side;
  PBvh bones;

  public Piece(float startX, float startY, float startZ, PBvh bone_file) {
    xPos = startX;
    yPos = startY;
    zPos = startZ;
    side = 0.01;
    bones = bone_file;
  }

  void draw() {
    set_side();
    if (side > 0.01) {
      noStroke();
      fill(255, 255, 255, side);
      translate(xPos, yPos, zPos);
      sphereDetail(5);
      sphere(9);
      translate(-xPos, -yPos, -zPos);
    }
  }

  void set_side() {

    //LINE-BASED FIGURE IMPLEMENTATION
    float head_dist = get_dist(bones.parser.getBones().get(48));
    float left_shin_dist = get_line_dist(bones.parser.getBones().get(5), bones.parser.getBones().get(6));
    float right_shin_dist = get_line_dist(bones.parser.getBones().get(3), bones.parser.getBones().get(2));
    float left_thigh_dist = get_line_dist(bones.parser.getBones().get(5), bones.parser.getBones().get(4));
    float right_thigh_dist = get_line_dist(bones.parser.getBones().get(3), bones.parser.getBones().get(4));
    float left_forearm_dist = get_line_dist(bones.parser.getBones().get(30), bones.parser.getBones().get(31));
    float right_forearm_dist = get_line_dist(bones.parser.getBones().get(11), bones.parser.getBones().get(12));
    float left_arm_dist = get_line_dist(bones.parser.getBones().get(29), bones.parser.getBones().get(30));
    float right_arm_dist = get_line_dist(bones.parser.getBones().get(10), bones.parser.getBones().get(11));
    float torso_dist = get_line_dist(bones.parser.getBones().get(0), bones.parser.getBones().get(8));

    boolean close_enough = ((head_dist<700) || (left_shin_dist<100) || (right_shin_dist<100) ||
                            (left_thigh_dist<150) || (right_thigh_dist<150) || (left_forearm_dist<100) ||
                            (right_forearm_dist<100) || (left_arm_dist<150) || (right_arm_dist<150) ||
                            (torso_dist<370));
  
    //LINE-BASED OR POINT-ONLY IMPLEMENTATION
    if (!close_enough) {
      side *= 0.91;
    } else {
      //side *= 200;
      side = maxSide;
    }
    /*if (side < 0.01) {
      side = 0.01;
    }*/
    if (side < 1){ side = 0.01; } if (side >= maxSide){
      side = maxSide;
    }
  } 

  float get_dist(BvhBone b) {
    float x1 = b.absPos.x;
    float y1 = b.absPos.y;
    float z1 = b.absPos.z;
    float dist1 = abs(x1-xPos);
    float dist2 = abs(y1-yPos);
    float dist3 = abs(z1-zPos);
    return (dist1*dist1)+(dist2*dist2)+(dist3*dist3);
  }

  float get_line_dist(BvhBone b1, BvhBone b2) {
    float x1 = b1.absPos.x;
    float y1 = b1.absPos.y;
    float z1 = b1.absPos.z;
    float x2 = b2.absPos.x;
    float y2 = b2.absPos.y;
    float z2 = b2.absPos.z;
    float x3 = xPos;
    float y3 = yPos;
    float z3 = zPos;
    float dx = abs(x1-x2);
    float dy = abs(y1-y2);
    float dz = abs(z1-z2);
    float otherDist = sq(dx)+sq(dy)+sq(dz);
    if (otherDist == 0) otherDist = 0.001;
    float u = (((x3 - x1)*(x2 - x1)) + ((y3 - y1)*(y2 - y1)) + ((z3 - z1)*(z2 - z1)))/otherDist;
    if ((u >=0) && (u <= 1)) {
      float x = x1 + u*(x2 - x1);
      float y = y1 + u*(y2 - y1);
      float z = z1 + u*(z2 - z1);
      float dist4 = abs(x - xPos);
      float dist5 = abs(y - yPos);
      float dist6 = abs(z - zPos);
      return sq(dist4) + sq(dist5) + sq(dist6);
    }
    return 999999;
  }

  float getRed() {
    //FOR PINK 1: 
    return map(xPos, -400, 100, 100, 200);
    //FOR TEAL: return map(yPos, 350, 0, 2, 250);
    /* FOR RAINBOW:
    if ((millis()%30000) < 18000){
      return map((millis()%30000), 0, 18000, 255, 0);
    } else if ((millis()%30000) < 20000){
      return 0;
    } else {
      return map((millis()%30000), 20000, 30000, 0, 255);
    } */
    return 255;
  }

  float getGreen() {
    //return map(xPos, -400, 100, 50, 150);
    //FOR PINK 1: 
    return 100;
    //FOR TEAL: return map(yPos, 350, 0, 132, 255);
    /* FOR RAINBOW:
    if ((millis()%30000) < 18000){
      return map((millis()%30000), 0, 18000, 0, 255);
    } else if ((millis()%30000) < 20000){
      return map((millis()%30000), 18000, 20000, 255, 0);
    } else {
      return 0;
    } */
    return 255;
  }

  float getBlue() {
    //FOR PINK 1: 
    return map(yPos, -50, 600, 250, 50);
    //FOR TEAL: return map(yPos, 350, 0, 130, 255);
    /* FOR RAINBOW:
    if (millis()%30000 < 18000){
      return 0;
    } else if ((millis()%30000) < 20000){
      return map((millis()%30000), 18000, 20000, 0, 255);
    } else {
      return map((millis()%30000), 20000, 30000, 255, 0);
    } */
    return 255;
  }
}