関数型言語の勉強にSICPを読もう - (16) 2章 - データによる抽象の構築 - 2.2.4 図形言語(73-82ページ)

早くSchemeで絵を出したいので、 frame, painterの概念を学ぶところから進めようと思います。
幸い Gauche-glで線を引く部分は理解できているので、本文中の wave painterは実装できるとふんでいます。

フレーム座標写像について

文章を読んでもさっぱり分からず、うんうん唸って図を描いてみたら分かった。
人に説明する形で理解を確かめるテスト。


まず今回扱う図形や絵を 0<=x,y <=1 の正方形の中に描くというルールを決めます。
(この正方形のことを単位方形と呼んでいます。)
そして例えば、ある図形(例えばうさぎの絵)を特定のフレーム内に表示したいとします。

うさぎの目は単位方形内の(x1, y1)という点としてあらわされているのですが、実際のフレームではどこの座標になるでしょうか?
これを求めるのがフレーム座標写像です。(伝わったかな?)


これで大体イメージがつかめると思うので、実際にノートに図を描いてフレーム座標写像を試してみると理解が深まると思います。
これが理解できたので次にフレーム座標写像を実装していきます。

frame.scm

フレームを定義します。このファイルに含まれるコードは環境に依存しません。

;; frame
(define (make-frame origin edge1 edge2)
  (list origin edge1 edge2))

(define (origin-frame frame)
  (car frame))

(define (edge1-frame frame)
 (car (cdr frame)))

(define (edge2-frame frame)
  (car (cdr (cdr frame))))

orgin, edge1, edge2から frameを作ります。

vector.scm

frameは3つの vector (orgin, edge1, edge2)により定義されます。
このvector をここで定義します。

;; vector
(define (make-vect x y)
  (cons x y))

(define (xcor-vect v)
  (car v))

(define (ycor-vect v)
  (cdr v))

(define (add-vect v1 v2)
  (make-vect (+ (xcor-vect v1) (xcor-vect v2))
             (+ (ycor-vect v1) (ycor-vect v2))))

(define (sub-vect v1 v2)
  (make-vect (- (xcor-vect v1) (xcor-vect v2))
             (- (ycor-vect v1) (ycor-vect v2))))

(define (scale-vect s v)
  (make-vect (* s (xcor-vect v))
             (* s (ycor-vect v))))

segment.scm

今回利用する painter は線分によって構成されるので segment を定義します。
segment の定義には point を利用するのでそのコードもまとめてあります。
このファイルに含まれるコードは環境に依存しません。

;; point
(define (make-point x y)
  (cons x y))

(define (x-point p)
  (car p))

(define (y-point p)
  (cdr p))

(define (print-point p)
  (newline)
  (display "(")
  (display (x-point p))
  (display ",")
  (display (y-point p))
  (display ")"))

;; segment
(define (make-segment p1 p2)
  (cons p1 p2))

(define (start-segment s)
  (car s))

(define (end-segment s)
  (cdr s))

test.scm

上のsegment, vector, frame を利用してpainter を作り、実際に表示します。
図形を描くための要素は以下のとおり。

painter 描かれるものを示す手続き
frame painterを描く場所を示すデータ

つまり、図形painterを フレームframeの形に合わせて出力するのが目的となります。


このコードで環境に依存する部分は線を描く draw-line 関数です。
ここではあえて draw-line は線を描くのではなく線の座標を display するようにしています。
painter がきちんと動くようになるまでは、Open GLの部分につながないことでデバッグを容易にしようという意図です。(どこに問題があるか一目で分かるように)

(load "/home/taro/draw/segment.scm")
(load "/home/taro/draw/frame.scm")
(load "/home/taro/draw/vector.scm")

(define (draw-line p1 p2)
  (print-point p1)
  (print-point p2)
  )

(define (segments->painter segment-list)
  (lambda (frame)
    (for-each
     (lambda (segment)
       (draw-line
        ((frame-coord-map frame) (start-segment segment))
        ((frame-coord-map frame) (end-segment segment))))
     segment-list)))

(define (frame-coord-map frame)
  (lambda (v)
    (add-vect
     (origin-frame frame)
     (add-vect (scale-vect (xcor-vect v)
                           (edge1-frame frame))
               (scale-vect (ycor-vect v)
                           (edge2-frame frame))))))

(define s1 (make-segment (make-point 0 0) (make-point 1 1)))
(define s2 (make-segment (make-point 0 0) (make-point 0 1)))
(define segments (list s1 s2))

(define frame (make-frame (make-vect 1 1) (make-vect 1 0) (make-vect 0 1)))

(define (main args)
  ((segments->painter segments) frame)
  )

実際に実行してみると・・・。

gosh test.scm
(1,1)
(2,2)
(1,1)
(1,2)

なんとなく合っていそうですね。
次回はこれを実際に画面に表示してみます。(楽しみは後に取っておくタイプ)


※「SICPを読もう」の目次はこちら


計算機プログラムの構造と解釈
Gerald Jay Sussman Julie Sussman Harold Abelson 和田 英一
ピアソンエデュケーション (2000/02)
売り上げランキング: 56,404