yoppa.org


多摩美 – メディア芸術演習 VI – メディア・アート II 2014

第5回: openFrameworks Addonを使用する 1 – ofxGui、ofxBox2d

Addon(アドオン)とは、openFrameworksに機能を拡張するためのライブラリーやフレームワークです。processingのLibrariesのように、openFrameworks単体ではできなかった様々な機能を実現することが可能となります。Addonは、oF本体の開発者以外でも独自に開発して追加することが可能であり、繰り返し用いる機能や、CやC++で記述された既存のライブラリーをopenFrameworksに統合することができます。

今回は、Addonの使用の入門編として、プロジェクトにGUIを追加する「ofxGui」と、高度な物理演算ライブラリ「ofxBox2d」、さらにBox2Dに高度流体シミュレーションを付加した「ofxLiquidFun」をとりあげます。

Addon(アドオン)とは

Addonとは、oepnFrameworksの機能を何らかの方法で拡張するコードで、「ofx」という接頭辞で始まります。Addonは、ProcessingでいうLibrariesのような存在で、openFrameworks単体ではできなかった様々な機能を実現したり、頻繁に使用する機能をまとめたりすることが可能です。Addonは、openFrameworks本体の開発者以外でも独自に開発して追加することが可能です。

Addonsの目的

アドオンとは、oepnFrameworksの機能を何らかの方法で拡張するコードです。アドオンを作る理由は2つあります。

  • 外部ライブラリやフレームワークを、openFrameworksに適用して簡単に統合させることができる。例: KinectコントローラーをopenFrameworksで使用するためのofxKinectや、MIDIコマンドを送受信するためのofxMidiなど。
  • 自分自身または他のopenFrameworksのプログラマーにとって、複雑な作業を単純化できる。例: julapyによるofxQuadWarpや、ofTheoによるofxControlPanelなど。

openFrameworksのパッケージに付属されたAddon

openFrameworksのパッケージには、最初からいくつかのAddonが付属しています。openFrameworks 0.8.4には以下のアドオンが付属しています。

  • ofx3DModelLoader : 3Dモデルを読み込み
  • ofxOpenCv : OpenCVを活用したコンピュータビジョン
  • ofxAssimpModelLoader : 様々なフォーマットの3Dモデルを読み込み
  • ofxOsc : Open Sound Control(OSC)で外部のアプリケーションと通信
  • ofxGui : プロジェクトにGUIに追加
  • ofxVectorGraphics : ベクター画像を扱う
  • ofxKinect : Kinect(ゲームコントローラー)からの情報を取得
  • ofxXmlSettings : アプリケーションの設定を、XML形式で保存
  • ofxSvg : SVG(Scalable Vector Graphics)を扱う
  • ofxNetwork : tcp/ip を用いたネットワーク通信
  • ofxThreadedImageLoader : 連番のイメージファイルを読み込み

Addonを入手する

最初から付属しているAddon以外にも、大量のAddonが世界中の開発者によって開発され公開されています。Addonは主にGithubを利用して公開されるのが一般的です。

Githubに公開されたAddonに関する情報は、ofxAddonsのサイトにジャンル毎にまとめられています。

http://ofxaddons.com/

ここから、必要となるAddonを選択すると、個別のGithubのページにリンクしています。このページの「Download Zip」ボタン をクリックしてZip形式でダウンロードします。

ダウンロードしたAddonは、openFrameworksのパッケージ内のaddonsフォルダに格納します。

  • of_v0.8.4_osx_release/addons

これでAddonの準備は完了です。

プロジェクトへのAddonの追加

プロジェクトにAddonを追加するには、ProjectGeneratorを利用すると便利です。

ProjectGeneratorの画面で「Addons:」ボタンを押すと、現在addonsフォルダ以下に格納したアドオンの一覧が表示されます。プロジェクトに追加したいアドオンをチェックしてGenerateすると、生成されたプロジェクトにAddonが組込まれています。

screenshot_412

