workshop/perfume_and_bvh/main


Processing & Perfume

このページでは,Perfume Global Siteから提供されているperfumeのモーションデータを読み込み, Processingで表示させる手順を紹介します.手順は次のとおりになります.

  1. Perfume Global Siteから Processingサンプル,モーションデータ(BVH),音楽をダウンロード
  2. Processingサンプルをダウンロードしたモーションデータと音楽で実行するように修正する

1. Perfume Global Siteから Processingサンプル,モーションデータ(BVH),音楽をダウンロード

ではまず,Perfume Global SiteからProcessingサンプル,モーションデータ,音楽をダウンロードしましょう

次にp5f_sampleフォルダの以下フォルダに次のファイルを追加します.
  • aachan.bvh, kashiyuka.bvh, nocchi.bvhをdataフォルダに追加
  • Perfume_globalsite_sound.wavをdataフォルダに追加

2. Processingサンプルをダウンロードしたモーションデータと音楽で実行するように修正する

では最後にp5f_sample.pdeをProcessingで開き,最初から setup()関数の終わりまで,次のコードに置き換えます.

import ddf.minim.*;

PBvh bvh1, bvh2, bvh3;
	  
Minim minim;
AudioPlayer player;

public void setup()
{
  size( 1280, 720, P3D );
  background( 0 );
  noStroke();
  frameRate( 60 );

  bvh1 = new PBvh( loadStrings( "aachan.bvh" ) );
  bvh2 = new PBvh( loadStrings( "kashiyuka.bvh" ) );
  bvh3 = new PBvh( loadStrings( "nocchi.bvh" ) );

 minim = new Minim(this);
  player = minim.loadFile("Perfume_globalsite_sound.wav", 2048);
  player.play(); //再生
  loop();
}
ここまでできたら実行してみましょう.

モーションの頂点を取得する

ここで,各ファイルにおけるモーションデータは白丸で画面上に表示されています. そこで,この頂点位置を様々な形て表示し,Perfumeの動きを表現してみましょう. 実際にモーションを描画している部分はPBvhファイルのdraw()を参照してください.元々のサンプルでは 各頂点を拡張for分を利用して配列から取得しています,まずはこのfor分を利用して,一旦各配列に値を代入し直してみます. ただし,頭,手,足,のシンプルな点のみに限定します.

PBvh.pdeのpublic void draw()部分
public void draw()
  {
    float[] pos_x = new float[5];
    float[] pos_y = new float[5];
    float[] pos_z = new float[5];
    int count = 0;
    fill(color(255));

    for ( BvhBone b : parser.getBones())
    {
      if (!b.hasChildren()) {
        pos_x[count] = b.absEndPos.x;
        pos_y[count] = b.absEndPos.y;
        pos_z[count] = b.absEndPos.z;
        count=count+1;
      }
    }

    for ( int i = 0; i < count; i++ ) {
      pushMatrix();
      translate(pos_x[i], pos_y[i], pos_z[i]);
      ellipse(0, 0, 10, 10);
      popMatrix();
    }
  }
 

上記コードにて,各頂点がpos_x,pos_y,pos_zの配列に代入されました.この座標データを利用して,グラフィック描画を変化させていきます.

モーションデータを利用しながら,人の動きに見えないようなグラフィック表現に変更してください.

https://processing.org/reference/を参照して,Processingで利用可能な様々なグラフィック表示を適用してみましょう.例えば sphere()関数を使って各頂点を3次元オブジェクトで大きく描画してみたり,ベジェ曲線や直線等で各頂点を描画するなどするこで,同じデータが様々な表現が可能であることを確認しましょう.

各5頂点を線でつなげて表示してみました.PBvh.pdeのpublic void draw()部分を変更してください.

 public void draw()
  {
    float[] pos_x = new float[23];
    float[] pos_y = new float[23];
    float[] pos_z = new float[23];
    int count = 0;
    fill(color(255));

    for ( BvhBone b : parser.getBones())
    {
      if (!b.hasChildren()) {
        pos_x[count] = b.absEndPos.x;
        pos_y[count] = b.absEndPos.y;
        pos_z[count] = b.absEndPos.z;
        count=count+1;
      }
    }
    noFill();
    beginShape();
    for ( int i = 0; i < count; i++ ) {
      stroke(255, 255, 255, 2555);
      strokeWeight(10);
      vertex(pos_x[i], pos_y[i], pos_z[i]);
    }
    endShape(CLOSE);
  }
  

上記のサンプルから更に,各クラスに色付けをし,背景更新をとめ,頂点座標の線描画にアルファ値を持たせることで,下記のようなグラフィックを描くことができました.

p5f_sample
  import ddf.minim.*;
PBvh bvh1, bvh2, bvh3;

Minim minim;
AudioPlayer player;

public void setup()
{
  size( 1280, 720, P3D );
  smooth();
  background( 255 );
  noStroke();
  frameRate( 60 );

  bvh1 = new PBvh( loadStrings( "aachan.bvh" ), color(255, 0, 0, 10));
  bvh2 = new PBvh( loadStrings( "kashiyuka.bvh" ), color(0, 255, 0, 10) );
  bvh3 = new PBvh( loadStrings( "nocchi.bvh" ), color(0, 0, 255, 10));

  minim = new Minim(this);
  player = minim.loadFile("Perfume_globalsite_sound.wav", 2048);
  player.play(); //再生
  loop();
}

