openFrameworks x iOS #001

openFrameworks x iOS #001 from Takepepe on Vimeo.

最近めっきり暖かくなりましたね。
花粉が飛び始めたようですが、Takepepeまだ花粉症ではないのでどこ吹く風です。
(花粉症の方すみません;)

今回は上の動画の様なものを作りました。
openFrameworksで作成した複数のiOSアプリから、
openFrameworksで作成したDesktopアプリケーションを操作します。
通信には前回同様、OSCを使っています。

前回は1対1の一方通信でしたが、今回は1対複の通信になります。
動画上では2台のiPhoneのタッチ座標を取得し、その座標をつなぐ線が描画されていますが、
コード上はOSC接続している端末数に応じて点と線が増えていくものになっています。
iPadやiOSシュミレーターでも確認できます。
(引き出しに眠っているiPhone3GSが復活。捨てなくてよかった。)
物理演算処理にはBox2Dを使用しています。

全体の流れを詳しく説明していきます。

  1. iOS端末からDesktopアプリケーションにOSC通信
  2. iOS端末の数に応じたClientインスタンスの生成
  3. Box2Dのセッティングと円を降らす処理
  4. Clientメンバの座標間で線をひく

1.iOS端末からDesktopアプリケーションにOSC通信

まずはiOSアプリをopenFrameworksで作ります。
アプリ単体で見ると、タッチした場所に円が描かれるだけの簡単なものになっています。

oscS.h (iOS端末のコード)


#define HOST "192.168.10.191" // iMacのIPアドレス
#define PORT 8000 // 接続ポート

class oscS : public ofxiPhoneApp{
	
    public:
        void setup();
        void update();
        void draw();
        float getX(float mx);
        float getY(float my);
        void touchMoved(ofTouchEventArgs & touch);
    
    private:
        ofxOscSender sender;
};

oscS.mm (iOS端末のコード)


#include "oscS.h"
#include "ofxOsc.h"

//--------------------------------------------------------------
void oscS::setup(){
    
	ofxAccelerometer.setup();
    sender.setup( HOST, PORT );
	ofBackground(0, 0, 0);
    
}

//--------------------------------------------------------------
void oscS::update(){

}

//--------------------------------------------------------------
void oscS::draw(){
	ofSetColor(255, 255, 255);
    ofCircle(mouseX, mouseY, 10);
}

//--------------------------------------------------------------
float oscS::getX(float mx){
    int res = ofGetWidth();
    return mx/res;
}

//--------------------------------------------------------------
float oscS::getY(float my){
    int res = ofGetHeight();
    return my/res;
}

//--------------------------------------------------------------
void oscS::touchMoved(ofTouchEventArgs & touch){
    ofxOscMessage m;
    m.setAddress( "/mouse/position" );
    m.addFloatArg( getX(touch.x) );
    m.addFloatArg( getY(touch.y) );
    sender.sendMessage( m );
}

2.iOS端末の数に応じたClientインスタンスの生成

ここからはDesktopアプリケーションです。
まずは「Client」クラスを作成します。
このクラスはコンストラクタにiOS端末のipアドレスを引数で渡します。
update関数が呼ばれるたび、タッチ座標を変換・更新します。

Client.h

#include "ofxOsc.h"
class Client {
    public:
        Client(std::string ip);
        void update(ofxOscMessage message);
        void draw();
        ofPoint pos;
        string getIp();
    
    private:
        string ip;
};

Client.cpp

#include "ofxOsc.h"

//--------------------------------------------------------------
Client::Client(string _ip){
    ip = _ip;
}

//--------------------------------------------------------------
void Client::update(ofxOscMessage message){
    ofxOscMessage m = message;
    
    // Clientオブジェクトの更新
    if ( m.getAddress() == "/mouse/position" && ip == m.getRemoteIp()){
        
        // 画面比率にあわせてタッチ座標を変換
        pos.x = ofGetWidth() * m.getArgAsFloat(0);
        pos.y = ofGetHeight() * m.getArgAsFloat(1);
    }
}

//--------------------------------------------------------------
void Client::draw(){
    ofCircle(pos.x, pos.y, 5);
}

//--------------------------------------------------------------
string Client::getIp(){
    return ip;
}

メインスレッドでOSCメッセージを受信した際、そのメッセージが新しい端末からの場合、
Clientインスタンスが生成されるようになっています。

oscR.h (抜粋)

#include "Client.h"
#define PORT 8000
class oscR : public ofBaseApp{
    public:
        void setup();
        void update();
        void draw();
    
    private:
        void updateClient();
        ofxOscReceiver  receiver;
        vector <Client *> clients;

}

oscR.cpp (抜粋)

#include "oscR.h"
#include "ofxOsc.h"

