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 ); } }