public void draw()
{
  //camera
  float _cos = cos(millis() / 5000.f);
  float _sin = sin(millis() / 5000.f);
  camera(width/4.f + width/4.f * _cos +200, height/2.0f-100, 550 + 150 * _sin, width/2.0f, height/2.0f, -400, 0, 1, 0);

  pushMatrix();
  translate( width/2, height/2-10, 0);
  scale(-1, -1, -1);

  //model
  bvh1.update( millis() );
  bvh2.update( millis() );
  bvh3.update( millis() );
  bvh1.draw();
  bvh2.draw();
  bvh3.draw();
  popMatrix();

}

  
PBvh
  public class PBvh
{
  public BvhParser parser;  
  color c;
  public PBvh(String[] data, color _c)
  {
    parser = new BvhParser();
    parser.init();
    parser.parse( data );
    c = _c;
  }
  
  public void update( int ms )
  {
    parser.moveMsTo( ms );//30-sec loop 
    parser.update();
  }
  
 public void draw()
  {
    float[] pos_x = new float[23];
    float[] pos_y = new float[23];
    float[] pos_z = new float[23];
    int count = 0;
    fill(color(255));

    for ( BvhBone b : parser.getBones())
    {
      if (!b.hasChildren()) {
        pos_x[count] = b.absEndPos.x;
        pos_y[count] = b.absEndPos.y;
        pos_z[count] = b.absEndPos.z;
        count=count+1;
      }
    }
    noFill();
    beginShape();
    for ( int i = 0; i < count; i++ ) {
      stroke(c);
      strokeWeight(1);
      vertex(pos_x[i], pos_y[i], pos_z[i]);
     }
    endShape(CLOSE);
  }
}
  
プログラムを実行した際のスクリーンショット

応用例:楽曲のビートに合わせる

FFTを利用して,低周波成分の平均レベルを参照し,しきい値を決めて,そのタイミングで 背景色をランダム更新しています.ビートと動機して背景色が変わっていきます.

p5f_sample
BvhParser parserA = new BvhParser();
PBvh bvh1, bvh2, bvh3;
import ddf.minim.analysis.*;
import ddf.minim.*;

Minim minim;
FFT fftLin;
AudioPlayer player;

public void setup()
{
  size( 1280, 720, P3D );
  background( 255 );
  noStroke();
  stroke(0);
  fill(0);
  frameRate( 30 );

  bvh1 = new PBvh( loadStrings( "aachan.bvh"), 1 );
  bvh2 = new PBvh( loadStrings( "kashiyuka.bvh"),2 );
  bvh3 = new PBvh( loadStrings( "nocchi.bvh"),3 );


  minim = new Minim(this);
  player = minim.loadFile("Perfume_globalsite_sound.wav");
  player.play();

  fftLin = new FFT( player.bufferSize(), player.sampleRate() );
  fftLin.linAverages( 30 );

  loop();
}

public void draw()
{
  //camera
  float _cos = cos(millis() / 5000.f);
  float _sin = sin(millis() / 5000.f);
  camera(width/4.f + width/4.f * _cos +200, height/2.0f-100, 550 + 150 * _sin, width/2.0f, height/2.0f, -400, 0, 1, 0);
  pushMatrix();
  translate( width/2, height/2-10, 0);
  scale(-1, -1, -1);

  // perform a forward FFT on the samples in jingle's mix buffer
  // note that if jingle were a MONO file, this would be the same as using jingle.left or jingle.right
  fftLin.forward( player.mix );
  
  if( fftLin.getAvg(0) > 60 ){
    background(random(255), random(255), random(255));
  }
  //model
  bvh1.update( millis() );
  bvh2.update( millis() );
  bvh3.update( millis() );
  bvh1.draw();
  bvh2.draw();
  bvh3.draw();
  popMatrix();
}

PBvh
int leap = 30; //how far the point travels each iteration also controls opacity

public class PBvh
{

  public BvhParser parser;  
  public int id;
  public PBvh(String[] data, int number)
  {
    parser = new BvhParser();
    parser.init();
    parser.parse( data );
    id = number;
  }
  
  public void update( int ms )
  {
    parser.moveMsTo( ms );//30-sec loop 
    parser.update();
  }
  
  public void draw()
  {
    float[] pos_x = new float[5];
    float[] pos_y = new float[5];
    float[] pos_z = new float[5];
    int count = 0;
    
    fill(color(255));
     
    for( BvhBone b : parser.getBones())
    {
      pushMatrix();
      translate(b.absPos.x, b.absPos.y, b.absPos.z);
      //ellipse(0, 0, 2, 2);
      popMatrix();
      if (!b.hasChildren())
      {
        pushMatrix();
        translate( b.absEndPos.x, b.absEndPos.y, b.absEndPos.z);
        //ellipse(0, 0, 10, 10);
        popMatrix();
        pos_x[count] = b.absEndPos.x;
        pos_y[count] = b.absEndPos.y;
        pos_z[count] = b.absEndPos.z;
        count=count+1;
      }
        
      noFill();
      beginShape();
      for( int i = 0; i  < count; i++ ){
        if( id == 1 ){
         stroke(205-i, 0, 0, 5);
        }
        else if( id == 2 ){
           stroke(0,205-i,0 ,5);
        }
        else if( id == 3 ){
           stroke(0, 0, 205-i, 5);
        }        
          strokeWeight(random(leap));
        for( int j = 0; j < 30; j++ ){         
          vertex(pos_x[i]+random(-leap,leap), pos_y[i]+random(-leap, leap), pos_z[i]+random(-leap,leap));
        }
      }
      endShape(CLOSE);      
    }
  }
}

Copyright (c) 2015 Tetsuaki BABA all rights reserved.