//--------------------------------------------------------------
void oscR::setup(){

    receiver.setup( PORT );
    
}
//--------------------------------------------------------------
void oscR::update(){
    
    // OSCクライアントの更新
    updateClient();

}
//--------------------------------------------------------------
void oscR::updateClient(){
    while( receiver.hasWaitingMessages() ){
        ofxOscMessage m;
        receiver.getNextMessage( &m );
        
        // 1つ目のOSCクライアント
        if(clients.size() == 0){
            clients.push_back(new Client(m.getRemoteIp()));
        }
        
        // 新しいOSCクライアントからのメッセージかどうか
        int flag = 1;
        for(int i=0 ; i< clients.size() ; i++){
            if(clients[i]->getIp() == m.getRemoteIp()){
                flag = 0;
            }
        }
        
        // 新しいOSCクライアントだったら
        if(flag){
            clients.push_back(new Client(m.getRemoteIp()));
        }
        
        // Clientオブジェクトの更新
        int size = clients.size();
        for(int i=0 ; i< size ; i++){
            clients[i]->update(m);
        }
    }
}
//--------------------------------------------------------------
void oscR::draw(){

    ofSetColor(50,200,255);
    ofFill();
    for(int i=0; i<clients.size(); i++) {
        clients[i]->draw();
    }

}

3.Box2Dのセッティングと円を降らす処理

次にBox2Dの実装です。
updateでカウントアップし、circlesの数が100に達したら
新しいofxBox2dCircleを作り、古いものは削除します。

oscR.h (抜粋)

#include "ofxBox2d.h"
class oscR : public ofBaseApp{
    public:
        void setup();
        void update();
        void draw();
    
    private:
        void updateCircles();
        int count;
    
        ofxBox2d box2d;
        vector <ofxBox2dCircle *> circles;
    
};

oscR.cpp (抜粋)

#include "ofxBox2d.h"

//--------------------------------------------------------------
void oscR::setup(){

    count = 0;
    
    // Box2D設定
    box2d.init();
    box2d.setGravity(0, 0.1);
    box2d.createBounds(0, 0, ofGetWidth(), ofGetHeight());
    box2d.setFPS(10.0);
    box2d.setIterations(5, 5);
    
}

//--------------------------------------------------------------
void oscR::update(){
    
    // ofxBox2dCircleを更新
    updateCircles();
    box2d.update();

}

//--------------------------------------------------------------
void oscR::updateCircles(){
    count++;
    if(count > 100){
        
        // ofxBox2dCircleの追加
        ofPoint pos;
        int r = ofRandom(10, 30);
        int x = ofRandom(r, ofGetWidth());
        pos.set(x,r);
        
        ofxBox2dCircle *c = new ofxBox2dCircle();
        c->setPhysics(1.0, 0.2, 0.2);
        c->setup(box2d.getWorld(),x,r,r);
        circles.push_back(c);
        
        // 100個以上になったら先頭のofxBox2dCircleを削除
        if(circles.size() > 100){
            vector <ofxBox2dCircle *>::iterator it = circles.begin();
            (*it)->destroy();
            delete *it;
            it = circles.erase(it);
        }
        
        count = 0;
    }
}

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

    ofNoFill();
    ofSetColor(255,255,255);
    for(int i=0; i<circles.size(); i++) {
        circles[i]->draw();
    }

}

4.Clientメンバの座標間で線をひく

最後にiOS端末から取得した座標でofxBox2dPolygonの線をひきます。
ofPolylineに座標を追加し、ofxBox2dPolygonに代入します。
画面上で線が見える様、draw関数ではofPolylineを描画しています。

oscR.h (抜粋)

#include "ofxBox2d.h"

class oscR : public ofBaseApp{
    public:
        void setup();
        void update();
        void draw();
    
    private:
        void updatePolyLine();
        ofPolyline drawing;
        ofxBox2dPolygon polyLine;
    
};

oscR.cpp (抜粋)

#include "ofxBox2d.h"

//--------------------------------------------------------------
void oscR::update(){
    
    // 複数のOSCクライアントでofxBox2dPolygonを更新
    updatePolyLine(); 
}

//--------------------------------------------------------------
void oscR::updatePolyLine(){
    int size = clients.size();
    
    // OSCクライアントが複数だったら
    if(size >= 2){
        drawing.clear();
        for(int i=0 ; i< size ; i++){
            // 座標を追加
            drawing.addVertex(clients[i]->pos.x, clients[i]->pos.y);
        }
        // 3点以上必要なので最後の座標を追加
        drawing.addVertex(clients[size-1]->pos.x, clients[size-1]->pos.y);
        
        // ofPolylineをofxBox2dPolygonに代入
        polyLine.clear();
        polyLine.setPhysics(0.0, 0.5, 0.5);
        polyLine.addVertexes(drawing);
        polyLine.create(box2d.getWorld());
        
    }
}

//--------------------------------------------------------------
void oscR::draw(){
 
    ofSetColor(50,100,255);
    if (drawing.size()){
        drawing.draw();
    }

}