7. GLUTを用いたプログラムの構造化

【プログラミング序論D】 目次, 講義ページ, 授業科目, www.kameda-lab.org 2018/01/16a

GLUTライブラリは、プログラマに対してイベント駆動型プログラミングを推奨しています。

ここでは、それにあわせて自分たちのプログラミングを構造化していきましょう。
それが済むと、いよいよOpenGLプログラミングの流儀に合わせて描画関係の関数をいろいろと導入していきます。

本節も基本はeclipseの利用を前提に説明を行います。
中盤以降、一部のソースファイルは変更されないままとなってきますが、プロジェクト作成にあたっては変更のないソースファイルもコピーをします(プロジェクト間のファイルの共有も可能ですが、本授業ではそのような使い方はしません)。

参考リンク

OpenGL(ver.2.1)の各関数(小文字のglの2文字で始まる関数)の説明
OpenGLのProgramming Guide Book (ver1.1, 通称赤本)
GLUT - The OpenGL Utility Toolkit
The OpenGL Utility Toolkit (GLUT) Programming Interface API Version 3


07.01. プログラムの構造設計

06-09-LogoOpenGL.cでは、まだ単一ファイルでしたが、これを分割して将来の大規模化に備えます。

そのために、ファイル分割を考えます。
実際に分割してしまう前に、どの単位で分割するか考えねばなりません。
06-09-LogoOpenGL.cには、何種類の関数群が含まれていると考えるべきでしょうか?
(Eclipseで関数が一覧できるビューがありますよね)

  1. ヘッダ
  2. CG物体作成
  3. カメラの設定
  4. 1フレームの描画
  5. コールバック
  6. OpenGLの初期化
  7. メイン関数

上記は一例です。
この例に沿ってプログラムを書き直したものが下記です。

【プログラム07-01】

  1. 07-01-Planning.c (06-09からの差分)

演習

07-01-ex1: 関数のグループ化
上記の構成において、各グループに属すると思われる関数をリストアップしなさい。

07-01-ex2: グループの定義
上記の構成において、各グループについて、どういう理由でまとまっているか、説明を試みなさい。

07-01-ex3: ソースコードの差異
07-01-Planning.cは06-09-LogoOpenGL.cと機能的に異なりますか?違う場合はその部分を説明しなさい。

07-01-ex4: 関数のグループ化の表示
07-01-Planning.cのプログラム構造がわかりやすくなるようにEclipseの表示を工夫しなさい。


07.02. ファイル分割

04節ですでに学習済みですが、今度は分割ファイル群によるプログラミングの本番です。
以下に正解もありますが、原則自分でまずは全て作業してください。

作業としては単純で、07-01-Planning.cを複数ファイルに分割するだけです。
Eclipseでもemacsでもgeditでも構わないので、6つにソースを分割してください。

以下は実際の作業後の様子です。
自分で作業が終わるまで中を見ないように!

【プログラム07-02】

  1. 07-02-CommonHeaders.h (07-01からの差分)
  2. 07-02-Callback.c (07-01からの差分)
  3. 07-02-EmbededObjects.c (07-01からの差分)
  4. 07-02-Initialization.c (07-01からの差分)
  5. 07-02-Projection.c (07-01からの差分)
  6. 07-02-Rendering.c (07-01からの差分)
  7. 07-02-MainFunction.c (07-01からの差分)

演習

07-02-ex1: ファイル中の関数の対応
07-01節のどのグループがどのソースファイルに対応するか、述べなさい。

07-02-ex2: コンパイルエラーの理由
本節の作業で分割は終わりましたが、この状態ではコンパイルが通りません。コンパイルが通らない理由を3つ説明しなさい。

07-02-ex3: コンパイルエラー表示
本節の作業で分割は終わりましたが、この状態ではコンパイルが通りません。コンパイルが通らない理由が示されているEclipseの表示を全て確認しなさい。


07.03. ファイル分割に伴う修正

07-02節の作業は単に分割しただけなので、コンパイルが通らなくても当然です。

ここでは、さらに修正を加えて、実行が出来る状態にまで復旧させます。

必要な情報と知識はすべて揃っているはずなので、自分で(下の処置後のソースを見ずに)上記3要素の作業を行ってみましょう。
自分で一通り作ってみてから、下記との書き方の違いを自分の目で確認してみてください。
自分で作業が終わるまで中を見ないように!

【プログラム07-03】

  1. ic2-CommonHeaders.h (07-02からの差分)
  2. 07-03-Callback.c (07-02からの差分)
  3. 07-03-EmbededObjects.c (07-02からの差分)
  4. 07-03-Initialization.c (07-02からの差分)
  5. 07-03-Projection.c (07-02からの差分)
  6. 07-03-Rendering.c (07-02からの差分)
  7. 07-03-MainFunction.c (07-02からの差分)

演習

07-03-ex1: プロトタイプ宣言に変数名が不要な理由
関数のプロトタイプ宣言において、07-03-CommonHeaders.hでは、関数の引数名まで与えていますが、C言語の仕様では引数の型さえ示せば引数名は必要ないことになっています。Cコンパイラの仕様上、変数名が不要な理由を説明しなさい。

