Hair Particle Drawing Project – OpenFrameWorks version

Meret Oppenheim hair particle portrait

Meret Oppenheim hair particle portrait final

For the new Openframworks version of my old hair particle drawing project I have chosen to switch gears from the politcal satire for now and I have chosen a new subject for this exploration. I decided to do a large portrait of Meret Oppenheim. Oppenheim was the the artist who made the very famous fur lined teacup. The photo this image is sampled from was taken in 1937 by Man Ray.

hair particle sketch of Meret Oppenheim from Don Relyea on Vimeo.

The below sketches and the above video are early tests. I went through several iterations before I got it just right. In addition to the sketches below I posted some others in a set in my flickr account.

Big thanks to Bruce Sterling for mentioning this project in his Wired blog, Beyond the Beyond!

meret oppenheim sketch openframeworks Meret Oppenheim Sketch openframeworks meret oppenheim ksetch openframeworks
various Sketches of Meret Oppenheim in OF
Click on the above thumbnails to enlarge

Source Code
So you can get an idea of how this works I have included the main source code for the hair particle class with comments. The class is pretty simple and works well. In the update function of testapp I am just poll an array of particles to see which ones are active and which ones are not. The ones that are not active get assigned to draw a new hair particle.

I was really surprised how easy it was to port this project over from Director. I was even more surprised how much faster it runs in OF. The performance boost moving to OF is truly impressive.

   1:  #include "hair_particle.h"
   2:  // hair particle drawing class by don relyea
   3:  // www.donrelyea.com
   4:  // July 22 2008
   5:
   6:  hair_particle::hair_particle(void){
   7:       pActive=false; //init particle not active
   8:  }
   9:
  10:  hair_particle::~hair_particle(void){
  11:  }
  12:  void hair_particle::update_frame(){
  13:
  14:      //if im active lets draw
  15:     if (pActive=true){
  16:      domyangle();  //decide which way to grow
  17:      doforward(); //grow
  18:      pScale=pScale-0.125; //Decay the scale
  19:
  20:      if  (pScale < 1){ //keep it at least 1 pixel size
  21:        pScale=1;
  22:      }
  23:
  24:      pBlend=pBlend-pDecay; //Decay the blend
  25:
  26:      //Draw Routine here
  27:      ofSetColor(pBorW,pBorW,pBorW,pBlend); //set color and alpha blend
  28:      ofEnableAlphaBlending(); //enable alpha blending
  29:      ofCircle(myX,myY, pScale); //draw a circle
  30:      ofDisableAlphaBlending(); //disable alpha blending
  31:
  32:      //reset if particle gets so transparent as to not be visible
  33:      if  (pBlend < 1){
  34:        reset_me(); //reset particle
  35:      }
  36:     }
  37:  }
  38:  void hair_particle::reset_me(){
  39:      //reset so we can recyle the particle
  40:      //why have in sep function?
  41:      //Bcuz we might want to be able to kill particles by calling this function
  42:       pActive=false;
  43:  }
  44:  bool hair_particle::active(){
  45:      // am i busy or ready for a new assignment
  46:       return pActive;
  47:  }
  48:  void hair_particle::doforward(){
  49:   //making progress every frame
  50:   myX = myX +cos(myAngle)*myspeed;
  51:   myY = myY +sin(myAngle)*myspeed;
  52:
  53:  }
  54:  void hair_particle::domyangle(){
  55:      //which way am I going during my brief existence
  56:      mydelta=mydelta+ofRandom(-myKink,myKink);
  57:
  58:      float adelta = mydelta/57.2958;
  59:
  60:      myAngle = myAngle +adelta; //increment the angle
  61:
  62:      //keep the angle between 0 and 360
  63:      if (myAngle > 360){
  64:          myAngle=  myAngle-360;
  65:      }
  66:      if (myAngle < 0){
  67:          myAngle=  myAngle+360;
  68:      }
  69:
  70:  }
  71:  void hair_particle::draw_me( float ablend, float asize, float aloch, float alocv){
  72:      //----this code initializes the particle------//
  73:      pActive=true; //its alive!
  74:      myKink=ofRandom(0,3); //Kinky is better =)
  75:      myX = aloch; //give the particle a starting point
  76:      myY = alocv; //notice veiled reference to old director lingo lol
  77:      myAngle = ofRandom(0,360); //grow hair in random direction
  78:      myspeed = 1; //move one pixel a frame
  79:
  80:      if (ablend > 210){
  81:          //pBorW=0; //black hair, pBorW=255; for white hair
  82:          pBorW=255-ablend;  //greyscale hair
  83:          pScale=asize*3.50;  //root of hair is thicker, drypoint print effect
  84:          pDecay=1.95; // how fast the particle decays
  85:      } else if(ablend > 100 && ablend < 210){
  86:          //pBorW=0;
  87:          pBorW=(255-ablend)/2; //greyscale hair tweaked darker
  88:          pScale=asize*1.25;
  89:          pDecay=1.25;
  90:      } else if(ablend > 65 && ablend < 100){
  91:          //pBorW=0;
  92:          pBorW=(255-ablend)/3; //greyscale hair tweaked darker
  93:          pScale=asize*0.45;
  94:          pDecay=0.85;
  95:      } else if(ablend > 35 && ablend < 65){
  96:          //pBorW=255;
  97:          pBorW=(255-ablend)/4; //greyscale hair tweaked darker
  98:          pScale=asize*0.35;
  99:          pDecay=0.85;
 100:      } else if(ablend > 2 && ablend < 35){
 101:          //pBorW=255;
 102:          pBorW=(255-ablend)/5; //greyscale hair tweaked darker
 103:          pScale=asize*0.35;
 104:          pDecay=0.85;
 105:      } else {
 106:          pBorW=0;
 107:          pScale=0.15;
 108:          pDecay=0.85;
 109:      }
 110:
 111:    pBlend=ablend; //set the starting the alpha blend this will be reduced by the pDecay
 112:    mydelta = ofRandom(-10,10); //initial hair kink
 113:
 114:  }