Addonを使ってみる 1 – ofxGui

では、1つAddonを使ってプロジェクトを作成してみましょう。ひとつ目は、ofxGuiです。

長らく待望されていた、openFrameworksのオフィシャルGUIで、プログラムで頻繁に調整する必要のあるパラメータをGUIから設定可能にします。設定した項目は、XML形式で設定ファイルとして保存と読込をすることができます。openFrameworksのv.0.8.0以降パッケージに付属して配布されているので、すぐに利用可能です。

まず、ofxGuiを組み込んだプロジェクトを作成します。ProjectGeneratorを起動して、以下の2つをチェックします。

  • ofxGui
  • ofxXmlSettings

ofxXmlSetteingは、ofxGuiで設定した値を保存するために使用します。

screenshot_413

では、まず簡単なプログラムを作成して、そのパラメータをGUIを用いて操作できるようにしてみましょう。色のついた円を画面に表示するプログラムを作成します。

この円のパラメーターは、ofApp.h内で以下のパラメータで指定されています。

// 円のパラメーター
float radius;
ofColor color;
ofVec2f position;

これらの値をGUIでコントロールできるように変更していきます。ofxGuiには型に応じて様々なパーツが用意されています。

  • ofxIntSlider : 整数型 (int) のスライダー
  • ofxFloatSlider : 浮動小数点型 (float) のスライダー
  • ofxVec2Slider : 2次元ベクトルのスライダー
  • ofxColorSlider : カラー生成スライダー
  • ofxButton : ボタン
  • ofxToggle : トグルスイッチ
  • ofxLabel : ラベル (テキスト表示)
  • ofxPanel : GUIの外枠

こうしたパーツを利用して、円を描くパラメータを置き換えていきます。

testApp.h

#pragma once

#include "ofMain.h"
#include "ofxGui.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    // GUIのパラメーター
    ofxPanel gui;
    ofxFloatSlider radius;
    ofxColorSlider color;
    ofxVec2Slider position;
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    ofBackground(127);
    ofSetCircleResolution(32);
    
    // colorの初期値、最小値、最大値を設定
    ofColor initColor = ofColor(0, 127, 255, 255);
    ofColor minColor = ofColor(0,0,0,0);
    ofColor maxColor = ofColor(255,255,255,255);
    
    // positionの初期値、最小値、最大値を設定
    ofVec2f initPos = ofVec2f(ofGetWidth()/2, ofGetHeight()/2);
    ofVec2f minPos = ofVec2f(0, 0);
    ofVec2f maxPos = ofVec2f(ofGetWidth(), ofGetHeight());
    
    // GUIを設定
    gui.setup();
    gui.add(radius.setup("radius", 200, 0, 400));
    gui.add(color.setup("color", initColor, minColor, maxColor));
    gui.add(position.setup("position", initPos, minPos, maxPos));
}

//--------------------------------------------------------------
void ofApp::update(){
    
}

//--------------------------------------------------------------
void ofApp::draw(){
    // パラメータを適用して円を描画
    ofSetColor(color);
    ofCircle(ofVec2f(position), radius);
    
    // GUIを表示
    gui.draw();
}

これで、円の半径、色、位置をGUIから変更できるようになりました。

screenshot_414

では、もう少し複雑なプロジェクトにGUIを組み込んでみましょう。前回作成した引力をつかったサンプルにGUIを組み込んでみたいと思います。

このプロジェクトのパーティクルのパラメータをGUIでコントロールしてみましょう。例えば、以下の項目を変更できるようにしてみます。

  • パーティクルの数 : num (int)
  • 摩擦力 : friction(float)
  • パーティクルのサイズ : size(float)
  • 引き付けられる引力の強さ : attraction(float)
  • パーティクルの色 : fgColor(ofColor)
  • 背景色 : bgColor(ofColor)

ここで、size、attractoin、fgColor、bgColorはGUIから設定した値をそのままプログラムに埋めこめば適用可能です。ところが、num(パーティクルの数)とfriction(摩擦力)は、値を変更した後でそれぞれのパーティクルに値を設定する処理をする必要があります。