07-03-ex2: プロトタイプ宣言の意味
ic2_BootWindow()関数を例にして、07-03-MainFunction.cと07-03-Initialization.cの間でプロトタイプ宣言がどのように機能するのか、コンパイル時点でのコンパイラの観点から説明しなさい。

07-03-ex3: 複数個所のブレークポイント
main()関数、ic2_DrawFrame()関数、ic2_OpenGLLogo()関数のそれぞれの関数ブロックの先頭にブレークポイントを設定し、ic2_OpenGLLogo()のブレークポイントまでステップ実行せずに進んでみなさい。


07.04. イベントの追加(キーボード)

GLUTライブラリでは幾つかのイベントに対してコールバックを設定できる関数(コールバック登録関数)が用意されています。
コールバックの登録

ここでは、'q'キーが押されればプログラムが終了するプログラムにしてみましょう。

まずは、コールバックされてからの動作の設計です。
コールバックされた後の動作として今回は「プログラムの終了」をします。
プログラムを任意の地点から強制終了する関数として、exit()関数があります。
(どうやって exit()関数の存在を知るのか?新しい英語の動詞を覚えるのと同じで、必要に迫られたらそれを知っている人から聞くしかありませんね)

  1. exit()関数の文法は?
  2. exit()関数の機能は?
  3. この関数は他でも将来使う予定がありそうですか?

それから、イベントと対を成してコールバックを仕掛けなくてはいけません。
そのために、コールバックを登録するにあたって必要な情報を集めます。

  1. 'q'キーが押されるという行為はどういうイベントとしてみなされますか?
  2. そのイベントに対してコールバック関数を登録できるコールバック登録関数は、なんというglutライブラリ関数でしょうか?
  3. 登録できるコールバック関数は、引数が指定されています。その数と型、それにその意味は?

こうした情報を用意して、プログラムの変更に臨みます。
プログラムのどの部分に手を加えるべきでしょうか?
目星がついたら、まずは自分でここだろうというところにコメント行などで、マークを打ってみましょう。
そのあとで、下の例をみて、自分の予想が正しかったかどうかを確認してください。
自分で作業が終わるまで中を見ないように!

【プログラム07-04】(未掲載分は変更無し)

  1. ic2-CommonHeaders.h (07-03からの差分)
  2. 07-04-Callback.c (07-03からの差分)
  3. 07-04-Initialization.c (07-03からの差分)

演習

07-04-ex1: イベントの種類
GLUTライブラリで扱えるイベントを調べて、それを列挙しなさい。

07-04-ex2: 関数の引数に関数名
コールバック登録関数はその引数に「関数」を取ります。C言語上の概念としてこれが可能な理由を説明しなさい。

07-04-ex3: コールバック時の引数
コールバック登録関数で登録できる関数は引数まで限定されています。C言語上の概念としてこうせざるを得ない理由を説明しなさい。

07-04-ex4: キーボード関数の引数
ic2_NormalKeyInput()関数の引数について、glutKeyBoardFunc()関数にも注意しつ説明しなさい(資料の出典は明記すること;Internetでの資料の場合は出典を2つ示すこと)。

07-04-ex5: キーボード関数の引数の確認
ic2_NormalKeyInput()関数の引数について、eclipseのブレークポイント機能を用いて実際に確認しなさい。


07.05. イベントの追加(ロゴの拡大縮小)

さらに練習として、's'キーが押されればロゴを小さくし、'S'キーが押されればロゴを大きくするプログラムにしてみましょう。

まずは、コールバックされてからの動作の設計です。
ロゴのサイズはic2_OpenGLLogo(float s); の引数sで制御できます。
's'なら0.5、'S'なら0.95にしましょう。
ic2_OpenGLLogo(float s);を呼び出している場所でこの値をセットします。
ic2_OpenGLLogo(float s);を呼び出している場所と、's'/'S'のキーイベントを拾えるところが違うので、大域変数logoscaleを用意して値を渡せるようにします。
(残念ながら大域変数以外で手早くこの間に値を渡せる方法はあまりありません‥)

コールバック登録側では、's'なら0.5、'S'なら0.95という値を logoscale にセットするだけです。

実際には大域変数の操作に伴って、各部の改変が必要になります。

【プログラム07-05】(未掲載分は変更無し)

  1. ic2-CommonHeaders.h (07-04からの差分)
  2. 07-05-Callback.c (07-04からの差分)
  3. 07-05-Rendering.c (07-03からの差分)
  4. 07-05-MainFunction.c (07-03からの差分)

演習

07-05-ex1: ロゴの色変化
'c'/'C'でロゴの色(どの部分でも可)を変化させるプログラムに改造しなさい。'c'で変色、'C'で元の色に戻せること。

07-05-ex2: ロゴの形状変化
'd'/'D'でロゴの形状(どの部分でも可)を幾何的に変形させるプログラムに改造しなさい。'd'で変形、'D'で元の形に戻せること。

07-05-ex3: ロゴの形状変化 (トグル)
上記ex2において、'd'だけで変形するプログラムに改造しなさい(1回目で変形、2回目で元に戻す)。

07-05-ex4: キーイベントの確認
キーボードからの入力によって、ic2_NormalKeyInput()関数が呼ばれていることを確認しなさい。


kameda[at]iit.tsukuba.ac.jp