previous sketch Arrow Hands next sketch

Click and drag the yellow dot!

The hands rotate to point at the yellow dot. As each one points more towards the dot, it opens from a fist into a pointing hand. The hand sizes and locations are random: if you re-load the page, you'll get a new assortment of hands. Note that the bigger hands rotate more slowly than the small hands.

To make the source photos, I just started up my webcam one day, and recorded my hand going from a fist to a pointing finger. Then I cut away the background in Photoshop and saved the images. You could easily replace these pictures with any other kind of animation that pleases you.

The Program

Arrow[] ArrowList;                      // list of Arrows
color TargetColor = color(255, 255, 0); // target is yellow
float TargetX, TargetY;                 // location of target

void setup() {
  size(600, 500);                    // make graphics window
  smooth();                          // draw things nicely
  TargetX = width * random(.2, .8);  // start target randomly
  TargetY = height * random(.2, .8);
  ArrowList = new Arrow[15];  // create the array of Arrow objects
  for (int i=0; i<ArrowList.length; i++) {
    // roll up some random numbers to specify a new arrow
    float minR = width*.02;
    float maxR = width*.1;
    float radius = random(minR, maxR);
    float changeSpeed = map(radius, minR, maxR, .1, .01);
    float centerx = random(radius, width-radius);
    float centery = random(radius, height-radius);
    float startAngle = random(0, TWO_PI);
    // make the new arrow
    ArrowList[i] = new Arrow(centerx, centery, radius, changeSpeed, startAngle);
  }
}

void draw() {
  // Draw the arrows and target. If the target's moving, update the arrows.
  background(200);
  for (int i=0; i<ArrowList.length; i++) {
    ArrowList[i].render();
  }
  drawTarget();
  if (mousePressed) updateArrows();
}

void drawTarget() {  // draw the target circle
  stroke(0);
  fill(TargetColor);
  ellipse(TargetX, TargetY, 40, 40);
}

void mousePressed() { setTargetXY(); }
void mouseDragged() { setTargetXY(); }

void setTargetXY() { 
  TargetX = mouseX;  
  TargetY = mouseY; 
}

void updateArrows() {  
  for (int i=0; i<ArrowList.length; i++) {
    ArrowList[i].update(TargetX, TargetY);
  }
}

// A class to hold an arrow object.  
PImage[] ArrowHands;       // images of my hand from fist to pointing
boolean GotHands = false;  // did we read the hand images yet?

class Arrow {
  PVector center;                   // center of arrow
  float radius, angle, changeSpeed; // size, orientation, spin speed
  color clr;                        // color at this moment
  int imageIndex;                   // where we are in the animation

  // construct a new Arrow
  Arrow(float startX, float startY, float startRadius, 
      float startChangeSpeed, float startAngle) {
  center = new PVector(startX, startY);
  radius = startRadius;
  angle = startAngle;
  changeSpeed = startChangeSpeed;
  clr = color(255);
  imageIndex = 0;
  if (!GotHands) {  // read in the hand photos only once
    GotHands = true;
      ArrowHands = new PImage[18];
      for (int i=0; i<ArrowHands.length; i++) {
        String name = "arrowHandPix/hand"+nf(i, 2)+".png"; 
        ArrowHands[i] = loadImage(name);
      }
    }
  }

  void render() {
    fill(clr);
    noStroke();
    pushMatrix();
      translate(center.x, center.y);   // move to place arrow on screen
      scale(radius/120.0);  // Picked by eye to make nicely-sized hands
      rotate(angle);        // spin in place to point at the target
	  // photos are 397x191, so this next line centers them 
      image(ArrowHands[imageIndex], -199, -95);  
    popMatrix();
  }
  
  void update(float goalX, float goalY) {
    float newAngle = atan2(goalY-center.y, goalX-center.x);
    // adjust the angles so the arrow turns either clockwise or
    // counter-clockwise, depending on which way gets to the goal faster
    while (angle < 0) angle += TWO_PI;
    while (newAngle  < 0) newAngle  += TWO_PI;
    float diffAngle = abs(angle - newAngle);
    if (diffAngle > PI) {
      if (newAngle > angle) newAngle -= TWO_PI;
                       else angle -= TWO_PI;
     }    
     float speed = changeSpeed;
     speed = constrain(speed, .05, .3);
     angle = lerp(angle, newAngle, speed);
     float angleMismatch = abs(angle-newAngle);      // how far off are we?
     angleMismatch = constrain(angleMismatch, 0, 1); 
     // use the angle mismatch to make an index into the image array
     imageIndex = int(ArrowHands.length * (1-angleMismatch));
     imageIndex = constrain(imageIndex, 0, ArrowHands.length-1);
   }
}