みなさん,こんにちは
おかしょです.
この記事では3次元の矢印をProcessingで描く方法を解説します.
今回のような3次元の矢印を描こうと思ったのは,ロボットの姿勢角を3次元でわかりやすくするためです.
3次元の姿勢変化は複雑で,頭の中のイメージだけでは理解するのが難しいため作成することにしました.
この記事を読むと以下のようなことがわかる・できるようになります.
- 3次元の矢印の描き方
- Processingのプログラミング
この記事を読む前に
3次元の矢印を描く前に,以下の記事では2次元の矢印を描く方法を解説しています.
この記事では以下の記事と一部かぶっている部分があるため,説明を省略することがあります.
まだ読んでいない方は先に読んでおくことをおすすめします.
使用するコマンド
3次元の矢印を描くには2次元の時と同様に,様々なコマンドを駆使する必要があります.
2次元の時は矢印を線で表現していましたが,3次元の時は角度によっては矢印に見えなくなってしまいます.
そこで,3次元の矢印の矢の部分は円錐型,コーンのような形状で表現します.
このようにすることで,矢印がどのような方向でも矢印のように見えます.
まずは,この円錐の作成や矢印の作成に必要になったコマンドを説明していきます.
使用するコマンド
矢印を書くためにはさまざまなコマンドを駆使する必要があります.
ここでは,プログラム内で使用するコマンドを一つ一つ簡単に解説していきます.
Processingを触ったことがある方は,すでに知っているコマンドもあると思うので読み飛ばしても問題はありません.
background()
描画範囲内の背景を設定します.
引数によって背景の色や透明度などを設定できます.
size()
描画範囲の大きさを設定します.
単位はpixelで横と縦のサイズを引数とします.
translate()
座標軸を平行移動させます.
引数に入力した分だけ原点が平行移動します.
camera()
視点の設定を行います.
視点の座標や向き,天地などの設定ができます.
stroke()
線の色を指定します.
noStroke()
枠線や線をなくします.引数は必要ありません.
fill()
引数で指定した色で塗りつぶしを行います.
beginShape(), vertex(), endShape()
このコマンドによって,複雑な形状の図形を描くことができます.
beginShape()の引数にはモード名を入力します.このモードによって図形の描き方が異なります.
今回は”TRIANGLE_FAN”というモードで描きます.
vertex()では引数に座標を入力することで,描く図形の頂点を指定します.
このvertex()コマンドを複数使用することで,多角形を描くこともできます.
endShape()コマンドでbiginShape()でスタートした図形の描画を終了させることができます.
line()
4つの引数で始点と終点を指定して,線を引くことができます.
pushMatrix(), popMatrix()
pushMatrix()でそれまでの座標軸を記憶します.
続く,popMtrix()で記憶した座標軸を吐き出すことができます.
このようにすることで,pushMatrix()とpopMtrix()の間でtranslateやrotateを使って座標軸を移動させても,元の座標軸に戻すことができます.
どちらも,引数は必要ありません.
rotateX(), rotateY(), rotateZ(),
座標軸を各軸周りに回転させます.
引数にradian単位で角度を入れることで,座標軸を任意の角度回転させることができます.
回転方向には注意が必要です.詳しくはこちらを参考にしてください.
atan2()
アークタンジェントの計算ができます.
二つの引数を必要とし,それぞれの正負によって-πからπの範囲で答えを返します.
2がついていない場合は,引数は1つで-π/2からπ/2の範囲で返します.
sin(), cos()
三角関数のサインとコサインの計算をします.
以上のコマンドを使用して,3次元に矢印を描きます.
3次元の矢印の描画
それでは,実際に動作するプログラムを以下に示します.
解説はその後に行います.ぜひ実行してみてください.
import processing.opengl.*;
void setup()
{
background(255); // 背景の色指定 白に指定
size(500, 500, OPENGL);
translate(250, 250, 0);
camera(200.0, -200.0, 250.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0);
rotateX(PI/2);
arrow(0, 0, 0, 150, 0, 0, 255, 0, 0);
arrow(0, 0, 0, 0, 150, 0, 0, 255, 0);
arrow(0, 0, 0, 0, 0, 150, 0, 0, 255);
}
void draw()
{
}
void cone(int L, float radius, int Color1, int Color2, int Color3)
{
float x, y;
noStroke();
fill(Color1, Color2, Color3);
beginShape(TRIANGLE_FAN); // 底面の円の作成
vertex(0, 0, -L);
for(float i=0; i<2*PI; )
{
x = radius*cos(i);
y = radius*sin(i);
vertex(x, y, -L);
i = i+0.01;
}
endShape(CLOSE);
beginShape(TRIANGLE_FAN); // 側面の作成
vertex(0, 0, 0);
for(float i=0; i<2*PI; )
{
x = radius*cos(i);
y = radius*sin(i);
vertex(x, y, -L);
i = i+0.01;
}
endShape(CLOSE);
}
void arrow(int x1, int y1, int z1, int x2, int y2, int z2, int Color1, int Color2, int Color3)
{
int arrowLength = 10;
float arrowAngle = 0.5;
float phi = -atan2(y2-y1, x2-x1);
float theta = PI/2-atan2(z2-z1, x2-x1);
stroke(Color1, Color2, Color3);
line(x1, y1, z1, x2, y2, z2);
pushMatrix();
translate(x2, y2, z2);
rotateY(theta);
rotateX(phi);
cone(arrowLength, arrowLength*sin(arrowAngle), Color1, Color2, Color3);
popMatrix();
}
2次元の矢印を描く時と同様で,setup関数内で下の方にあるarrow関数を使って3次元の矢印を描きます.
2次元の矢印の時とかぶる部分もあるので,3次元の矢印を描く際に必要なところだけ解説していきます.
setup関数
まず,setup関数内では3次元の表示が行えるようにsize()コマンドで3つ目の引数にOPENGLを入力しています.
この引数にはP3Dを入力する方法もあるのですが,P3DよりもOPENGLの方が処理が早いため,こちらを利用しています.
このOPENGLを使用するにはグラムの冒頭で”import processing.opengl.*;”と書く必要があるので注意してください.
7行目では視点の設定をcamera()コマンドで設定しています.
このコマンドの使い方はやや複雑で,説明すると長くなるので,別の記事で解説しようと思います.
9, 10, 11行目で矢印を描画しています.
それでは矢印を描くプログラムの解説をしていきます.
円錐の描き方
まずは矢印の矢の部分に当たる,円錐の描き方を解説します.
円錐を描く関数は以下のようになっています.
void cone(int L, float radius, int Color1, int Color2, int Color3)
{
float x, y;
noStroke();
fill(Color1, Color2, Color3);
beginShape(TRIANGLE_FAN); // 底面の円の作成
vertex(0, 0, -L);
for(float i=0; i<2*PI; )
{
x = radius*cos(i);
y = radius*sin(i);
vertex(x, y, -L);
i = i+0.01;
}
endShape(CLOSE);
beginShape(TRIANGLE_FAN); // 側面の作成
vertex(0, 0, 0);
for(float i=0; i<2*PI; )
{
x = radius*cos(i);
y = radius*sin(i);
vertex(x, y, -L);
i = i+0.01;
}
endShape(CLOSE);
}
このプログラムでは,まず底面の円から描いて,その後に側面を描くようにしています.
底面の作成
今回6行目で使用しているTRIANGLE_FANというモードは,最初に一つの頂点の座標を指定し,その後に指定した座標で作られる三角形を連結させることができます.
そのため,最初に指定する頂点を円の中心点として,その後の座標を円周上の点として細かく指定していけば,円を描くことができます.
最初の中心点の指定は7行目で行っています.
このときに第一引数であるLを使っています.このようにすることで,矢の部分の大きさを決定しています.
8行目から14行目でfor文を使って,円周上の点を細かく指定していきます.
このとき,第二引数であるradiusを使用して,底面の円の半径を指定しています.
そして,最後に15行目でendShape(CLOSE)とすることで,図形の描画を終了します.
このようにすることで,底面の作成ができます.
側面の作成
次に側面は先ほどの底面の円と同様にTRIANGLE_FANを使用します.
まず,中心は円錐の頂点とします.
これは17行目で指定しています.
次に指定していく点は底面の円の円周上になります.
これは18から24行目のfor文で細かく指定しています.
最後にendShape(CLOSE)で図形の描画を終了します.
お気づきの方もいるかもしれませんが,円錐の頂点は17行目指定したように原点になっています.
そのため,矢印の基となる直線の先を原点にするようにtranslate()を使って座標系を移動させる必要があります.
この処理はarrow関数内で行っています.
矢印を完成させる
円錐のプログラムを使用して,3次元の矢印を完成させます.
arrow関数を以下に再度示します.
void arrow(int x1, int y1, int z1, int x2, int y2, int z2, int Color1, int Color2, int Color3)
{
int arrowLength = 10;
float arrowAngle = 0.5;
float phi = -atan2(y2-y1, x2-x1);
float theta = PI/2-atan2(z2-z1, x2-x1);
stroke(Color1, Color2, Color3);
line(x1, y1, z1, x2, y2, z2);
pushMatrix();
translate(x2, y2, z2);
rotateY(theta);
rotateX(phi);
cone(arrowLength, arrowLength*sin(arrowAngle), Color1, Color2, Color3);
popMatrix();
}
5, 6行目のphiとthetaには第1ら第6引数を用いたアークタンジェントの計算によって矢印の角度が格納されます.
そのため,11, 12行目ではそれらの角度を用いて座標系の回転をrotateY()とrotateX()を利用して行っています.
7行目のstrokeでは第7, 8, 9引数を用いて線の色をRGBで設定しています.
8行目では矢印の基となる直線を描いています.
9行目のpushMatrix()でこれまでの座標系を保存して,14行目にpopMatrix()をすることで矢印を描く前と後で座標系が変化しないようにしています.
10行目で座標系の原点を矢印の先端に移動させて,11, 12行目で矢の部分が直線に沿うように座標軸を回転させています.
13行目では3, 4行目で定義した円錐のサイズを使用して,直線の先端に円錐を描いています.
以上の手順によって,矢印の直線と円錐を描いています.
まとめ
この記事では3次元の矢印をProcessingで描く方法を解説しました.
上記に示したプログラムを実行すれば,3次元の矢印を描くことができます.
しかし,これらの矢印を応用して使いたい場合はプログラムがどのような構成になっているのかを理解する必要があります.
続けて読む
この記事で作成したプログラムはこのブログでさまざまなところで使用しています.
例えば,以下の記事でも使用しています.
以下の記事ではcameraコマンドという視点を変更するコマンドの説明をしています.
興味のある方は読んでみてください.
Twitterでは記事の更新情報や活動の進捗などをつぶやいているので気が向いたらフォローしてください.
それでは最後まで読んでいただきありがとうございました.
コメント