読者です 読者をやめる 読者になる 読者になる

Common Lisp、cl-openglでマンデルブロ集合を描画した

この記事はりひにーさんのLisp Advent Calendar24日目の記事です。

昨日はりひにーさんの本当はコワイ副作用の恐怖 - 想像力の欠如は深刻な欠点の一つである。でした。
以前(ほとんど1年前ですが)common lispでマンデルブロ集合を計算させました。前は計算結果を書き出してProcessingで描画しましたが、OpenGLを使えるようになったのでcl-opengl(cl-glut)を使ってlispで描画したいと思います。
OSはwindows8.1、処理系はclisp-2.49、エディタはEmacsのSLIME 2.5(たぶん)です。
コードはこちら。
cl-glutによるマンデルブロ集合の描画

下準備

freeglut.dllを用意する。

OpenGLで描画したものをウィンドウなどに表示させるためにfreeglutを使います。そのfreeglutをCommon Lispで扱うcl-glutを使用するためにはfreeglut.dllが必要らしく、これは自分でソースコードからコンパイルして入手する必要があります。ちょっと面倒ですね。検索すればたくさん関連の記事が見つかるので頑張ってください。

Quicklispをインストールする

ライブラリの利用にQuicklispを使います。asdfはまだよくわかりません。QuicklispはモダンCommon Lisp: Quicklispによるライブラリ環境などを参考にインストールさせてもらいました。
quicklisp.lispを読み込む際のLispインタープリタのカレントディレクトリはslimeを起動した場所になるようなので、ファイルはそこに置くか、絶対パスを指定して読み込みます。(truename "./")でカレントディレクトリを調べられるそうなので必要であれば調べてください。

ライブラリを読み込む

Quicklispをインストールした後は

(ql:quickload :cl-opengl)
(ql:quickload :cl-glu)
(ql:quickload :cl-glut)
(ql:quickload :cl-glut-examples)
(cl-glut-examples:gears)

でcl-openglなどを読み込んでテストしてみます。依存するファイルは自動的に読み込まれるっぽいのでcl-glut-examplesのみを読み込んでもいいかもしれません。
3つの歯車が表示されたら成功です。

コードを書く

コード

OpenGL絡みのところのみを記載します。

(defclass my-window (glut:window)
  ()
  (:default-initargs :title "mandelbrot" :width *width* :height *height*
		     :mode '(:single :rgb :depth)))

(defmethod glut:display-window :before ((w my-window))
  (gl:clear-color 1 1 1 0)
  (gl:matrix-mode :projection)
  (gl:load-identity)
  (gl:ortho 0 *width* *height* 0 -1 1))

(defun set-mandelbrot-vertexes (latice-points)
  (mapcar #'(lambda (x)
	      (let ((latice-point (car x))
	       	    (n (cadr x)))
		(cond ((= n -1) (%gl:color-3f 0 0 0))
		      (t (%gl:color-3f (* n 0.2) 0 0)))
		(gl:vertex (* *magnification* (realpart latice-point)) (* *magnification* (imagpart latice-point)) 0)))
	  latice-points))

(defmethod glut:display ((window my-window))
  (gl:clear :color-buffer-bit)
  (%gl:color-3f 0 0 0)
  (gl:push-matrix)
  (gl:translate (/ *width* 2) (/ *height* 2) 0)
  (gl:begin :points)
  (set-mandelbrot-vertexes *mandelbrot*)
  (gl:end)
  (gl:pop-matrix)
  (gl:flush))

(defparameter *mandelbrot* (get-mandelbrot))
(defun draw-mandelbrot ()
  (glut:display-window (make-instance 'my-window)))

OpenGL、freeglutを触ったことがあればメソッド名はそのままなのであまり問題はないと思います。ウィンドウのクラスを作った後はdisplay、keyboard、reshapeなどのメソッドは書いて評価するだけでよく、登録は必要ないみたいです。スッキリして良いです。
気になったのは%gl:color-3fの部分で、ライブラリの指定に%をつける記法を見たことがありませんでした。ちゃんと調べていないのでまだ何なのかはわかっていません。
注意すべきところは正射影の設定を行うgl:orthoの部分です。サンプルからそのまま持ってきていたりすると画面の両端が-1から1までになってしまったりします。

つらかったこと

display関数内でエラーが起きるとプログラムが止まってしまい、強制終了させるとslimeも終了しまってデバッグがしにくいです。もっといいやり方はあると思うのですが…
また、glutのウィンドウを出しつつ、emacsのauto-completeを効かせるとEmacsの動作が止まってしまいます。これはC-gで抜け出せるので何とかなりますが。
もっとEmacsとSLIMEを使いこなしたいですね…

実行結果

f:id:soma_arc:20141224200118j:plain

おわり

面倒で放置していましたが、ようやくlispでグラフィックスを扱えるようになりました。あとはちゃんとしたGUIを使えるようにしたいのですが、道は長そうです。もっとLispを使ってLisp力を高めていきたいです。