ofxGuiではパラメータを変更した後に特定の処理をする場合、イベントリスナーを設定するという方法で変更するようにしています。例えば、frictionを設定した後には、全てのパーティクルのインスタンスのプロパティーを変更する必要があります。また、numを変更した場合には、設定した数に応じてパーティクルを格納した配列を操作する必要が生じてきます。以下のようにしてイベントリスナー追加します。

まずofApp::setup()にfrictionとnumのリスナーを追加します。

// イベントリスナー
num.addListener(this, &ofApp::onNumChanged);
friction.addListener(this, &ofApp::onFrictionChanged);

この設定したイベントリスナーは、ofApp内に関数として実装します。

// 摩擦を変更
void ofApp::onFrictionChanged(float &friction){
    for (int i = 0; i < particles.size (); i++) {
        particles[i].friction = friction;
    }
}

// パーティクルの数を変更
void ofApp::onNumChanged(int &num){
    // もしパーティクルの数が設定数よりも少ない場合
    if (num < particles.size()) {
        // パーティクルを削除
        for (int i = 0; i < particles.size() - num; i++) {
            particles.pop_back();
        }
    }
    // もしパーティクルの数が設定数よりも多い場合
    if (num > particles.size()) {
        // パーティクルを追加
        int addNum = num - particles.size();
        for (int i = 0; i < addNum; i++) {
            Particle p;
            p.friction = friction;
            p.setup(ofVec2f(ofRandom(ofGetWidth()), ofRandom(ofGetHeight())), ofVec2f(0, 0));
            particles.push_back(p);
        }
    }
}

このようにすることで、値を変更したタイミングで特定の処理をすることが可能となります。

最終的に以下のようなプログラムになりました。

screenshot_418

Addonを使ってみる 2 - ofxBox2D

ofxBox2Dとは

もう一つ、別のAddonを使用してみましょう。今度は、openFrameworksのパッケージには同梱されていないAddonsをダウンロードして使用してみます。

今回は、2次元での物理演算ライブラリofxBox2Dを取り上げてみます。

ofxBox2Dは、Box2Dという2次元での物理シミュレーションのためのライブラリをopenFrameworksのAddonとして作成したものです。このofxBox2Dを使用すると重力や物体同士の衝突判定、引力、ばねなどの様々な物理シミュレーションが簡単に利用可能です。

ofxBox2Dの入手

ofxBox2Dは下記のGithubリポジトリからダウンロードします。

Zipをダウンロードして展開したフォルダーを、addonsフォルダに格納します。

ofxBox2Dプロジェクトの作成

ofxBox2Dを利用したプロジェクトは、ProjectGeneratorを利用して作成します。Addons欄でofxBox2Dをチェックして生成します。

screenshot_421

ofxBox2D物理世界の生成

ofxBox2Dでは物理法則を適用した世界を生成し、そこに物体を追加していきます。まず、物理世界を生成して追加してみましょう。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxBox2d.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    ofxBox2d box2d; // Box2Dの世界
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    // 画面の基本設定
    ofSetFrameRate(60);
    ofBackground(0);
    
    // Box2Dの世界の設定
    box2d.init();               // 初期化
    box2d.setGravity(0, 10);    // 重力
    box2d.createBounds();       // 画面の周囲に壁を作成
    box2d.setFPS(30.0);         // box2Dの世界でのFPS
    box2d.registerGrabbing();   // 物体をつかめるようにする
}

//--------------------------------------------------------------
void ofApp::update(){
    box2d.update();             // box2Dの更新
}

//--------------------------------------------------------------
void ofApp::draw(){

}

ofxBox2d、物体を追加する(単体)

では、作成した世界に物体を追加してみましょう。ofxBox2Dにはあらかじめ物理法則を適用可能な基本図形が用意されています。

  • 円 – ofxBox2dCircle
  • 四角形 – ofxBox2dRect
  • ポリゴン – ofxBox2dPolygon

