ofMeshを使って図形を描画する

openFrameworksには図形を描画するための便利な関数が複数存在する。

ofDrawRectangle(x, y, w, h);
ofDrawCircle(x, y, w, h);

などがそれに当たる。

しかし、ジェネラティブなプログラムを作りたい場合、これら関数を使うより便利な方法がある。自ら点と線を書いて図形を描画することだ。
これを実現するクラスがofMeshだ。
備忘のため、ofMeshを使って図形を描画する方法を記す。

なお、oF公式ドキュメント「ofBook」の以下ドキュメントを参考にした。
openframeworks.cc

Mesh

Meshとは、頂点の集合のことだ。それら頂点は互いに1本の線で接続が可能だ。
頂点を線で接続した形状をプリミティブと呼んだりする。下記の3Dモデルはプリミティブの一例だ。
f:id:pfont:20200202093711j:plain

ofMesh

ofMeshは、openFrameworks上でMeshの管理を担当するクラスだ。
以下公式ドキュメントを読むと、openFrameworks上でMeshを扱う上で役立つクラスメソッドが多数用意されている。
openframeworks.cc

openFrameworksでMeshを扱う際、このofMeshを使用する。

ofMeshで点を打つ

早速ofMeshで図形の描画を始める。まずは頂点を打つことだ。

ofApp.hでofMeshを宣言する。

#pragma once

#include "ofMain.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 mouseEntered(int x, int y);
		void mouseExited(int x, int y);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
		
		ofMesh mesh; // ofMeshの初期化
};

次にofApp.cppを見る。setup関数にofMeshの設定を記載する。

void ofApp::setup(){
    mesh.setMode(OF_PRIMITIVE_POINTS);
    mesh.enableColors();

   // 3点の座標を用意する
    ofVec2f top(100.0, 50.0);
    ofVec2f left(50.0, 150.0);
    ofVec2f right(150.0, 150.0);

   // (100.0, 50.0)の位置に赤い点を打つ
    mesh.addVertex(top);
    mesh.addColor(ofFloatColor(1.0, 0.0, 0.0));

   // (50.0, 150.0)の位置に緑の点を打つ
    mesh.addVertex(left);
    mesh.addColor(ofFloatColor(0.0, 1.0, 0.0));

   // (150.0, 150.0)の位置に青い点を打つ
    mesh.addVertex(right);
    mesh.addColor(ofFloatColor(1.0, 1.0, 0.0));
}

setModeにOF_PRIMITIVE_POINTSを設定した。これはプリミティブとして点を描画するためである。
なお、setModeで指定できる値には以下が存在する。

  • OF_PRIMITIVE_POINTS
  • OF_PRIMITIVE_LINES
  • OF_PRIMITIVE_LINE_STRIP
  • OF_PRIMITIVE_LINE_LOOP

あとは座標の任意の位置に点を打つ。今回は2次元座標を扱うため、頂点座標の指定にはofVec2fを用いている。

draw関数内でmesh.draw()をしてあげると、meshに格納された頂点情報を描画してくれる

void ofApp::draw(){
    ofBackground(0);
    mesh.draw();
}

これを実行すると以下となる。

f:id:pfont:20200202095525p:plain
非常に見づらいが、左上に3点の頂点がある。

ofMeshで三角形を描く

点を打ったので、あとは点同士を線で結ぶ。すると、正三角形が出来上がる。
すでに頂点は存在するため、setModeを変更して線を引っ張る。setup関数内の記述を変更する。

void ofApp::setup(){
    mesh.setMode(OF_PRIMITIVE_LINES); // OF_PRIMITIVE_LINESに変更
    mesh.enableColors();

   // 3点の座標を用意する
    ofVec2f top(100.0, 50.0);
    ofVec2f left(50.0, 150.0);
    ofVec2f right(150.0, 150.0);

   // (100.0, 50.0)の位置に赤い点を打つ
    mesh.addVertex(top);
    mesh.addColor(ofFloatColor(1.0, 0.0, 0.0));

   // (50.0, 150.0)の位置に緑の点を打つ
    mesh.addVertex(left);
    mesh.addColor(ofFloatColor(0.0, 1.0, 0.0));

   // (150.0, 150.0)の位置に青い点を打つ
    mesh.addVertex(right);
    mesh.addColor(ofFloatColor(1.0, 1.0, 0.0));
}

実行する。結果は以下。
f:id:pfont:20200202100059p:plain

1本しか引っ張ってくれない。不親切だ。しかしこれには理由がある。
OF_PRIMITIVE_LINESを指定すると、頂点のペアを作り、それら同士を直線で結ぶ挙動を取るからだ。たとえば頂点が[v1, v2, v3, v4]4つ存在するとき、[v1, v2]同士と[v3, v4]同士で直線を結ぶ。
つまり、今回は3点のうちはじめにaddVertexした2点 [top, left]間で直線を結んでしまった。

これを解消するには、他のモードを試す。
結論から言うと、三角形を描画したければ以下のようにする。

void ofApp::setup(){
    mesh.setMode(OF_PRIMITIVE_LINE_LOOP); // OF_PRIMITIVE_LINE_LOOPに変更
    mesh.enableColors();

   // 3点の座標を用意する
    ofVec2f top(100.0, 50.0);
    ofVec2f left(50.0, 150.0);
    ofVec2f right(150.0, 150.0);

   // (100.0, 50.0)の位置に赤い点を打つ
    mesh.addVertex(top);
    mesh.addColor(ofFloatColor(1.0, 0.0, 0.0));

   // (50.0, 150.0)の位置に緑の点を打つ
    mesh.addVertex(left);
    mesh.addColor(ofFloatColor(0.0, 1.0, 0.0));

   // (150.0, 150.0)の位置に青い点を打つ
    mesh.addVertex(right);
    mesh.addColor(ofFloatColor(1.0, 1.0, 0.0));
}

OF_PRIMITIVE_LINE_LOOPを設定すると、Meshに格納した順に頂点同士を線で結ぶ。そして、最後の頂点vnを頂点v1に結ぶ。こうすることで閉じた図形が出来上がる。

Meshで図形を描く利点

頂点に対する処理や演算が非常に楽になる。
ofMeshは配列で頂点の位置を保存しているため、全頂点を一気に操作することが容易い。
対してofDrawRectangle(x, y, w, h)などの関数は、すべての関数の引数に変数を設定し、変数操作を行う必要がある。1つの三角形であればよいが、2億個となると話は別だ。プログラムを書くことも大変だし、点の挙動をコントロールすることも嫌になる。