Perfume Global #prfm_global_site

Standard

This is part of promotion for Japanese Electro-pop group, Perfume. Basically Perfume’s marketing team offers motion capture data for their dancing routine and also a parser for the mocap data.

What I did next is to use Processing to parse and show the visualization of it.

You can download my source files from here: https://www.dropbox.com/s/el1s4nrskljvqxu/PerfumeGlobal.zip

It requires PeasyCam library for processing which basically implements trackball rotation camera. Get PeasyCam here: http://mrfeinberg.com/peasycam/

Have fun with it !

PerfumeGlobal: main class in Processing

import peasy.*;
import krister.Ess.*;

/* trackball camera */
PeasyCam cam;

/* Instances from ESS library to obtain the audio and perform Fast Fourier Transform on it */
AudioChannel audio_channel;
FFT fft;
color [] spectograph_colors = new color[32];

GlobalManager global = new GlobalManager();
int start;
boolean play = true;

public void setup() {
 size( 800, 600, P3D );

/* Randomize colors for the equalizer (spectograph) running on the back */
 for ( int i = 0; i < 32; i++ )
 spectograph_colors[i] = color( random(255), random(255), random(255), 200 );

textFont( createFont( "Arial", 24 ), 12 );
 Ess.start(this);

/* init the trackball camera */
 cam = new PeasyCam( this, width / 2.0f, height / 2.0f - 100.0f, 0, 500 );
 cam.setFreeRotationMode();

/* Read in the mocap data */
 global.init();

audio_channel = new AudioChannel( "Perfume_globalsite_sound.wav" );
 fft = new FFT( 64 );

frameRate( 120 );

loop();
}

public void draw() {
 background( 0 );

if ( !play )
 return;

/* update each skeleton based on the timing */
 global.update( millis() - start );

/* draw the background equalizer */
 fft.getSpectrum( audio_channel );
 pushMatrix();
 pushStyle();
 translate( 10.0f, 0.0f, -380.0f );
 for ( int i = 0; i < 32; i++ ) {
 fill( spectograph_colors[i] );
 float temp = max( 0, 180 - fft.spectrum[i] * 175 );
 rect( i * 25, temp, 20, height / 2.0f - temp + 0.5 );
 }
 popStyle();
 popMatrix();

/* draw the ground */
 pushMatrix();
 fill( 255, 255, 255, 200 );
 noStroke();
 translate( width / 2.0f, height / 2.0f + 15, 0.0f );
 box( 800, 5, 800 );
 popMatrix();

/* draw each dancing figures */
 pushMatrix();
 translate( width/2, height/2-10, 0);
 scale(-1, -1, -1);
 global.draw( cam.getRotations() );
 popMatrix();
}

public void stop() {
 Ess.stop();
 super.stop();
}

void keyPressed() {
 if ( keyCode == 32 ) {
 play = !play;
 start = millis();

if ( play )
 audio_channel.play();
 else
 audio_channel.stop();
 }
}

Skeleton: each Skeleton represents a stick figure

/* Each skeleton represents one stick figure */
public class Skeleton {
 private BvhParser parser;
 private color skeletonColor;
 private String id;

public Skeleton( String id, color skeleton_color, String filename ) {
 /* Instantiate parser and parse the mocap data */
 parser = new BvhParser();
 parser.init();
 parser.parse( loadStrings( filename ) );

this.id = id;
 this.skeletonColor = skeleton_color;
 }

private BvhParser getParser() {
 return parser;
 }

/* Update the current motion of the skeleton based on the timing */
 public void update( int ms ) {
 parser.moveMsTo( ms );
 parser.update();
 }

public void draw( float [] rotations ) {
 fill( this.skeletonColor );
 stroke( this.skeletonColor );

BvhBone prev = null;
 /* To determine the size of circular shadow */
 float min_x = 9999;
 float min_z = 9999;
 float max_x = -9999;
 float max_z = -9999;
 for ( BvhBone bone : parser.getBones() ) {
 /* draw lines between joints */
 pushMatrix();
 if ( prev != null ) {
 strokeWeight( 6 );
 line( bone.absPos.x, bone.absPos.y, bone.absPos.z,
 prev.absPos.x, prev.absPos.y, prev.absPos.z);
 }

 strokeWeight( 4 );
 translate( bone.absPos.x, bone.absPos.y, bone.absPos.z );
 ellipse( 0, 0, 10, 10 );
 popMatrix();

if ( !bone.hasChildren() ) {
 int bone_size = 15;

 /* if it's head, draw it big enough */
 if ( "Head".equals( bone.getName() ) ) {
 bone_size = 30;

pushMatrix();
 translate( bone.absEndPos.x + 20, bone.absEndPos.y + 20, bone.absEndPos.z );
 rotateX( rotations[0] );
 rotateY( rotations[1] );
 rotateZ( rotations[2] );
 scale(-1, -1, -1);
 text(id, 0, 0, 0 );
 popMatrix();
 }

/* draw the joints as circles (including head) */
 pushMatrix();
 translate( bone.absEndPos.x, bone.absEndPos.y, bone.absEndPos.z );
 ellipse( 0, 0, bone_size, bone_size );
 popMatrix();

prev = null;
 }
 else
 prev = bone;

/* update the size and position of the shadow */
 if ( min_x > bone.absPos.x )
 min_x = bone.absPos.x;
 if ( min_z > bone.absPos.z )
 min_z = bone.absPos.z;

if ( max_x < bone.absPos.x )
 max_x = bone.absPos.x;
 if ( max_z < bone.absPos.z )
 max_z = bone.absPos.z;
 }

/* draw the shadow */
 pushMatrix();
 noStroke();
 fill( 100, 100, 100, 200 );
 translate( (max_x + min_x) / 2.0f, -8, (max_z + min_z) / 2.0f );
 rotateX( radians(90) );
 ellipse( 0, 0, (max_x - min_x + 15), (max_z - min_z + 15) );
 popMatrix();
 }
}

GlobalManager: convenient class to manage all the Skeletons

import java.util.*;

/* Simple convenient class to encapsulate all the Skeletons */
public class GlobalManager {
 private List<Skeleton> skeletons = new ArrayList<Skeleton>();

/* Init by reading all the mocap data */
 public void init() {
 skeletons.add( new Skeleton( "aachan", color(153, 153, 255), "aachan.bvh" ) );
 skeletons.add( new Skeleton( "kashiyuka", color(153, 255, 153), "kashiyuka.bvh" ) );
 skeletons.add( new Skeleton( "nocchi", color(255, 153, 153), "nocchi.bvh" ) );
 }

/* Update each stick figures' motion based on current time */
 public void update( int ms ) {
 for ( Skeleton skeleton : skeletons )
 skeleton.update( ms );
 }

/* Draw each figures on screen */
 public void draw( float [] rotations ) {
 for ( Skeleton skeleton : skeletons )
 skeleton.draw( rotations );
 }
}