はじめに
このプロジェクトでは、OpenGL と GLUT(Graphics Library Utility Toolkit)を使って簡単な時計のアニメーションを作成します。このアニメーションは、動いている時計の針を持つ時計を表示し、現在の時刻を表します。時計はリアルタイムで更新され、時針、分針、秒針の動きをシミュレートします。まず、プロジェクトファイルをセットアップし、その後必要なコードを進めます。
👀 プレビュー

🎯 タスク
このプロジェクトでは、以下を学びます。
- プロジェクトファイルとライブラリをセットアップする方法
- ウィンドウを作成し、OpenGL を初期化する方法
- 時計の背景と輪郭を描画する方法
- 時計を回転させて 12 時の位置を上部にする方法
- 現在の時刻を取得し、時計の針の位置を計算する方法
- 時計に時針、分針、秒針を描画する方法
- ウィンドウをリサイズし、時計をリアルタイムで表示する方法
🏆 成果
このプロジェクトを完了すると、以下ができるようになります。
- OpenGL と GLUT をセットアップして初期化する
- OpenGL を使って基本的な形状と線を描画する
- OpenGL でオブジェクトを回転させる
- 現在の時刻を取得し、それを使ってオブジェクトをアニメーション化する
- ウィンドウのリサイズとグラフィックのリアルタイム表示を処理する
プロジェクトファイルを作成する
まず、ターミナルで以下のコマンドを実行して、OpenGL と GLUT ライブラリをインストールします。
sudo apt update
sudo apt-get install mesa-utils freeglut3-dev -y
これにより、Mesa 3D グラフィックライブラリと GLUT ライブラリがインストールされます。その後、gcc を使って OpenGL プログラムをコンパイルできます。
次に、新しいファイルを作成し、好きなコードエディタで開きます。ファイル名は clock_opengl.c とします。
cd ~/project
touch clock_opengl.c
ヘッダーをインクルードして変数を定義する
次に、このプロジェクトを実装するためのコードを段階的に書きます。
#include <GL/gl.h>
#include <GL/glut.h>
#include <time.h>
#include <math.h>
#include <stdio.h>
const int WINDOW_WIDTH = 800;
const int WINDOW_HEIGHT = 600;
const int ROTATE_DEGREES = 180;
3 つの整数定数 WINDOW_WIDTH、WINDOW_HEIGHT、および ROTATE_DEGREES が定義されています。これらの定数は、ウィンドウの幅と高さ、および回転角度を定義するために使用されます。
ウィンドウをセットアップして OpenGL を初期化する
このステップでは、ウィンドウサイズを設定し、OpenGL と GLUT を初期化します。
int main(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
glutCreateWindow("Clock Animation");
glutDisplayFunc(drawClockHands);
glutReshapeFunc(reshape);
glutIdleFunc(idle);
glClearColor(0.0, 0.0, 0.0, 1.0);
glutMainLoop();
return 0;
}
glutInit():GLUT ライブラリを初期化するために使用されます。glutInitDisplayMode():ウィンドウの表示モードを設定します。GLUT_DOUBLEはダブルバッファリングを使用することを意味し、GLUT_RGBは RGB カラーモードを使用することを意味します。glutInitWindowSize():ウィンドウのサイズを設定します。glutCreateWindow():ウィンドウを作成し、ウィンドウのタイトルをClock Animationに設定します。glutDisplayFunc():描画関数を設定し、グラフを描画する必要があるときにdrawClockHands関数が呼び出されるようにします。glutReshapeFunc():ウィンドウサイズ変更のコールバック関数を設定します。つまり、ウィンドウサイズが変更されると、reshape関数が呼び出されます。glutIdleFunc():アイドルコールバック関数を設定します。つまり、他のイベント処理がないときにidle関数が呼び出されます。glClearColor(0.0, 0.0, 0.0, 1.0):空の色を黒(RGBA カラー値)に設定します。glutMainLoop():GLUT のメインループに入り、これは継続的に実行され、ウィンドウイベント、グラフの描画、ユーザー入力を処理します。
時計の背景を描画する
drawClockHands と呼ばれる関数を作成します。ここでは、暗い色の円を含む時計の背景を描画するコードを追加します。
void drawClockHands() {
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
glColor3f(0.1, 0.1, 0.1);
glVertex2f(-1, -1);
glVertex2f(1, -1);
glVertex2f(1, 1);
glVertex2f(-1, 1);
glEnd();
}
glClear():この関数呼び出しは、新しいグラフを描画する準備として色バッファをクリアします。GL_COLOR_BUFFER_BITは、色バッファをクリアすることを意味し、新しい描画を行う前に前の描画の内容をクリアします。glBegin():この関数は、要素(この場合は四角形)の定義の開始を示します。GL_QUADSは、グラフを定義するために四角形を使用したいことを意味します。glColor3f():現在の描画色の浮動小数点数値を RGB 色(赤、緑、青)に設定します。ここでは、色を暗灰色に設定しており、RGB 値(0.1, 0.1, 0.1)は暗灰色を表します。glVertex2f():四角形の角を定義します。
ここでの目的は、OpenGL ウィンドウに暗灰色の四角形を描画して時計の背景とすることです。4 つの頂点が四角形の境界を定義し、glBegin と glEnd の間のコードはグラフの外観を定義するために使用されます。これは、時針、分針、秒針などの他の要素の描画の前に通常行われる時計の背景描画の一部です。
時計を回転させる
時計を 180 度回転させて、12 時の位置を上部にするコードを追加します。
// drawClockHands() 関数の中
glPushMatrix();
glRotatef(ROTATE_DEGREES, 0, 0, 1);
glPushMatrix():これは OpenGL の関数で、現在の行列状態(通常はモデルビュー行列)をスタックに積み上げ、後で復元できるようにします。これは一般的な手法で、描画中にいくつかの異なる行列変換を行う必要がある場合に、後続の変換が以前の状態に影響を与えないようにするために使用されます。glRotatef():これはもう一つの OpenGL 関数で、回転変換を行います。現在のモデルビュー行列を Z 軸を中心にROTATE_DEGREESだけ回転させます。(0, 0, 1)は回転軸の定義で、ここでは Z 軸を中心とした回転を意味します。最初の値は X 軸を中心とした回転成分、2 番目の値は Y 軸を中心とした回転成分、3 番目の値は Z 軸を中心とした回転成分です。
時計の輪郭を描画する
時計の輪郭を、1 時間ごとの目盛り付きで描画します。
// drawClockHands() 関数の中
glLineWidth(3.0);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 360; i += 6) {
double angle = i * M_PI / 180;
double x = 0.9 * cos(angle);
double y = 0.9 * sin(angle);
glColor3f(1.0, 1.0, 1.0);
glVertex2d(x, y);
}
glEnd();
ここでの主な目的は、時計の背景に白い円形の輪郭を描画して、時計の外枠を表すことです。glLineWidth は線の太さを設定し、glBegin と glEnd の間のループは原点を中心とする円上に一連の点を描画し、それらをつないで閉ループを形成します。これが時計の外輪郭です。
現在の時刻を取得して時計の針の位置を計算する
現在時刻を取得し、時計の針(時、分、秒)の位置を計算します。
// drawClockHands() 関数の中
time_t rawtime;
struct tm* timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
int hours = timeinfo->tm_hour % 12;
int minutes = timeinfo->tm_min;
int seconds = timeinfo->tm_sec;
システムから取得した生の時刻データを格納するために、rawtime という変数を定義します。time_t は、特定の時点(通常は 1970 年 1 月 1 日)から現在までの時間の秒数を表すために一般的に使用されるデータ型です。
tm 構造体へのポインタを timeinfo と呼んで定義します。tm 構造体は、年、月、日、時、分、秒などの日付と時刻の様々な要素を格納するために使用されます。
time 関数を使用して現在のシステム時刻を取得し、その時刻の秒数を rawtime 変数に格納します。time 関数が返す秒数は、特定の時点(通常は 1970 年 1 月 1 日、つまり UNIX タイムスタンプと呼ばれる)から現在時刻までの秒数を表します。
localtime 関数を使用して、time 関数から取得した秒数(rawtime 変数に格納されている)を具体的な日付と時刻情報に変換し、結果を timeinfo 構造体に格納します。localtime 関数は、秒数を年、月、日、時、分、秒などの情報に変換し、この情報を timeinfo 構造体に格納します。
timeinfo 構造体から現在の時数を取得し、その後 12 で割って時数を 12 時間制に制限します。tm_hour は時を示します。timeinfo 構造体から現在の分数を取得します。tm_min は分を示します。timeinfo 構造体から現在の秒数を取得します。tm_sec は秒を示します。
時針を描画する
時計に時針を描画します。
// drawClockHands() 関数の中
double hourAngle = -(90 + hours * 360 / 12) + (360 / 12) * minutes / 60;
glLineWidth(5.0); // 線の太さを設定
glBegin(GL_LINES);
glVertex2d(0, 0);
glColor3f(1.0, 0.0, 0.0); // 時針の色
glVertex2d(0.5 * cos(hourAngle * M_PI / 180), 0.5 * sin(hourAngle * M_PI / 180));
glEnd();
まず、時計は通常 12 時間制であるため、hours の数を時計盤上の角度に変換する必要があります。この計算は、12 時間制の時数を 360 度の角度範囲にマッピングすることから始まります。その後、minutes(分針の位置による)の寄与を時針の角度に加えます。得られる hourAngle は、12 時の方向に対する時針の角度を表します。
次に、要素を定義します。この場合、時針を表す線分です。線分の始点は時計の中心である座標 (0, 0) に位置します。線分の終点は、三角関数 cos と sin を使用して時針の終端位置を計算します。その後の分針と秒針も同じ方法で描画されます。
分針を描画する
時計に分針を描画するコードを追加します。
// drawClockHands() 関数の中
double minuteAngle = -(90 + minutes * 360 / 60);
glLineWidth(3.0); // 線の太さを設定
glBegin(GL_LINES);
glVertex2d(0, 0);
glColor3f(0.0, 1.0, 0.0); // 分針の色
glVertex2d(0.7 * cos(minuteAngle * M_PI / 180), 0.7 * sin(minuteAngle * M_PI / 180));
glEnd();
時計盤は通常 60 分であるため、minutes の数を時計盤上の角度に変換する必要があります。この計算は、まず分の数を 360 度の角度範囲にマッピングし、それに 90 度を引いて 0 分を 12 時の方向に対応付けます。得られる minuteAngle は、12 時の方向に対する分針の角度を表します。
秒針を描画する
時計に秒針を描画するコードを追加します。
// drawClockHands() 関数の中
double secondAngle = -(90 + seconds * 360 / 60);
glLineWidth(1.0); // 線の太さを設定
glBegin(GL_LINES);
glVertex2d(0, 0);
glColor3f(0.0, 0.0, 1.0); // 秒針の色
glVertex2d(0.9 * cos(secondAngle * M_PI / 180), 0.9 * sin(secondAngle * M_PI / 180));
glEnd();
時計は通常 60 秒であるため、seconds の数を時計盤上の角度に変換する必要があります。この計算は、まず秒の数を 360 度の角度範囲にマッピングし、それに 90 度を引いて 0 秒を 12 時の方向に対応付けます。得られる secondAngle は、12 時の方向に対する秒針の角度を表します。
最後に、元の回転を復元して時計のアニメーションを表示します。
// drawClockHands() 関数の中
glPopMatrix();
glutSwapBuffers();
glPopMatrix() は、以前保存された行列状態を復元するために使用され、glutSwapBuffers() は、ダブルバッファリング描画を完了し、描画された画像を画面にレンダリングして、スムーズなアニメーション効果を達成するために使用されます。
ウィンドウのリサイズとリアルタイム表示
ウィンドウサイズが変更されたときに呼び出される reshape 関数を作成します。
void reshape(int w, int h) {
glViewport(0, 0, w, h); // ウィンドウサイズの調整のための OpenGL ビューポートを設定
glMatrixMode(GL_PROJECTION); // 射影行列モードに切り替える
glLoadIdentity(); // 現在の行列をリセット
gluOrtho2D(-1, 1, -1, 1); // 直交射影行列を設定
glMatrixMode(GL_MODELVIEW); // モデルビュー行列モードに戻す
}
この reshape 関数の目的は、ウィンドウサイズが変更されたときに新しいウィンドウサイズに応じて OpenGL のビューポートを調整し、適切な射影行列を設定して、新しいウィンドウサイズ下で描画されたグラフが正しく表示されるようにすることです。
次に、プログラムがアイドル状態のときにいくつかの操作を行うための idle 関数を定義します。
void idle() {
glutPostRedisplay(); // アイドル状態のときに再表示を要求する
}
この idle 関数の目的は、プログラムがアイドル状態のときに glutPostRedisplay() を呼び出して、ウィンドウの内容を再描画するように要求することで、ウィンドウの内容をアニメーション化またはリアルタイムで更新するためです。
プロジェクトをコンパイルして実行する
- ターミナルを開き、プロジェクトディレクトリに移動します。
cd ~/project
- 次のコマンドを使用してコードをコンパイルします。
gcc -o clock_opengl clock_opengl.c -lGL -lglut -lGLU -lm
- アプリケーションを実行します。
./clock_opengl

まとめ
このプロジェクトでは、OpenGL と GLUT を使って簡単な時計のアニメーションを作成する方法を学びました。プロジェクトファイルのセットアップ、ウィンドウと OpenGL の初期化、時計の背景の描画、時計の回転、時計の輪郭の描画、現在時刻の取得、そして時計の針の描画について説明しました。これらの手順に従うことで、視覚的に魅力的な方法で現在時刻を表示する基本的な時計のアニメーションを作成することができます。



