課題
もくじ
プログラムの説明書
プログラムの説明書は,次のような形式でプログラムの先頭に挿入して下さい.
=begin
所属: 都学部1回生
氏名: 左京太郎
学生番号: 0123456789
ここにプログラムの説明を書く
=end
次の項目を含めるようにしてください.
- 作品の概要(どんな作品か)
- プログラムの操作方法(必要なら実行方法も)
- 工夫した点,アピールしたい点など
- 作品の自己評価
- 改良したい点(さらに時間があればどうしたいか)
テンプレートファイル
次のテンプレートを利用して構いません. カメラと光源を適当に配置しています. またキーボードでカメラを動かす仕組みを入れてあります.
技術情報
自由制作で使う可能性のある技術要素をいくつか紹介します.
GLUT図形オブジェクト
GLUTライブラリでは,以下のような図形オブジェクトが定義されています. これらを組み合わせたり,あるいは適宜スケーリングすることで, さらに複雑なオブジェクトを構築することもできます.
次に曲面オブジェクトに関するサンプルプログラムを示します.
# ティーポット
# size: 大きさ
GLUT.SolidTeapot(size)
GLUT.WireTeapot(size)
# トーラス
# 中心=原点,z軸に垂直
# inner トーラス内部の円の直径(太さを決める)
# outer 中心の空洞部の円の半径(大きさを決める)
# sides 放射方向の分割数
# stacks 放射方向と直交する方向の分割数
GLUT.SolidTorus(inner,outer,sides,stacks)
GLUT.WireTorus(inner,outer,sides,stacks)
# 球
# 中心=原点
# radius 半径
# slices 経線方向の分割数
# stacks 緯線方向の分割数
GLUT.SolidSphere(radius,slices,stacks)
GLUT.WireSphere(radius,slices,stacks)
# 円錐
# 中心軸=z軸,底面:z=0,頂点=(0,0,height)
# base 底面の半径
# height 高さ
# slices 経線方向の分割数
# stacks 緯線方向の分割数
GLUT.SolidCone(base,height,slices,stacks)
GLUT.WireCone(base,height,slices,stacks)
# 四面体
# 原点中心
GLUT.SolidTetrahedron()
GLUT.WireTetrahedron()
# 立方体
# 原点中心
# size: 大きさ
GLUT.SolidCube(size)
GLUT.WireCube(size)
# 八面体
# 原点中心
GLUT.SolidOctahedron()
GLUT.WireOctahedron()
# 十二面体
# 原点中心
GLUT.SolidDodecahedron()
GLUT.WireDodecahedron()
# 二十面体
# 原点中心
GLUT.SolidIcosahedron()
GLUT.WireIcosahedron()
GLU図形オブジェクト
GLUライブラリには,二次曲面(quadric surface)として定義されている図形オブジェクトが用意されています. 次にサンプルプログラムを示します(GLUT図形オブジェクトのサンプルと同じです).
二次曲面を利用する際には,二次曲面オブジェクトが必要となります. また予め二次曲面の描画スタイルなどを指定しておく必要があります.
# 二次曲面オブジェクトの生成
quadric = GLU.NewQuadric()
# 二次曲面の描画スタイル
# quadric 二次曲面オブジェクト
# style 描画スタイル(GLU::POINT, GLU::LINE, GLU::FILL, GLU::SILHOUETTE)
GLU.QuadricDrawStyle(quadric,style)
# 二次曲面の法線処理モード
# quadric 二次曲面オブジェクト
# mode 法線モード(GLU::NONE, GLU::FLAT, GLU::SMOOTH)
GLU.QuadricNormals(quadric,mode)
# 二次曲面のテクスチャ処理モード
# quadric 二次曲面オブジェクト
# mode テクスチャON/OFF (GL::TRUE, GL::FALSE)
GLU.QuadricTexture(quadric,mode)
二次曲面オブジェクトは一つ用意すれば,それをすべてのGLU図形オブジェクトの描画のために何度でも使うことができます. つまり二次曲面ごとに二次曲面オブジェクトを個別に用意する必要はありません. 利用できる図形オブジェクトを以下に列挙します.
# 球面
# quadric 二次曲面オブジェクト
# radius 半径
# slices 経線方向の分割数
# stacks 緯線方向の分割数
GLU.Sphere(quadric,radius,slices,stacks)
# 円柱面(中心軸=z軸,底面はz=0,上面はz=height)
# quadric 二次曲面オブジェクト
# base 底面側の半径
# top 上面側の半径
# height 高さ
# slices 経線方向の分割数
# stacks 緯線方向の分割数
GLU.Cylinder(quadric,base,top,height,slices,stacks)
# 円盤(孔あき;z軸に垂直)
# quadric 二次曲面オブジェクト
# inner 内側の半径(これが0なら孔はなくなる)
# outer 外側の半径
# slices 放射方向の分割数
# rings 円盤を構成する同心円の数
GLU.Disk(quadric,inner,outer,slices,rings)
# 扇型(孔あき;z軸に垂直)
# quadric 二次曲面オブジェクト
# inner 内側の半径(これが0なら孔はなくなる)
# outer 外側の半径
# slices 放射方向の分割数
# rings 円盤を構成する同心円の数
# start 開始位置(度)
# angle 中心角(度)
GLU.PartialDisk(quadric,inner,outer,slices,rings,start,angle)
文字の表示
画面に文字(英数字,記号)を表示するには,次のようにdrawStringメソッドを利用します. 残念ながら日本語を表示することはできません.
次のライブラリを利用する
require "cg/bitmapfont"
:
display = Proc.new {
# x,y: 表示位置(世界座標系)
# str: 表示する文字列
# font: フォント(以下のいずれかを指定する)
# GLUT::BITMAP_9_BY_15
# GLUT::BITMAP_8_BY_13
# GLUT::BITMAP_TIMES_ROMAN_10
# GLUT::BITMAP_TIMES_ROMAN_24
# GLUT::BITMAP_HELVETICA_10
# GLUT::BITMAP_HELVETICA_12
# GLUT::BITMAP_HELVETICA_18
drawString(x,y,str,font)
# 直前に書いた文字列の続きに別の文字列を表示する場合は,
# 次のメソッドを利用する.
drawStringCont(str2,font)
}
文字を表示する際に,必要であれば, 以前の課題で行ったように,一時的に変換行列を初期化して,シェーディング,テクスチャ,Zバッファを無効化します. 具体的な処理の記述については課題08か課題09のプログラムを参考にしてください.
なおdrawString,drawStringContメソッドは,この実習用に用意したもので,OpenGLで標準に使えるものではありません(Ruby/OpenGLにも含まれていません).
[参考] 文字の表示位置の制御
drawStringにつづいてdrawStringContを使うときに, drawStringContの直前でGL.colorで色を変えてもそれが反映されません. そこで文字列の途中で色を変える場合にはdrawStringContを使う代わりに(スマートではありませんが)表示する位置を調整しつつdrawStringを使って下さい. 以前の課題のように2次元平面上に文字を描くように設定をしたとき,描画される文字列の世界座標系でのサイズは,次のrasterSizeメソッドで知ることができます.
## 世界座標系での文字列の幅/高さを得る
# obj: 対象とする文字列または文字数
# font: 利用するフォント
# range: 画面の左端から右端(上端から下端)までの距離(世界座標)
# pixels: ウインドウのサイズ(ピクセル数での幅/高さ)
# dir: BITMAP_DIM_WIDTH(幅を得る),BITMAP_DIM_HEIGHT(高さを得る)
rasterSize(obj,font,range,width)
# "S plane: "という文字列のGLUT::BITMAP_9_BY_15での幅
# 画面の左端から右端までの距離=2.0
# ウインドウの幅=600ピクセル
# wに世界座標系での文字列の幅(x方向の長さ)が代入される
w = rasterSize("S plane: ",GLUT::BITMAP_9_BY_15,2.0,600,BITMAP_DIM_WIDTH)
# GLUT::BITMAP_TIMES_ROMAN_24の1文字の高さ(最初の引数=1で文字数を指定している)
# 画面の上端から下端までの距離=2.0
# ウインドウの高さ=800ピクセル
# hに世界座標系での文字列の高さ(y方向の長さ)が代入される
h = rasterSize(1,GLUT::BITMAP_TIMES_ROMAN_24,2.0,800,BITMAP_DIM_HEIGHT)
桁数の制御
数値の表示桁数を指定したい場合には次のようにするとよいでしょう.
# xの小数点以下を2桁までを文字列strにする str = "%.2f" % x # xを(小数点をふくめて)全体で6桁,小数点以下2桁までで文字列str2にする str2 = "%6.2f" % x # red,green,blueのそれぞれに正負の符号を付けて小数点以下2桁まで並べてcolor=(...)と表示する. str3 = "color=(%+.2f,%+.2f,%+.2f)" % [red,green,blue] # あとは生成した文字列(str,str2,str3)を適宜drawStringで表示する
色の指定
描画する物体の色(材質)を決めるには課題08で作成したプログラムを利用するとよいでしょう.
背景色
またGL.Colorで設定する色のサンプルを示すのに次のプログラムも利用できます.
操作方法は次の通りです.
[r],[R]: 赤成分の増減([r]:増やす,[R]:減らす) [g],[G]: 緑成分の増減([g]:増やす,[G]:減らす) [b],[B]: 青成分の増減([b]:増やす,[B]:減らす) ※ [ALT]を押しながら操作すると,より細かな単位で色を変更できます. [q],[ESC]: 終了
なおウインドウの背景色はGL.ClearColorで指定します.
# 背景色の設定(red,green,blue,alpha)
GL.ClearColor(0.4,0.4,1.0,1.0)
テクスチャ用画像
利用可能なテクスチャ画像のサンプルについてはPandAで確認して下さい.
特殊なキーの扱い
カーソルキー[↓][↑]やファンクションキー[F1]...[F12],ホームキー[Home]などの通常の文字以外のキー入力はキーボードコールバック(KeyboardFunc)では扱えません. それらをインタラクションに利用するには,特殊キー用のコールバックを別途作成して,それを「SpecialFunc」として登録します.
# 特殊なキー入力をあつかうコールバック special = Proc.new { |key,x,y| case key when GLUT::KEY_LEFT # [←] # [←]を押したときの処理 when GLUT::KEY_RIGHT # [→] # [→]を押したときの処理 when GLUT::KEY_UP # [↑] # [↑]を押したときの処理 when key == GLUT::KEY_DOWN # [↓] # [↓]を押したときの処理 when key == GLUT::KEY_F1 # [F1] # [F1]を押したときの処理 when key == GLUT::KEY_F2 # [F2] # [F2]を押したときの処理 end } : : GLUT.SpecialFunc(special) # 特殊キー用の入力コールバック登録 : GLUT.Mainloop()
キーボードモディファイアの取得
キーボード入力コールバックにおいて,GLUT.GetModifiersというメソッドで, [Shift],[Ctrl],[Alt]キーが押されているかどうかをチェックできます.
- 注意
仮想型端末を利用しているため,キーの組み合わせ次第では想定通り動作しなかったり,予想外の挙動が示したりする場合がありますので注意してください. たとえばWWWブラウザのショートカットキーと競合する場合が問題になります.
mod = GLUT.GetModifiers() if (mod & GLUT::ACTIVE_ALT)!=0 # # [Alt]が押されている場合の処理 # end if (mod & GLUT::ACTIVE_CTRL)!=0 # # [Ctrl]が押されている場合の処理 # end if (mod & GLUT::ACTIVE_SHIFT)!=0 # # [Shift]が押されている場合の処理 # end
なお[Shift],[Ctrl]を押しながらキーを押すとイベントとして発生するkeyが変化する場合がありますので注意してください. たとえば[Shift]+[a]の場合,keyは"A"に変わります(小文字→大文字). また[Ctrl]+[x]の場合,keyは「24番の文字」になります. これは「key.ord == 24」で判定できます(他の判定方法もあります).
when key case 'a' # [a]の場合 case 'A' # [A](=[Shift]+[a])の場合 case 'x' # [x]の場合 end when key.ord case 0x1b # [ESC]の場合 case 0x18 # 0x18 == 24 # [Ctrl]+[x]の場合 end
(一時的に)キーのデータを端末に表示させてみれば,どういうイベントが発生しているのかを知ることができます.
keyboard = Proc.new { |key,x,y|
# keyとその番号(key.ord)を端末に表示する
p [:key,key,:ord,key.ord]
}
タイマーコールバック
タイマーコールバックは指定した時間が経過したときに処理を実行する仕組みを提供します. タイマーコールバックはGLUT.TimerFuncで登録します. タイマーコールバックが1回実行されると登録は解除されるようになっています. タイマーコールバックの内部で再度タイマーコールバックを登録すれば 一定間隔で同一の処理を繰り返し実行することができます.
INTERVAL=500 # 実行間隔(msec) timer = Proc.new { |v| # メッセージを表示(vの値を表示する) puts "call timer with #{v}" # 再度タイマーをセット # timerにはv+1を渡す GLUT.TimerFunc(INTERVAL,timer,v+1) } keyboard = Proc.new { |key,x,y| case key when 't' # タイマーをセット # INTERVALミリ秒後にtimerを実行する.timerに0を渡す GLUT.TimerFunc(INTERVAL,timer,0) end }
タイマーコールバックを使ったサンプルプログラムを示します.
乱数
プログラムにランダム性を取り入れるには,次のメソッドを利用します.
rand(n) # n>0のとき,0〜n-1のいずれかの整数をランダムに返す.
rand(0) # [0,1)区間の値をランダムに返す