gotutiyan’s blog

競技プログラミングをやったりopenframeworksでお絵かきをしたりしています。

processingとopenframeworksを比較してみる

processing(以下P)やopenframeworks(以下OF)を使ってグラフィックを創作する人はたくさんいて、中にはコードを公開している人もいます。普段僕はprocessingを使っているけど、この人の公開しているコードはopenframeworksなのか・・と読むのを諦めてしまうのはもったいないです。

ここではPとOFのどちらかを主流言語にしている人向けに、このメソッドはこっちではこうなります、といったことを列挙していきます。(需要があるかどうかはともかくとして、やってみたかったので)

PまたはOFのどちらかを初めて触る人の参考にもなる気がしています。

processingとopenframeworksの簡単な特徴

processingはjavaベースの言語です。一つ一つの命令が簡単で非常に書きやすいです。また初学者が詰まりやすい環境構築も一瞬で、公式サイトからzipを取ってきて解凍するだけです。

openframeworksは言語というよりはライブラリで、C/C++をベースに書けます。C/C++を使うだけあって実行速度が速いので、多少重い処理でもスムーズに動きます。その代わり環境構築が少し大変な面があります。

基本的な構造

PとOF両者とも、setup()とdraw()があります。setup()は一番最初に実行されて、draw()は実行中に1秒間に数十回という速さで何度も実行されます。これにより、パラパラ漫画のようにアニメーションが作れます。

また、OFにはdraw()の他にupdate()が存在します。一応、draw()の中には実際に図形を描画して、update()では変数の更新を行うといった役割があるようですが、特に気にしなくても動きます。
以下はテンプレです。

//P
void setup(){
  
}
void draw(){
  
}
//OF
#include "ofApp.h"
void ofApp::setup(){
 
}
void ofApp::update(){
    
}
void ofApp::draw(){
    
}

また、Pではsetup()やdraw()を絶対に使わないと動かないわけではありません。描くものによりますが、使わないときもあります。OFは必須です。

以下ではよく使う(と思われる)メソッドを両方で見ていきます。

図形描画とシステム関係

・点

Pはpoint( )で点を打てます。(x座標、y座標)です。
OFには点を打つメソッドはありません。(もしあれば教えてください。。)もし打ちたいときは、後述するofDrawCircle( )の半径を極端に小さくすれば良いです。

/*P*/  point(50,50);
/*OF*/ ofDrawCircle(50,50,1,1); //半径を1くらいにすれば点っぽくなるだろう
・線

lineです。(始点のx、始点のy、終点のx、終点のy)です。

//(50,50)から(100,100)に線を引く
/*P*/  line(50,50,100,100);
/*OF*/ ofDrawLine(50,50,100,100);
・四角

rectangleですね。デフォルトでは(左上の頂点のx、左上の頂点のy、横の長さ、縦の長さ)です。

/*P*/  rect(100, 100, 100, 50);
/*OF*/ ofDrawRectangle(100, 100, 100, 50);

デフォルトでは指定した座標が左上の頂点に来ますが、四角の中心に持ってくることもできます。後述するrotate系を扱うときには便利です。
この設定は以下のようにできます。

/*P*/  rectMode(CENTER);
/*OF*/ ofSetRectMode(OF_RECT_MODE_CENTER);
・円と楕円

(中心のx、中心のy、横の直径、縦の直径)です。もちろん、横と縦の直径を揃えれば綺麗な円になります。直径であることに注意してください。
また、OFには円しか書かない人向けのofDrawCircle(中心x、中心y、半径)があります。指定するのは半径であることに注意です。

/*P*/  ellipse(100, 100, 50, 100); /*円と楕円*/
/*OF*/ ofDrawEllipse(100, 100, 50, 100);  /*円と楕円*/
/*OF*/ ofDrawCircle(100, 100, 250);  /*円*/
・三角形

三角形は頂点が3つあるので、(x1, y1, x2, y2, x3, y3)のように3点分のx,y座標を順番に入れれば良いです。

