2007-10-08
■ 台形 
台形を表示するだけのプログラム。
プログラミング/Haskell/GLUT - Flightless wingと射影変換辺りを参考にした。
import Graphics.UI.GLUT main = do initialDisplayMode $= [RGBAMode, DoubleBuffered, Multisampling, WithDepthBuffer] initialWindowSize $= Size 640 480 getArgsAndInitialize createWindow "GLTest" displayCallback $= displayScene reshapeCallback $= Just reshapeWindow mainLoop displayScene = do clearColor $= Color4 0.3 0.3 0.3 0.0 clear [ColorBuffer] loadIdentity drawObject swapBuffers flush drawObject = preservingMatrix $ do renderPrimitive Quads $ mapM_ vertex [Vertex3 (-0.5) (-0.5) 0.0, Vertex3 0.5 (-0.5) 0.0, Vertex3 0.3 0.5 (-0.8), Vertex3 (-0.3) 0.5 (-0.8) :: Vertex3 GLfloat] reshapeWindow :: Size -> IO () reshapeWindow size@(Size w h) = do viewport $= (Position 0 0, size) matrixMode $= Projection loadIdentity ortho2D left right top bottom -- near::-1 far::1 lookAt (Vertex3 0.0 0.0 (-1.0)) -- 視点がどこか (Vertex3 0.0 0.0 1.0) -- どこを見るか (Vector3 0.0 1.0 0.0) -- どちらが上か matrixMode $= Modelview 0 where left = (-(fromIntegral w)/640):: GLdouble right = ( (fromIntegral w)/640):: GLdouble top = (-(fromIntegral h)/480):: GLdouble bottom = ( (fromIntegral h)/480):: GLdouble
ちょっと長いのはリサイズしても見た目が変わらないようにするため。
Zバッファの設定とか中途半端に付いてるけど、これから有効にする予定。
調べてもなかなか平行投影を使っている人がいなくて困った。
どうやら今のhOpenGLにはorthoが入ってないようだ。オブジェクトの奥行きは-1~1の範囲になる。
レイヤー指定に奥行きを使うから少し気を付けなくてはならない。
次は、
- 複数のオブジェクトを描く
- キー入力を受け付ける
- 入力を溜めておく
- 色を変える
が出来るようにしよう。これだけ出来て、ようやく線が引けるようになる。
描画ソフトだとほとんどが変数になるけど、一般的に表せるなら特に問題ないはず。
頂点カラー
上のコードで、以下の関数を差し替える。
drawObject = preservingMatrix $ do renderPrimitive TriangleFan $ do color (Color4 0.9 0.4 0.4 0.0:: Color4 GLfloat) vertex (Vertex3 (-0.5) (-0.5) 0.0:: Vertex3 GLfloat) color (Color4 0.4 0.9 0.4 0.0:: Color4 GLfloat) vertex (Vertex3 0.5 (-0.5) 0.0:: Vertex3 GLfloat) color (Color4 0.0 0.4 0.9 0.0:: Color4 GLfloat) vertex (Vertex3 0.3 0.5 (-0.8):: Vertex3 GLfloat) color (Color4 0.9 0.0 4.9 0.0:: Color4 GLfloat) vertex (Vertex3 (-0.3) 0.5 (-0.8):: Vertex3 GLfloat)
これで台形にグラデーションが付く。ついでに、今後のことを考えてTriangleFanにしておく。
まだ、renderPrimitiveやmapM_の使い方が良く分からないのでコードを書くのに苦労してしまった。
量の分からないオブジェクトを描くにはどうしてもリストにしなければならない気がするけど、colorとvertexを同時に扱わなければならない。
どちらも扱える新しい型を作ればいいのかな?
2007-10-06
■ GLUT 
どこへ行っても最初のプログラムを複雑に書きすぎだ。
やっぱり画面を表示するだけのプログラムが必要だよね。
import Graphics.UI.GLUT main = do createWindow "GLTest" displayCallback $= displayScene mainLoop displayScene = do flush
HSPなら何も書かないところだけど、haskellじゃそれは無理。
それでも、ほとんど疑念を挟むところはないね。
GLUTをimportして、窓を作って、何を描くか指定してメインループへ移行。
メインループは、窓を維持するために必要だね。
HSPじゃ標準では窓はリサイズできないけど、GLUTの窓はできる。その入力を待つためにメインループがを使う……で合ってるかな。
displayCallbackは画面の描画が必要なときに呼び出されるコールバック関数を指定する。
とりあえず、画面を更新するだけの関数を作って放り込んだ。
flushのかわりにprint "hoge"でも入れると、flushの必要性と呼び出されるタイミングが分かる。
GLUTの良いところは、MainLoopに入ったらあとはコールバック関数だけで動かすところだね。
動作がモジュール化されているので、問題点がどこにあるかすぐに分かる。
もちろん、自分でも分かりやすくなるように注意して書かないといけないけど。
欠点は同じところにあって、スタートさせてしまえば、トップレベルでの分岐はできない。
まあ、そんなことが必要なプログラムは複雑すぎるので、簡単に書けるように心掛ければ大丈夫。
それに、Cならメモリ消費量が問題になったりするけど、haskellなら通常と対して事情が変わらないので気にしなくていい。
これだけだと何か描いたりできないから、窓の設定から考えなくちゃいけないけど、今までHSPでコードを書いてきた身としては、これを改造して作ることを考えた方が気が楽だ。
2007-10-04
■ OpenGLの文書を軽く読んだ。 
GLUTはGUIを提供しているのか。
ウィンドウ作って、レンダリングやキー入力をコールするところまでしてくれる、と。
レンダにはGLモジュールを使う。NURBSが使いたければGLUを使う感じかな。
うーん、結構面倒くさいな。
cairo/glitzの動作が軽ければそっちを使うんだけれど。
自分で似たものを作っても動作が軽くなる気はしないし、テスト版が出来たらcairoに変えるかも。
2007-10-02
■ fizzbuzz 
コードを書くのに慣れるために、FizzBuzz問題を解いてみる。
main = fizzBuzz [1..100] fizzBuzz (x:xs) = do putFizzBuzz x fizzBuzz xs fizzBuzz [] = putStrLn "done." putFizzBuzz x | mod x 15 == 0 = putStrLn "FizzBuzz" | mod x 5 == 0 = putStrLn "Buzz" | mod x 3 == 0 = putStrLn "Fizz" | True = print x
解き始めてテストして動作を確かめるまで5分くらいだった。
もう少し時間をかければ、もっと良いコードになるだろうけど、FizzBuzzを詰めても仕方がないので問題を挙げるだけに留める。
FizzBuzz問題は、「繰り返し」と「分岐」と「異なる型の表示」の3つの問題を含んでいる。
haskellでは、一番ネックになるのは異なる型を扱い、それを表示することではないだろうか。
今回は、表示する段で条件分岐して、それぞれの値に対する表示を行った。
他には、新しい型を作って、それでFizzBuzzリストを作るとか。そうすれば表示側は分岐しなくて済む。
■ 副作用の分離 
当たり前かも知れないけど、リアルタイムに入力を得て結果を返す関数は、入力を得る部分と、計算する部分は分離するべきである。
そうでないと、計算する部分に任意の値を与えてテストをすることがしにくくなる。
さらに、表示する部分も分けられた方が良い。テスト用には、実際の動作より、数値のリストなどの方が分かりやすいかも知れない。
このように分離をするためには、別に変数を使う必要はない。
それぞれの関数を別々に作り、呼び出し側で、それらをパイプした関数を作ればいいのだ。
それがMonadでありArrowである(と思う)。
- 入力を受ける
- 前回のデータを引き継ぐ
- 新しいデータを用いて表示系を書き直す
必要がある。
入力があるまで動かないイベントドリブンと、入力がないことも入力の一つとみなすタイムドリブンがある。
どちらにせよ、入力が決まったら次の動作に移行できる。
それさえ出来れば、現在の必要なデータを全て引数にして、次の動作へ写す関数を考えれば良い。
MonadでやるのとArrowでやるのとどちらの方が上手く書けるだろうか……。
■ haskellの動作イメージ 
どういう構造になっているか、出来るだけ単純に表すというのは、厳密にやらないとあまり意味がないけど、そういうのをぼんやり考えるのが好きだ。
haskellは、大雑把に見れば値の定義しかできない。そして、定義には順番は関係ない。
ただ、それだけだと何を表しているかさっぱり分からないので、値の定義がどこに所属するかを書ける。
実際、値の宣言と包含関係の提示が出来れば、データ構造を表すのには十分である。
データ構造が表せるだけではプログラミング言語としては不十分だ。ユーザが要求した値を返さなくてはいけない。
そのために、他の値の羅列で定義された値を評価して簡約する。簡約化の順番は、やはり定義によって決められる。
インタプリタなら、ユーザが指定する値をただ返せばいい。だが、普通のプログラムはそうなっていない。
ユーザは一つの値しか要求できず、しかもその値は破棄してしまう。
もはや、ただ値を返すプログラムは何のためにあるか分からない。
そこで登場するのが副作用である。
haskellは副作用を作るために少し細工がしてある。値を簡約化する際にアクションを起こせる。
アクションが起きると、データが書き換えられて世界が変わる。これはプログラムにとっては不都合だが、ユーザはそういった反応がなくては困ってしまう。
これを書いていたら、アスピレータっぽいと思った。水を入力して、水がそのまま出力される。この水は捨ててしまうけど、枝管の先が減圧されるという副作用が欲しい。
メインの作用は分かりやすい。でも、実際に使われるのは副作用で、それがどうやって引き起こされるのかは理解しにくい。
自作のスクリプト
haskellはデータ構造を表すのが基本だ。LISPも大体同じである。だとすれば、これらの書式はお絵描きツールのスクリプトにも流用できる。実際に絵を描くプログラムは作れるし。
しかし、そういった使い方をするには書式が一般的すぎる。haskellのコードでは絵を描いていることが一見して分かるとは思えない。
そこで、コードの書式を参考にしてスクリプトのルールを考える。やはり、プログラマは自分の言語を作れるべきなのだ。
そのためにも、良くできた言語を習得することは役に立つ。
マシン語やCの学習は、機械の動作を覚えるのには役に立つだろうが、スクリプトを作るためには役に立たないだろう。現在どちらの方が有用かというと、やはりスクリプトの作成に分があるだろう。