この中から、円(ofxBox2dCircle)をひとつ追加してみましょう。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxBox2d.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    ofxBox2d box2d;         // Box2Dの世界
    ofxBox2dCircle circle;  // 円
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    // 画面の基本設定
    ofSetFrameRate(60);
    ofBackground(0);
    
    // Box2Dの世界の設定
    box2d.init();               // 初期化
    box2d.setGravity(0, 10);    // 重力
    box2d.createBounds();       // 画面の周囲に壁を作成
    box2d.setFPS(30.0);         // box2Dの世界でのFPS
    box2d.registerGrabbing();   // 物体をつかめるようにする
    
    circle.setPhysics(3.0, 0.53, 0.1);  // 円の物理パラメータを設定
    circle.setup(box2d.getWorld(), ofGetWidth() / 2.0, 100, 40); // 円を物理世界に追加
}

//--------------------------------------------------------------
void ofApp::update(){
    box2d.update();             // box2Dの更新
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofSetColor(0, 127, 255);
    circle.draw();              // 円の描画
}

これで、物理世界に円が追加されました。マウスで掴んで投げることも可能です。

screenshot_422

ofxBox2d、複数の物体を追加する(配列)

では、次にvectorを使用して、大量の物体を追加してみましょう。

ofxBox2Dではvectorの使用法に若干注意が必要です。これまでの例のようにvectorを使用してofxBox2dCircleの可変長配列を作成すると、以下のようになるでしょう。しかし、この方法ではエラーになります。

vector  circles;
ofxBox2dCircle circle;
circles.push_back(circle);

ofxBox2DではofPtrというポインタを賢く管理する仕組みを導入するようになっています。

// in your header files
vector  > circles;

// now add a circle to the vector
ofPtr circle = ofPtr(new ofxBox2dCircle);

// to grab the pointer you use the get() function of ofPtr (std::shared_ptr)
circle.get()->setPhysics(3.0, 0.53, 0.1);
circle.get()->setup(box2d.getWorld(), 100, 100, 10);
circles.push_back(circle);

これを踏まえて、「c」のキーを押すと円を追加、「b」のキーを押すと四角形を追加するようにしてみましょう。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxBox2d.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    ofxBox2d box2d;                             // Box2Dの世界
    vector  >    circles;    // 円の配列
    vector  > boxes;        // 四角の配列
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    // 画面の基本設定
    ofSetFrameRate(60);
    ofBackground(0);
    
    // Box2Dの世界の設定
    box2d.init();               // 初期化
    box2d.setGravity(0, 10);    // 重力
    box2d.createBounds();       // 画面の周囲に壁を作成
    box2d.setFPS(30.0);         // box2Dの世界でのFPS
    box2d.registerGrabbing();   // 物体をつかめるようにする
}

//--------------------------------------------------------------
void ofApp::update(){
    box2d.update();             // box2Dの更新
}

//--------------------------------------------------------------
void ofApp::draw(){
    // 円を描画
    for(int i=0; idraw();
    }
    // 四角を描画
    for(int i=0; idraw();
    }
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    // cキーで円を追加
    if(key == 'c') {
        float r = ofRandom(4, 20);
        circles.push_back(ofPtr(new ofxBox2dCircle));
        circles.back().get()->setPhysics(3.0, 0.53, 0.1);
        circles.back().get()->setup(box2d.getWorld(), mouseX, mouseY, r);
        
    }
    // bキーで四角を追加
    if(key == 'b') {
        float w = ofRandom(4, 20);
        float h = ofRandom(4, 20);
        boxes.push_back(ofPtr(new ofxBox2dRect));
        boxes.back().get()->setPhysics(3.0, 0.53, 0.1);
        boxes.back().get()->setup(box2d.getWorld(), mouseX, mouseY, w, h);
    }
}

これで、たくさんの図形を追加することができるようになりました。

screenshot_424