/*P*/  triangle(100, 100, 150, 150, 50, 150);
/*OF*/ ofDrawTriangle(100, 100, 150, 150, 50, 150);
・好きな頂点を好きな数設定して繋ぐ

vertexですね。
繋ぎたい頂点を全てvertex(x座標、y座標)で列挙して、それらをbeginShape(); とendShape();で挟むイメージです。これで挟まないと描かれません。頂点は全て繋がれて、内側は塗りつぶされますが、noFill()で塗りつぶさないこともできます。

/*P*/  
beginShape();
vertex(50,50);
vertex(100,100)
.......
endShape();
/*OF*/ 
ofBeginShape();
ofVertex(50,50);
ofVertex(100,100)
.......
ofEndShape();
・画面幅を取得する

Pではシステム変数としてwidth,heightが用意されていますが、これらの値を使って何かやるときはsetup()でsize()を指定した後で使わないと正しい値が得られません。(size()指定前の初期値は100でエラーは出ないので、気づきにくいです。)
OFではofGetWidth(), ofGetHeight()です。

//画面の中心に円を描く
/*P*/  ellipse(width/2, height/2, 50, 50);
/*OF*/ ofDrawCircle(ofGetWidth()/2, ofGetHeight()/2, 50);
・文字をコンソールに出力

プログラムが予想通りに動かないとき、if文が本当に実行されているか、for文は本当に回っているかなどの確認をするときに、適当な位置に文字を出すプログラムを書けば良いです。
文字が出ればそのif文は実行されているなあ、ということが分かります。

/*P*/  println(値など)
/*OF*/  cout<<値など<<endl;

色関係

まずは背景色ですね。

/*P*/  background(255,0,0); /*赤色*/
/*OF*/ ofSetBackgroundColor(255,255,0); /*黄色*/

次に色を設定するメソッドです。これを書いた行以下の図形は、この色になります。

/*P*/  fill(0,255,0); /*緑*/
/*OF*/ ofSetColor(0,0,255);  /*青*/
・色情報を格納する変数を作る

色情報を格納する変数の型と、色情報を作るためのメソッド名は同じです。でもその名前は両者で違いますね。

/*P*/  color c = color(255,255,0);   //(R,G,B)
/*OF*/ ofColor c = ofColor(255,0,255);  //(R,G,B)

ここでデフォルトではRGBで色情報を与えますが、これをHSBで与えたいときは以下のような書き方をします。

//P
colorMode(HSB);
color c = color(70,255,255);  //(H,S,B)

//OF
color c;
c.setHsb(70,255,255);  //(H,S,B)
・色を塗りつぶさない

輪郭線だけになります。

/*P*/  noFill();
/*OF*/ ofNoFill();

入出力系

・マウス座標を取得
/*P*/  ellipse(mouseX, mouseY, 50,50);
/*OF*/ ofDrawCircle(ofGetMouseX(), ofGetMouseY(),50);
・マウスやキーが押されたかどうかを取得

PはmousePressed と keyPressedがあります。押されたらtrue,そうでなければfalseです。
OFはofGetMousePressed(), ofGetKeyPressed()があります。これも同様のbool値です。
if(mousePressed)と条件式に直接書くだけで判定できますね。

/*P*/  if(mousePressed) { /*処理*/ }
/*OF*/ if(ofGetMousePressed()) { /*処理*/  }

しかし、これでは高速に回るdraw()の中で何回も判定が起こってしまいます。(1回のクリックでも2,3回if文が実行されることがあります。)
これを「1回のクリックで1回だけ実行」という形にしたいとき、関数バージョンを使いましょう。

/*P*/ 
void mousePressed(){
   //処理
}
void keyPressed(){
   //処理
}
/*OF*/ 
void ofApp::mousePressed(int x, int y, int button){
       //処理
}
void ofApp::keyPressed(int key){
       //処理
}

これらの使い分けは、「押している間ずっとしてほしい処理」か「押すたびにしてほしい処理」かで使い分けると良いです。

・どのボタンが押されたか、どのキーが押されたか

