//変換並進・回転と拡大縮小
//今回は2次元図形の回転や平行移動などを座標変換で行う方法を勉強します.まず,これから変換していく対象である下記のような一辺が0.5の正方形を描いてみましょう.
#include<GL/glut.h>
double vertices[][2]={
{0.5, 0.0},
{0.0, 0.0},
{0.0, 0.5},
{0.5, 0.5}
};
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0, 1.0, 0.0);
glBegin(GL_LINE_LOOP);
for(int i = 0; i < 4; i++)
glVertex2d(vertices[i][0], vertices[i][1]);
glEnd();
glFlush();
}
int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA);
glutCreateWindow(argv[0]);
glClearColor(0.0, 0.0, 0.0, 0.0);
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
//今回は,この図形を回転したり,平行移動したりしますが,そのまえに,位置関係が判りやすいようにX-Y軸を描画します.display(void)に以下の描画を追加して,座標軸が描画されることを確認してください.
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_LINES);
/* X軸 */
glVertex2d(-1.0, 0.0);
glVertex2d(1.0, 0.0);
/* Y軸 */
glVertex2d(0.0, -1.0);
glVertex2d(0.0, 1.0);
glEnd();
//この図形を平行移動することを考えます.
//平行移動はx,yそれぞれに移動したい量を足せばよいので,例えば、x方向に0.2, y方向に-0.3移動させたければmain()関数の中で,
glColor3f(0.0, 1.0, 0.0);
glBegin(GL_LINE_LOOP);
for(int i = 0; i < 4; i++)
glVertex2d(vertices[i][0]+0.2, vertices[i][1]-0.3);
glEnd();
glFlush();
//とします.簡単ですね.しかし,このように実装してしまうと汎用性がなくなってしまいます.関数化しておきましょう!
void translate2D(double vertex[], const double tx, const double ty){
vertex[0] += tx;
vertex[1] += ty;
}
//このような実装をすれば任意の平行移動を適用することができます.この関数は以下のように呼び出します.
translate2D(&vertices[0][0], 0.2, -0.3);
//もちろん二次元配列を直接引数にしても構いません.ただし,念の為に,誤って配列の外をアクセスするプログラムを書かないように,頂点数を引数にしておきます.
void translate2D(double vertex[][2], int nv, const double tx, const double ty){
for(int i = 0; i < nv; i++){
vertex[i][0] += tx;
vertex[i][1] += ty;
}
}
//同様に,今度は回転変換を考えてみましょう.
//一般的な注意として,回転行列には三角関数が出てきますので,まずmath.hをインクルードしておきます.また,円周率を「const double PI = 3.14159265;」などとして,大域変数で定義しておくと便利です.環境によってはM_PIとして円周率が定義されてる事が多いのですが,ANSI CではM_PIなどはデフォルトで定義されていません.このような円周率などの数学の定数は,math.hをインクルー ドし,「#define _USE_MATH_DEFINES」と記述することによって利用可能になります.
//参考:https://msdn.microsoft.com/ja-jp/library/4hwaceh6.aspx
//さて,いよいよ図形を回転させるわけですが,これもベタ打ちで実装すると簡単です.時計と反対回りに60度回転させたいのであれば,頂点を以下の関数で変換します.このとき,math.hでは回転角度はradで与える必要があることに注意してください.また,座標系をどの様に定義していて,回転中心がどこで,またどちらの方向に回転するのもしっかり考えましょう.
glVertex2d(
vertices[i][0]*cos(PI/3) - vertices[i][1]*sin(PI/3),
vertices[i][0]*sin(PI/3) + vertices[i][1]*cos(PI/3)
);
//想像していた方向に,正しく60度回転しましたか?これで回転していることを確かめたら,関数化してみましょう.
//しかし,以下のような関数を用意すると失敗します.失敗することを確認して,なぜ失敗するのか考えてみましょう.
void rotate2D(double vertex[], const double r){
vertex[0] = vertex[0]*cos(r) - vertex[1]*sin(r);
vertex[1] = vertex[0]*sin(r) + vertex[1]*cos(r);
}
//このようにポインタ渡しを行い,数の中で演算結果を引数へ上書きする場合は注意が必要です.
//正しく動作させるためには,以下のようにtemp_x,temp_yを定義して,引数を受け取った後に一旦値を退避させると良いでしょう.
void rotate2D(double vertex[][2], int nv, const double r){
double temp_x;
double temp_y;
for(int i = 0; i < nv; i++){
temp_x = vertices[i][0];
temp_y = vertices[i][1];
vertex[i][0] = temp_x*cos(r) + temp_y*-1.0*sin(r);
vertex[i][1] = temp_x*sin(r) + temp_y*cos(r);
}
}
//これで平行移動と回転の両方がそろいました.
//例えば,このようなこれらの関数を以下のように実装すれば図形を自由に平行移動・回転を行うことができます
int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA);
glutCreateWindow(argv[0]);
glClearColor(0.0, 0.0, 0.0, 0.0);
glutDisplayFunc(display);
rotate2D(&vertices[0][0], 4, PI/6);
translate2D(&vertices[0][0], 4, 0.2, -0.3);
glutMainLoop();
return 0;
}
//rotate2Dとtranslate2Dの順番や適用回数,引数を様々に変えて実験してみてください.特に,回転と平行移動を入れ替えた時の挙動は特に注意しましょう.