課題
説明書について
プログラムの説明書は,次のような形式でプログラムの先頭に挿入して下さい.
=begin
# 最初に所属・氏名・学生番号を記載
所属: 都学部1回生
氏名: 左京太郎
学生番号: 0123456789
この後(=begin...=endの間)にプログラムの説明を書く
=end
説明書には次の項目を記載してください.
- プログラムの概要説明
- プログラムの操作方法
- 自己評価(達成度)
- 工夫した点,アピールしたい点など
- さらに改善・拡張が考えられる点など
テンプレート
次のテンプレートを提供します. ファイル名を適宜変更して使って下さい.
技術情報
自由制作で使う可能性のある技術要素をいくつか紹介します.
円や円弧などの図形
OpenGLには円,円板あるいは円弧,扇形を描くプリミティブはありません. この授業では次のようにしてこれらを描画できるようにしています.
require "opengl" require "glu" require "glut" # 次のライブラリを利用する require "cg/mglutils" # 円板と円 # MGLUtils.disc([cx,cy],radius) # MGLUtils.circle([cx,cy],radius) # [cx,cy]: 中心位置 # radius: 半径 MGLUtils.disc([-0.5,0.5],0.3) MGLUtils.circle([-0.5,-0.5],0.3) # 扇形と円弧 # MGLUtils.fan([cx,cy],radius,angle_start,angle_end) # MGLUtils.arc([cx,cy],radius,angle_start,angle_end) # [cx,cy]: 中心位置 # radius: 半径 # angle_start: 開始位置(度で指定) # angle_end: 終了位置(度で指定) MGLUtils.fan([0.5,0.5],0.4,30,150) MGLUtils.arc([0.5,-0.5],0.4,-30,30)
乱数
プログラムにランダム性を取り入れるには,次のメソッドを利用します.
rand(n) # n>0のとき,0,1,...,n-1のいずれかの整数をランダムに返す rand(0) # [0,1)区間の値をランダムに返す
文字の表示
画面に文字(英数字,記号)を表示するには,次のようにdrawStringメソッドを利用します. 残念ながら日本語を表示することはできません.
require "opengl" require "glu" require "glut" # 次のライブラリを利用する require "cg/bitmapfont" : display = Proc.new { # drawString(x,y,str,font) # 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(0.0,0.0,"Origin",GLUT::BITMAP_9_BY_15) # drawStringCont(str,font) # str: 表示する文字列 # font: フォント(以下のいずれかを指定する) # # 直前に書いた文字列の続きに別の文字列を表示する場合は, # このメソッドが利用できる. # drawStringCont(" Point",GLUT::BITMAP_9_BY_15) }
drawString,drawStringContメソッドは,この実習用に用意したもので,OpenGLで標準に使えるものではありません(Ruby/OpenGLにも含まれていません).
なおdrawStringにつづいてdrawStringContを使うときに, drawStringContの直前でGL.colorで色を変えても反映されないようになっています. 文字列の途中で色を変える場合にはdrawStringContを使う代わりに(スマートではありませんが)表示位置を調整してdrawStringを使って下さい. 描画される文字列の世界座標系(画面内での描画に使う座標系)でのサイズは次のメソッドで知ることができます.
# obj: 描画する文字列あるいは文字数 # font: 描画に用いるフォント # range: ウインドウの端から端までの世界座標系での長さ # wsize: ウインドウの大きさ # dir: BITMAP_DIM_WIDTH または BITMAP_DIM_HEIGHT # rasterSize(obj,font,range,wsize,dir) WSIZE=800 # 描画する文字列の世界座標系での横幅を求める(BITMAP_DIM_WIDTH) # "hello!"という文字列を「GLUT::BITMAP_9_BY_15」で描画する # ウインドウの左端から右端までの世界座標系での長さは2.0だとする(-1≦x≦1) # ウインドウの大きさ(横幅)はWSIZE(=800)とする # 得られた値をswに代入する sw = rasterSize("hello!",GLUT::BITMAP_9_BY_15,2.0,WSIZE,BITMAP_DIM_WIDTH) # 描画する文字列の世界座標系での横幅を求める(BITMAP_DIM_WIDTH) # 「GLUT::BITMAP_HELVETICA_18」で12文字分描画する場合を考える # ウインドウの左端から右端までの世界座標系での長さは2.0だとする(-1≦x≦1) # ウインドウの大きさ(横幅)はWSIZE(=800)とする # 得られた値をswに代入する sw = rasterSize(12,GLUT::BITMAP_HELVETICA_18,2.0,WSIZE,BITMAP_DIM_WIDTH) # 描画する文字列の世界座標系での高さを求める(BITMAP_DIM_HEIGHT) # 「GLUT::BITMAP_HELVETICA_18」での縦方向で3文字分の高さを求める # ウインドウの上端から下端までの世界座標系での長さは2.0だとする(-1≦y≦1) # ウインドウの大きさ(高さ)はWSIZE(=800)とする # 得られた値をshに代入する sh = rasterSize(3,GLUT::BITMAP_HELVETICA_18,2.0,WSIZE,BITMAP_DIM_HEIGHT)
マウスの動きの扱い
マウスがドラッグされている(ボタンが押された状態でマウスが動いている)ときにマウスポインタの位置を追跡するコールバック(MotionFunc)が利用できます. このコールバックとマウスボタンのコールバック(MouseFunc)を利用することで, ドラッグによる図形などの移動を実現できます(参照:ドラッグで図形を動かす方法).
#### マウスドラッグコールバック ######## motion = Proc.new {|x,y| # (x,y): マウスポインタの位置(ウインドウ座標) } : # コールバックの登録 GLUT.MotionFunc(motion)
マウスポインタの位置(x,y)はウインドウを基準とした座標系で与えられます. ウインドウの左上角が原点で,x座標は右向き,y座標は下向きにとられます. ウインドウのサイズの単位が座標系の単位と一致していて, 座標値としては整数点のみが得られます. 世界座標系(画面内での描画に使う座標系)ではないので注意が必要です.
マウスのボタンが押されていないときにマウスポインタの位置を追跡するコールバック(PassiveMotionFunc)も利用できます. 仕様はMotionFuncと同様です.
#### マウス追跡コールバック ######## p_motion = Proc.new {|x,y| # (x,y): マウスポインタの位置(ウインドウ座標) } : # コールバックの登録 GLUT.PassiveMotionFunc(p_motion)
[参考] マウスドラッグで図形を動かす方法
ここではマウスで画面上の図形をドラッグして動かす方法について説明します. この処理は「1.図形を掴む」「2.図形を動かす」「3.図形を離す」の3ステップで構成されます. これらは以下に示す要領で,マウスコールバック(MouseFunc)とドラッグのコールバック(MotionFunc)を組み合わせて実現できます.このときドラッグして動かす対象となる図形の情報をマウスコールバックとドラッグのコールバックで共有するために状態変数を使います.
- 図形をつかむ
まず図形の近くでマウスのボタンが押されたとき,図形を掴む処理を行います.
マウスコールバック(MouseFunc)を使って,ボタンが押された(GLUT::DOWN)ときに図形の領域とカーソルの位置(x,y)の関係を利用して,図形を掴んだかどうかを判定することになります.どういう状態であれば,掴んだと判定するのかはプログラムで適宜決めることになります.
掴んでいなかったら何もしません.掴んだ場合は,掴んだという情報を何らかのデータとして状態変数に記録するようにします.
なお掴む候補となる図形が2つ以上ある場合には,それぞれについて掴んだかどうかを適宜判定すること,また掴んだ場合にはどれを掴んだかを状態変数で識別できるようにしておくことが必要になります. - 図形を動かす
図形を掴んだ後,ボタンを押したままマウスが動かされたら,図形をそれに合わせて動かすようにします.
ボタンを押したままマウスが動かされると,ドラッグコールバック(MotionFunc)が実行されます.そのとき「何かを掴んでいる状態」であればカーソルの位置(x,y)を利用して,掴んでいる図形をカーソルの位置に合わせて描く処理を行います.
このとき「何かを掴んでいる」状態であるという条件を考慮することが必要です.またもちろん何を掴んでいるのかによって処理が変わります.このような処理のために必要な情報は,上に書いたように「図形を掴む処理」の段階で「状態変数」で適切に設定しておくことが想定されます.
なお何も掴んでないときにはドラッグされていても何もしません. - 図形を離す
図形が掴まれていてマウスドラッグで動かされている状態で,マウスボタンが離されたら(GLUT::UP),そこで図形を離して,ドラッグを終了します.ドラッグを終了するには最初に設定した「図形を掴んでいる」状態を解除します.
これを実現するにはマウスコールバック(MouseFunc)で,状態変数が「何も掴んでいない」状態を表すようにデータを更新します.
特別なキーの扱い
カーソルキー[↓][↑]やファンクションキー[F1]...[F12],ホームキー[Home]などの通常の文字以外のキー入力はキーボードコールバック(KeyboardFunc)では扱えません(asciiのマニュアルにキーコードが記載されていません). それらは特殊キー用のコールバック(SpecialFunc)を別途作成して,登録します.
修飾キーの状態取得
GLUT.GetModifiersというメソッドで, [Shift],[Ctrl],[Alt]キーが押されているかどうかをチェックできます.
keyboard = Proc.new { |key,x,y| puts "key=#{key}" modifiers=GLUT.GetModifiers() if (modifiers&GLUT::ACTIVE_CTRL)!=0 # [Ctrl]が押されているときの処理を書く puts "Ctrl " end if (modifiers&GLUT::ACTIVE_ALT)!=0 # [Alt]が押されているときの処理を書く puts "Alt " end if (modifiers&GLUT::ACTIVE_SHIFT)!=0 # [Shift]が押されているときの処理を書く puts "Shift" end }
これを使うと[Shift],[Ctrl],[Alt]という修飾キーを押す場合と押さない場合で処理を分けることができます. なお修飾キーを押したときと押さないときとでkeyの値が変わる場合は,そのことも考慮する必要があります. たとえば[Shift]+[a]の場合,[a]が[A]に変わります('a'と'A'は異なります).
色表
色指定のサンプルを示した表を準備しました.