先ほどの方法でクリックやキーが押された判定を受け取れば、押されたボタンやキーによってさらに処理を変えたいところです。
Pでは システム変数としてmouseButton,keyがあります。
・mouseButton={LEFT, CENTER, RIGHT}
・key=keyCode.R などkeyCode.[キーの名前]の形で表現。ただし矢印キーだけは、{"up", "down", "right", "left"}の形。

OFでは、前述した関数の中身のmousePressed( int x, int y, int button)において、buttonでどのボタンかを得ます。
button = 0(左)、1(中央)、2(右)
 キーに関しては、keyPressed(int key) におけるkeyに't' や'a'のように'シングルクオーテーション' で書けます。EnterやShift、矢印キーなど1文字では表せないものは、
OF_KEY_ENTER や OF_KEY_UPなど、OF_KEY_[キー名]で書けます。

//P
if(mousePressed && mouseButton==LEFT)   //左クリックしている間

void mousePressed(){
   if(mouseButton==LEFT)  //左クリックしたら(1クリックにつき1回)
}

if(keyPressed && key=="up") //矢印キーの上を押している間
if(keyPressed && key==keyCode.R)  //Rキーを押している間

//OF
void ofApp::mousePressed(int x, int y, int button){
     if(button==0) //左クリック
}
void ofApp::keyPressed(int key){
     if(key=='t') //tが押されたら
       if(key==OF_KEY_SHIFT) //shiftが押されたら
}

乱数系・数学系・数値操作系

・ランダムな値を生成

ランダムなfloat型(小数込み)の乱数を生成します。
random(乱数の範囲の始め、乱数の範囲の終わり)です。範囲の終わりに指定した値自体は、範囲に含まれないことに注意です。範囲にはマイナスを含めることもできます。また、random(x)と1つだけ値を書けば、0~xの範囲で乱数が生成されます。

/*P*/ 
float x= random(20,100); /*20~99*/
float y= random(100);  /*0~99*/
/*OF*/
float x= ofRandom(-25,75);  /*-25~74*/
float y= ofRandom(200); /*0~199*/
・(連続的な)乱数を生成

ノイズのことです。これは概念が少し難しいので、読み飛ばしても大丈夫です。
ノイズは与えた値が同じであれば同じ乱数を返します。また、入れた値が近ければ近いほど、より近い乱数を返します。ノイズによる乱数は必ず0~1の値です。

例として、noise(100) で 0.5を得るとします。このとき、noise(99.8)のように近い値を入れれば、0.48のように近い値を得ます。逆にnoise(40)のように全く違う値を入れれば、0.87のように全く違う値を得ます。

/*P*/  float x = noise(100);
/*OF*/ float x = ofNoise(100);
三角関数

三角関数は両者ともsin(),cos(),tan()で書けます。三角関数の性質から、-1 ~ 1の範囲の値を得ます。しかし度数法からラジアンへの変換の方法は違います。
(度数法とは0~360度で表されるものです。ラジアンは0 ~ 2πの範囲で表されるものです。三角関数において角度情報はラジアンで与えないといけないため、30度→π/6 のように変換する必要があります。 )
Pではradians(度数)で、 OFでは度数*DEG_TO_RADで変換できます。
DEG_TO_RAD は「degree(度数) to radians(ラジアン)」ということですね。

/*P*/   float x = sin(radians(180));
/*OF*/  float y = sin(180*DEG_TO_RAD));
・ある範囲を取る変数を別の範囲に移す

mapです。(変数名、元範囲の始まり、元範囲の終わり、目標範囲の始まり、目標範囲の終わり)です。
例えば三角関数の値が 0.5だとします。このとき、三角関数は-1から1の範囲しか取らないため、-1側からは75%、1側からは25%の位置にいると考えられます。これを0~100の範囲に移すとき、この割合を守れば 0.5 は75になります。このような操作を行うのがmapです。

これは前述したノイズの値と組み合わせたり、ある値を画面幅の割合に対応させるときなどに使います。

