メディア芸術演習 VI - メディア・アート II
openFramewrorks – addonを使う 1:ofxBox2Dで物理演算
アドオン(Addon)とは
今日の授業では、アドオンの利用について解説します。アドオン(addon)とはopenFrameworksに機能を拡張するためのライブラリーです。アドオンを追加することで、openFrameworks単体ではできなかった様々な機能を実現しています。また、アドオンはopenFrameworksの開発者以外でも独自に開発して追加することが可能となっており、様々な開発者がopenFrameworks向けのアドオンを公開しています。
OpenFramwroksのFAT版をインストールすると、既にいくつかのアドオンが付属してきます。ネットワーク通信から、画像解析、3次元モデルの読み込みなど、様々な領域をカバーしています。
- ofxDirList ディレクトリの項目の一覧を生成
- ofxXmlSettings アプリケーションの設定をXML形式で保存、読込み
- ofxOsc Open Sound Control (アプリケーション間で主にサウンド情報をやりとりするプロトコル) をopenFrameworksで使用する
- ofxOpenCv:画像処理・画像認識用のC言語ライブラリOpenCVを使用できるようにする
- ofxNetwork:ネットワーク通信のプロトコル、TCPとUDPを使用可能にする、マルチキャストにも対応
- ofxThred:クロスプラットフォームでスレッドの管理を実現
- ofxVectorMath:ベクトルや行列の計算をする
- ofxVectorGraphics:openFrameworksからPostscriptを生成し出力する
- ofx3dModelLoader:3ds形式の3DモデルをopenFrameworksに読みこむ
また、FAT版には収録されていないアドオンも多数存在します。様々な分野のアドオンが活発に開発され、openFrameworksのフォーラムなどで発表されています。ネット上からは様々なサードパーティーのアドオンを入手可能です。
アドオンを使用してみる:ofxBox2dを使う
では実際にアドオンを使用してみましょう。この章では、ofxBox2dというアドオンを試してみます。ofxBox2dは、Box2DというライブラリをopenFrameworksで使用できるよう移植したものです。Box2Dは「物理エンジン」と呼ばれるライブラリの一つです。物理エンジンは、重力や衝突、摩擦といった物理計算を複雑な計算をすることなく利用できるようにしたライブラリです。Box2DはもともとはC++で書かれたライブラリですが、その便利さからActionScript3やJava、C#など様々な言語に移植されています。
Box2Dのソースコードは、Google Codeで管理されています。最新版をダウンロードするにはGoogle Codeのプロジェクトページから直接ダウンロードするのが良いでしょう。
このリストの中から、2010年11月現在最新のバージョンである、ofxBox2d_v2.1 (ofxBox2d_v2.1.zip) をダウンロードして、Zipファイルを解凍してください。解凍すると「ofxBox2d」というフォルダが生成されます。
ダウンロードしたアドオンを使用するには所定の場所にインストールする必要があります。先程ダウンロードした「ofxBox2d」を下記の場所にインストールしてください。
- 《openFrameworksをインストールしたフォルダ》/addons/ofxBox2d
アドオンをopenFrameworksのプロジェクトに追加する
次にアドオンをプロジェクトに追加します。XCodeで「ファイル」→「新規プロジェクト」を選択し、プロジェクトタイプをopenFramework Applicationで新規のプロジェクトをプロジェクト名「ofxBox2dTest」で作成します。ここにofxBox2dを追加してみましょう。
まず、Xコード画面の左側のコラム、「グループとファイル」の欄の一番先頭にあるプロジェクト名「ofxBox2dTest」の表示されたアイコンをコントロール+クリック、もしくは右クリックします。表示されるメニューから「追加」→「新規グループ」を選択します。すると、「ofxBox2dTest」の下にフォルダが追加され、フォルダ名を入力するモードになります。ここでフォルダ名を「addons」に設定します。
次に作成したaddonsフォルダをコントロール+クリック、もしくは右クリックします。先程と同様にメニューが表示されます。今度は「追加」→「既存のファイル」を選択します。するとファイル選択のダイアログが表示されますので、先程addonsフォルダにインストールした「ofxBox2d」フォルダを選択してください。
設定は下記の画面のとおりにして、「追加」ボタンを押します。
最後に、グループとファイルの中のofxBox2dフォルダの中にある、「example」フォルダを選択し、デリートキーを押します。すると「参照を削除:この参照のみを削除しますか? オリジナルファイルもゴミ箱に入れますか?」という選択画面が表示されます。ここで「参照を削除」を選んでください。以上の作業を完了すると、グループとファイルの中身は、下記の画面のようになるはずです。これでプロジェクトにofxBox2dのアドオンが追加されました。
ofxBox2dを使用するには、もう1つofxVectorMathをアドオンとして追加する必要があります。ofxVectorMathは最初からopenFrameworksのFAT版に収録されているのでダウンロードする必要はありません。プロジェクトに追加すれば使用可能です。先程と同様の手順で、「addons」フォルダをコントロール+クリック、もしくは右クリックして、「追加」→「既存ファイルの追加」を選択します。ofxBox2dと同じ手順で、ofxVectorMathをフォルダごと選択してaddonsの中に追加します。ofxVectorMathフォルダの中にある「example」フォルダと「install.xml」ファイルもデリートキーで消去し「参照を削除」を選択します。最終的にグループとファイル内のaddonsフォルダの中身は下記のようになるでしょう。これで必要なアドオンの準備は完了です。
ofxBox2Dを利用したプログラム 1 – 落下する円
まずはじめに簡単なサンプルから始めましょう。画面をドラッグするとランダムに半径を設定した円が落下してくるというプログラムを作成しています。
testApp.h
#ifndef _TEST_APP #define _TEST_APP #include "ofMain.h" #include "ofxVectorMath.h" //ofxVectorMathのライブラリを読込み #include "ofxBox2d.h" //ofxBox2dのライブラリを読込み class testApp : 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); ofxBox2d box2d; //Box2Dのインスタンス vector <ofxBox2dCircle *> circles; //円を格納するvector }; #endif
testApp.cpp
#include "testApp.h" void testApp::setup(){ //画面設定 ofSetVerticalSync(true); ofBackground(0, 0, 0); ofSetFrameRate(60); ofSetCircleResolution(16); //Box2Dの世界を初期化 box2d.init(); //重力を設定、下に5の力 box2d.setGravity(0,1); //画面を壁で囲む box2d.createBounds(0, 0, ofGetWidth(), ofGetHeight()); //box2Dの計算を10fpsで box2d.setFPS(10); } void testApp::update(){ //Box2Dを更新 box2d.update(); } void testApp::draw(){ //circlesに格納された全ての円を描画(イテレータ使用) for(vector <ofxBox2dCircle *>::iterator it = circles.begin(); it != circles.end(); ++it) { (*it)->draw(); } //Box2Dで生成された図形を描画 box2d.draw(); //フレームレートと操作説明を表示 ofSetColor(255, 255, 255); string info = ""; info += "Press : delete all circles"; info += "\n\nFPS: "+ofToString(ofGetFrameRate()); info += "\nNumber of circles: " + ofToString(circles.size(), 0); ofDrawBitmapString(info, 20, 30); } void testApp::keyPressed(int key){ //cキーでcircle配列をクリア if (key == 'c') { //circles配列の全ての要素を消去する(イテレータ使用) for(vector <ofxBox2dCircle *>::iterator it = circles.begin(); it != circles.end();){ (*it)->destroyShape(); delete *it; it = circles.erase(it); } } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } //マウスドラッグで、円を追加 void testApp::mouseDragged(int x, int y, int button){ //ofxBox2dCircle(円)クラスをインスタンス化 ofxBox2dCircle* circle = new ofxBox2dCircle(); //半径を設定 float r = ofRandom(10, 30); //物理パラメータを設定(重さ、反発力、摩擦力) circle->setPhysics(1.0, 0.8, 0.5); //マウスの位置に円を設定 circle->setup(box2d.getWorld(), mouseX, mouseY, r); //生成した円をcirclesに追加 circles.push_back(circle); } void testApp::mousePressed(int x, int y, int button){ } void testApp::mouseReleased(int x, int y, int button){ } void testApp::windowResized(int w, int h){ }
testApp.hの冒頭で、#includeを使って、「ofxVectorMath.h」と「ofxBox2d.h」を読みこんでいるところに注目してください。このことで、ofxVectorMathとofxBox2dのアドオンの機能を利用できるようになります
#include "ofxVectorMath.h" //ofxVectorMathのライブラリを読込み #include "ofxBox2d.h" //ofxBox2dのライブラリを読込み
パブリック変数の定義として、ofxBox2dをインスタンス化してbox2dを生成しています。このofxBox2dが様々な演算をする物理エンジン本体です。それに加えて、生成された円ofxBox2dCircleのポインタ型を格納するためのVecor「circles」を作成します。
ofxBox2d box2d; //Box2Dのインスタンス vector <ofxBox2dCircle *> circles; //円を格納するvector
testApp.cppでは、setup()関数の中で、Box2Dの世界の様々な初期設定を行っています。「box2d.init()」でBox2Dの世界の初期化、「box2d.setGrabity(…)」で重力の設定(下向きに5の強さ)、「box2d.createBounds(…)」では、画面の上下左右を壁で囲んでいます。最後に、「box2d.setFPS(…)」で1秒間に描画するコマ数を設定しています。
//Box2Dの世界を初期化 box2d.init(); //重力を設定、下に5の力 box2d.setGravity(0,1); //画面を壁で囲む box2d.createBounds(0, 0, ofGetWidth(), ofGetHeight()); //box2Dの計算を10fpsで box2d.setFPS(10);
次に、mouseDragged()関数の処理をみてみましょう。ここでは、マウスがクリックされる度に、新規の円の生成をしています。まず、半径をランダムに決定しています。次に「ofxBox2dCircle」クラスをインスタンス化して「circle」を生成しています。このofBox2dCircleは、Box2Dで定義された円形の物体です。この物体をBox2Dの世界に追加すると、重力や物体同士の衝突などが自動的に適用され計算をしてくれます。次にこの生成された円に基本的な物理パラメータを追加します。「circle.setPhysics(…)」で、重さ、反発力、摩擦力を設定しています。基本設定が完了したところで「circle.setup(…)」が円をBox2dの世界に追加しています。生成された円は、vectorであるcirclesにpush_back()して追加しています。
//マウスドラッグで、円を追加 void testApp::mouseDragged(int x, int y, int button){ //ofxBox2dCircle(円)クラスをインスタンス化 ofxBox2dCircle* circle = new ofxBox2dCircle(); //半径を設定 float r = ofRandom(10, 30); //物理パラメータを設定(重さ、反発力、摩擦力) circle->setPhysics(1.0, 0.8, 0.5); //マウスの位置に円を設定 circle->setup(box2d.getWorld(), mouseX, mouseY, r); //生成した円をcirclesに追加 circles.push_back(circle); }
update()関数とdraw()関数での処理はとてもシンプルです。update()関数内では、ofxBox2dクラスのインスタンスbox2dに対して、update()メソッドを呼びだすだけで全ての座標計算を自動的に行います。こうした面倒な計算を全て任せてしまうことが出来る部分が、物理エンジンを使う最大のメリットと言えるでしょう。
void testApp::update(){ //Box2Dを更新 box2d.update(); }
座標の計算が終了したら、draw()関数で作成した円を全て描画します。circlesに格納された全ての円をfor文を利用して描画しています。最後にofxBox2dのインスタンスbox2dに対して、draw()メソッドを呼びだしています。これは、box2d側で生成されたオブジェクト、このサンプルの場合は上下左右の壁や、マウスで円をドラッグした際に表示されるラインなどを描画しています。
void testApp::draw(){ //circlesに格納された全ての円を描画(イテレータ使用) for(vector <ofxBox2dCircle *>::iterator it = circles.begin(); it != circles.end(); ++it) { (*it)->draw(); } //Box2Dで生成された図形を描画 box2d.draw(); }
ofxBox2Dを利用したプログラム 2 – 円や四角形を描画する
ofxBox2Dでは円以外にもいろいろな形態があらかじめ用意されています。あらかじめ利用可能な形態は下記のとおり沢山あります。
- ofxBox2dCircle – 円
- ofxBox2dRect – 四角形
- ofxBox2dLine – 線
- ofxBox2dJoint – 2つの物体を結ぶ線(ばね)
- ofxBox2dPolygon – 多角形
- ofxBox2dSoftBody – ばねを利用した、弾性をもった物体
次の例は、キーボードで選択することで、円と四角形を描くことができるというサンプルです。また、円や四角形位置や大きさは、マウスをドラッグすることで画面上に描くように設定することができるよう工夫しています。
testApp.h
#ifndef _TEST_APP #define _TEST_APP #include "ofMain.h" #include "ofxVectorMath.h" //ofxVectorMathのライブラリを読込み #include "ofxBox2d.h" //ofxBox2dのライブラリを読込み class testApp : 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); ofxBox2d box2d; //Box2Dのインスタンス vector <ofxBox2dCircle *> circles; //円を格納するvector vector <ofxBox2dRect *> rects; bool drawing; //形を描き中かどうか ofPoint drawStart; //描き始めの場所 int shape; //描く形 0:Circle、1:Rect }; #endif
testApp.cpp
#include "testApp.h" void testApp::setup(){ //画面設定 ofSetVerticalSync(true); ofSetCircleResolution(64); ofBackground(0, 0, 0); //描画は60fpsで ofSetFrameRate(60); //Box2Dの世界を初期化 box2d.init(); //重力を設定、下に5の力 box2d.setGravity(0,5); //画面を壁で囲む box2d.createBounds(0, 0, ofGetWidth(), ofGetHeight()); //Box2Dの計算を15fpsで box2d.setFPS(15); //描画中ではない drawing = false; //最初に描く形態は丸 shape = 0; } void testApp::update(){ //Box2Dを更新 box2d.update(); } void testApp::draw(){ //描画中の形態を描く if (drawing) { ofSetColor(127, 127, 127); ofNoFill(); if (shape == 0) { //描画中の円 ofCircle(drawStart.x, drawStart.y, ofDist(drawStart.x, drawStart.y, mouseX, mouseY)); } else { //描画中の四角形 ofRect(drawStart.x, drawStart.y, mouseX - drawStart.x, mouseY - drawStart.y); } } //circlesに格納された全ての円を描画 for(vector <ofxBox2dCircle *>::iterator it = circles.begin(); it != circles.end(); ++it) { (*it)->draw(); } //rectsに格納された全ての四角形を描画 for(vector <ofxBox2dRect *>::iterator it = rects.begin(); it != rects.end(); ++it) { (*it)->draw(); } //Box2Dで生成された図形を描画 box2d.draw(); //ログと操作説明 ofSetColor(255, 255, 255); string info = ""; info += "Press [1] to draw circle"; info += "\nPress [2] to draw rect"; info += "\nPress to delete all circles"; info += "\nPress [r] to delete all rects"; info += "\n\nFPS: "+ofToString(ofGetFrameRate()); info += "\nNumber of circles: " + ofToString(circles.size(), 0); info += "\nNumber of rects: " + ofToString(rects.size(), 0); ofDrawBitmapString(info, 20, 30); } void testApp::keyPressed(int key){ //1キーで丸を描くモードに if (key == '1') { shape = 0; } //2キーで四角形を描くモードに if (key == '2') { shape = 1; } if (key == 'c') { //circles配列の全ての要素を消去する(イテレータ使用) for(vector <ofxBox2dCircle *>::iterator it = circles.begin(); it != circles.end();){ (*it)->destroyShape(); delete *it; it = circles.erase(it); } } if (key == 'r') { //rects配列の全ての要素を消去する(イテレータ使用) for(vector <ofxBox2dRect *>::iterator it = rects.begin(); it != rects.end();){ (*it)->destroyShape(); delete *it; it = rects.erase(it); } } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ //物体を描き始めた地点を記録 drawStart.x = x; drawStart.y = y; //描画中モードへ drawing = true; } void testApp::mouseReleased(int x, int y, int button){ //マウスを離したら、物体を生成 //形態のモードに応じた物体を生成 if (shape == 0) { //円を生成 //物体のサイズを計測 float r = ofDist(drawStart.x, drawStart.y, x, y); //最低半径を10に r < 10? r = 10 : r; //Box2Dの円のインスタンスを生成 ofxBox2dCircle *c = new ofxBox2dCircle(); //物理特性の設定 c->setPhysics(1.0, 0.8, 0.5); //世界に追加 c->setup(box2d.getWorld(), drawStart.x, drawStart.y, r); //ベクターに追加 circles.push_back(c); } else { //四角形を生成 //サイズを計測 float w = abs(x - drawStart.x); float h = abs(y - drawStart.y); //最低の幅と高さを10に w < 10? w = 10 : w; h < 10? h = 10 : h; //Box2Dの四角形のインスタンスを生成 ofxBox2dRect *r = new ofxBox2dRect(); //物理特性の設定 r->setPhysics(1.0, 0.8, 0.5); //世界に追加 r->setup(box2d.getWorld(), drawStart.x, drawStart.y, w, h); //ベクターに追加 rects.push_back(r); } drawing = false; } void testApp::windowResized(int w, int h){ }
カスタムの図形を生成する
ofxBox2D example from Atsushi Tadokoro on Vimeo.
ofxBox2dCircleは、ワイヤーフレームだけの円で少し味気ない感じもします。そこで、ofxBox2dCircleを継承したオリジナルのクラスを作成して、より表現に幅を持たせてみたいと思います。前回と前々回で解説した、オブジェクト指向のoFプログラミングでやった手順通りに新規にクラスを追加します。XCodeの「グループとファイル」内のsrcフォルダをコントロール+クリック、もしくは右クリックして、「追加」→「新規ファイル」を選択し、新規ファイルのテンプレート選択画面から「MacOSX」→「C and C++」→「C++ File」を選択し、ファイル名を「CustomCircle.cpp」に設定します。
testAppからは、ofxBox2dCircleを使用するのと全く同じやり方で、CustomCircleを使用することが可能です。これは、CustomCircleがofxBox2dCircleを継承したクラスなので、その性質をそのまま受け継いでいるからです。
CustomCircleクラスではまた、それぞれの物体の「寿命」を設定しています。これは、一定時間表示すると自動的に自らを消滅させるという機能です。そのために、CustomCircle自体には、生死を判定する「death」というBoolean型の変数を用意して、カウンターの値が一定値を越えると、「death = true」にしています。testAppクラスから、現在画面に表示しているCustomCircleのインスタンスのdeathの状態を常にupdate関数でチェックして、もし死んでいる物体があれば、オブジェクトを消去するという処理をしています。これによって、一定時間で消滅するという動きを実現しています。
testApp.h
#ifndef _TEST_APP #define _TEST_APP #include "ofMain.h" #include "ofxVectorMath.h" //ofxVectorMathのライブラリを読込み #include "ofxBox2d.h" //ofxBox2dのライブラリを読込み #include "CustomCircle.h" //CustomCircleで作成したクラスを使用 class testApp : 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); ofxBox2d box2d; //Box2Dのインスタンス vector <CustomCircle *> circles; //CustomCircleを格納するvector }; #endif
testApp.cpp
#include "testApp.h" void testApp::setup(){ //画面設定 ofSetCircleResolution(16); ofBackground(0, 0, 0); //描画は60fpsで ofSetFrameRate(60); //画面の透明度を加算合成に glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); //Box2Dの世界を初期化 box2d.init(); //重力を設定、下に1の力 box2d.setGravity(0,0.5); //床を生成 box2d.createFloor(ofGetWidth(), ofGetHeight()); //Box2Dの計算を10fpsで box2d.setFPS(10); } void testApp::update(){ for(vector <CustomCircle *>::iterator it = circles.begin(); it != circles.end();){ //CustomCircleの状態を更新 (*it)->update(); //もし寿命が尽きていたら、CustomCircleを消去 if ((*it)->dead) { //形態をBox2Dの世界から消去 (*it)->destroyShape(); //オブジェクトを解放 delete *it; //動的配列から、オブジェクトを削除 it = circles.erase(it); } else { ++it; } } box2d.update(); //Box2Dを更新 } void testApp::draw(){ //circlesに格納された全ての円を描画 for(vector <CustomCircle *>::iterator it = circles.begin(); it != circles.end(); ++it) { (*it)->draw(); } //Box2Dで生成された図形を描画 box2d.draw(); //ログと操作説明 ofSetColor(255, 255, 255); string info = ""; info += "Drag Mouse to draw circles"; info += "\nPress to delete all circles"; info += "\n\nFPS: "+ofToString(ofGetFrameRate()); info += "\nNumber of circles: " + ofToString(circles.size(), 0); ofDrawBitmapString(info, 20, 30); } void testApp::keyPressed(int key){ //cキーでcircle配列をクリア if (key == 'c') { for(vector <CustomCircle *>::iterator it = circles.begin(); it != circles.end();){ (*it)->destroyShape(); delete *it; it = circles.erase(it); } } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ //半径をランダムに決める float r = ofRandom(5, 10); //Box2Dの円のインスタンスを生成 CustomCircle *c = new CustomCircle(); //物理特性の設定 c->setPhysics(1.0, 0.8, 0.1); //世界に追加 c->setup(box2d.getWorld(), x, y, r); //力をランダムに加える float force = 150; c->addForce(ofPoint(ofRandom(-force, force), ofRandom(-force, 0)), 1); //ベクターに追加 circles.push_back(c); } void testApp::mousePressed(int x, int y, int button){ } void testApp::mouseReleased(int x, int y, int button){ } void testApp::windowResized(int w, int h){ }
CustomCircle.h
#include "ofxVectorMath.h" #include "ofxBox2d.h" //ofxBox2dCircleを継承したクラスCustomCircleを定義 class CustomCircle : public ofxBox2dCircle { public: CustomCircle(); //コンストラクタ void update(); //カウンタ更新 void draw(); //円を描画する float counter; //カウンタ float phase; //初期位相 int lifeTime; //表示される長さ bool dead;//生死の判定 };
CustomCircle.cpp
#include "CustomCircle.h" CustomCircle::CustomCircle() { //現在の年齢 counter = 0; //初期位相 phase = ofRandom(0, PI*2); //寿命を設定 lifeTime = 300; //生死の判定 dead = false; } void CustomCircle::update() { //寿命が尽きたら死亡 if (counter > lifeTime) { dead = true; } //画面からはみ出たら死亡 if(getPosition().x < 0 || getPosition().x > ofGetWidth() || getPosition().y > ofGetHeight()) { //死亡宣告 dead = true; } //年齢を追加 counter++; } void CustomCircle::draw() { //半径を取得 float radius = getRadius(); float r = abs(sin(counter * 0.03 + phase)) * radius + radius * 0.25; //座標を変更 glPushMatrix(); //物体の位置に座標を移動 glTranslatef(getPosition().x, getPosition().y, 0); ofFill(); //色を塗り潰す //パーティクルを描く ofSetColor(127, 255, 255, 24); ofCircle(0, 0, radius*2.0); ofSetColor(31, 127, 255, 127); ofCircle(0, 0, r); ofSetColor(255, 255, 255); ofCircle(0, 0, r * 0.25); //座標を復元 glPopMatrix(); }
衝突を検知する
物体の衝突を検知することも可能です。下記の例では、衝突を検知するクラス b2ContactListener を継承した MyContactListener というクラスを作成し、物体同士が衝突した際に音を鳴らすようにしています。
testApp.h
#ifndef _TEST_APP #define _TEST_APP #include "ofMain.h" #include "ofxVectorMath.h" //ofxVectorMathのライブラリを読込み #include "ofxBox2d.h" //ofxBox2dのライブラリを読込み #include "CustomCircle.h" //CustomCircleで作成したクラスを使用 //衝突を検出するためのリスナーのクラス class MyContactListener : public b2ContactListener { public: MyContactListener(); void Add(const b2ContactPoint* point); void Remove(const b2ContactPoint* point); ofSoundPlayer mySound; }; // ----------------- testApp --------------------- class testApp : 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); ofxBox2d box2d; //Box2Dのインスタンス vector <CustomCircle *> circles; //CustomCircleを格納するvector MyContactListener contacts; //衝突感知のリスナー }; #endif
testApp.cpp
#include "testApp.h" //衝突を検知するリスナー MyContactListener の実装 extern testApp *myApp; MyContactListener::MyContactListener() { mySound.loadSound("Tink.aiff"); } void MyContactListener::Add(const b2ContactPoint* point){ //衝突を検知したら、ランダムなピッチで音を鳴らす mySound.setSpeed(ofRandom(0.5, 2.0)); mySound.play(); } void MyContactListener::Remove(const b2ContactPoint* point){ } // ----------------- testApp --------------------- void testApp::setup(){ //画面設定 ofSetCircleResolution(16); ofBackground(0, 0, 0); //描画は60fpsで ofSetFrameRate(60); //画面の透明度を加算合成に glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); //Box2Dの世界を初期化 box2d.init(); //重力を設定、下に1の力 box2d.setGravity(0,0.5); //床を生成 box2d.createFloor(ofGetWidth(), ofGetHeight()); //Box2Dの計算を10fpsで box2d.setFPS(10); //衝突感知のリスナーを追加 box2d.getWorld() -> SetContactListener(&contacts); } void testApp::update(){ for(vector <CustomCircle *>::iterator it = circles.begin(); it != circles.end();){ //CustomCircleの状態を更新 (*it)->update(); //もし寿命が尽きていたら、CustomCircleを消去 if ((*it)->dead) { //形態をBox2Dの世界から消去 (*it)->destroyShape(); //オブジェクトを解放 delete *it; //動的配列から、オブジェクトを削除 it = circles.erase(it); } else { ++it; } } box2d.update(); //Box2Dを更新 } void testApp::draw(){ //circlesに格納された全ての円を描画 for(vector <CustomCircle *>::iterator it = circles.begin(); it != circles.end(); ++it) { (*it)->draw(); } //Box2Dで生成された図形を描画 box2d.draw(); //ログと操作説明 ofSetColor(255, 255, 255); string info = ""; info += "Drag Mouse to draw circles"; info += "\nPress to delete all circles"; info += "\n\nFPS: "+ofToString(ofGetFrameRate()); info += "\nNumber of circles: " + ofToString(circles.size(), 0); ofDrawBitmapString(info, 20, 30); } void testApp::keyPressed(int key){ //cキーでcircle配列をクリア if (key == 'c') { for(vector <CustomCircle *>::iterator it = circles.begin(); it != circles.end();){ (*it)->destroyShape(); delete *it; it = circles.erase(it); } } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ //半径をランダムに決める float r = ofRandom(5, 10); //Box2Dの円のインスタンスを生成 CustomCircle *c = new CustomCircle(); //物理特性の設定 c->setPhysics(1.0, 0.8, 0.1); //世界に追加 c->setup(box2d.getWorld(), x, y, r); //力をランダムに加える float force = 150; c->addForce(ofPoint(ofRandom(-force, force), ofRandom(-force, 0)), 1); //ベクターに追加 circles.push_back(c); } void testApp::mousePressed(int x, int y, int button){ } void testApp::mouseReleased(int x, int y, int button){ //半径をランダムに決める float r = ofRandom(5, 10); //Box2Dの円のインスタンスを生成 CustomCircle *c = new CustomCircle(); //物理特性の設定 c->setPhysics(1.0, 0.8, 0.1); //世界に追加 c->setup(box2d.getWorld(), x, y, r); //力をランダムに加える //float force = 150; //c->addForce(ofPoint(ofRandom(-force, force), ofRandom(-force, 0)), 1); //ベクターに追加 circles.push_back(c); } void testApp::windowResized(int w, int h){ }
サンプルプログラムのダウンロード
今日とりあげたサンプルは下記からダウンロードしてください。