/*P*/  
float x = sin(radians(30));
map(x, -1, 1, 1, 100);  //sinなので-1 ~ 1の範囲を取る変数xを、0 ~ 100の範囲で同じ割合の数値にする。
/*OF*/ 
float x=sin(30*DEG_TO_RAD);
ofMap(x, -1, 1, 1, 100);
・2点間の距離を返す

distです。円同士の当たり判定などで役に立ちます。

/*P*/  dist(100,100,200,200);
/*OF*/ ofDist(100,100,200,200);
・開始からの経過時間を取得

Pではmills()です。
OFでは探してみると、ofGetElapsedTimeMillis()が近い感じでした。
これらはミリ秒(1秒=1000ミリ秒)で取得するので、1秒を測りたければmillis()/1000のようにして使います。

/*P*/  int time=millis();
/*OF*/ int time=ofGetElapsedTimeMillis();
・開始からの経過フレーム数を取得

フレーム数を取得します。これも三角関数の引数にしたり、一定時間ごとのアクションを起こすのに役立ちます。

//100フレーム毎に実行
/*P*/ if(frameCount % 100==0){処理} 
/*OF*/ if(getFrameNum() % 100==0){処理}

座標操作系

・原点の移動

PもOFも、デフォルトでは原点が画面左上にあります。
この原点の位置を移動させることができるtranslate()系メソッドがあります。

//両方、原点の位置を画面中心に持ってくる
/*P*/  translate(width/2,height/2);
/*OF*/ ofTranslate(ofGetWidth()/2, ofGetHeight()/2);
・原点周りの回転

原点を中心に座標系を回すrotate()系メソッドがあります。

/*P*/  rotate(PI/3);
/*OF*/ ofRotate(PI/3);

この2つのメソッドは共に原点に関する処理をするもので、非常に親密な関係にあります。
これらをうまく使えば、周期運動など面白い作品を作ることができます。

蛇足ですが、ずっと回し続けたいときには、

rotate(millis())

のように、ラジアンの部分に時間取得系の変数を突っ込んでやれば良いです。

・座標状態を保存

translateやrotateで動かした座標系は、同じメソッドにマイナスの値を与えることで元に戻ります。

/*P*/
translate( width/2, height/2 );
rotate(PI/6);
//何かしらの処理
rotate( - PI/6);  //回転させた分だけ逆に回すことで戻す
translate( - width/2, - height/2 ); //移動させた分だけ逆に移動すれば戻る

この操作をもう少し分かりやすく記述するために、pushMatrix()とpopMatrix()があります。
pushMatrix()は座標系を保存します。
popMatrix()はpushMatrix()で保存した座標系を取り出して適用します。
これらは何か変数に代入するとかではなくて、単に書くだけで大丈夫です。

/*P*/ pushMatrix(), popMatrix();
/*OF*/ ofPushMatrix(), ofPopMatrix();

先ほどの処理を書き直せば以下のようになります。

/*P*/
pushMatrix(); //動かす前の座標系を保存
translate( width/2, height/2 );
rotate(PI/6);
//何かしらの処理
popMatrix(); //保存したものを適用することで、元に戻す

画像を読み込む

画像を扱う流れは共通で、
・画像を扱う変数を宣言
・変数にどの画像を使うかのパスを与える
・実際に描画
です。

Pはあくまで独立したメソッドを使い、OFは変数のメンバ関数を使って指定していくところが少し異なりますね。
使う画像は.pdeや、.xcodeprojなど、プログラム本体以下の階層に置き、相対パスで指定します。

/*P*/
PImage pic;  //PImage型変数
pic=loadImage(相対パス);  //loadImageを用いてパスを指定
image( pic, 100, 100); //image(変数、左上の頂点のx座標、左上の頂点y座標)で描画

/*OF*/
ofImage pic;  //ofImage型変数
pic.load(相対パス);  //メンバ関数load( )でパスを指定
pic.draw(100,100); //メンバ関数draw()で描画

主なメソッドはこの辺りだと思います。また何か思いついたら、随時追加します。