기하 변환은 또 다른 페인터를 반환한다.
페인터 변환은 그 페인터에 넘기는 프레임을 바꿉니다. beside와 below와 뒤집기와 회전과 재귀 분할은 프로시저를 합성하고 마지막 실행은 여전히 보통의 유한 선분 목록을 만듭니다.
몇 가지 프레임 변환만으로 재사용할 수 있는 그림 조합 언어를 어떻게 만들까요?
- 하위 프레임을 만들어 페인터 변환하기
- beside와 below를 프로시저 조합기로 정의하기
- 같은 변환 원시 연산으로 뒤집기와 회전 표현하기
- 기본 페인터를 바꾸지 않고 네 부분 합성 만들기
- 깊이에 따라 선분 수가 자라는 재귀 분할 만들기
- 렌더링한 SVG를 선택한 실행의 선분 transcript와 연결하기
transform-painter는 페인터의 선분 데이터를 다시 쓰지 않습니다. 단위 정사각형의 제어점 세 개를 바깥 프레임으로 옮기고 새 하위 프레임을 만듭니다. 원래 페인터는 그 하위 프레임 안에서 실행됩니다. beside와 below는 변환된 페인터 두 개를 만들고 선분 목록을 이어 붙이며 뒤집기와 회전도 제어점 세 개를 다르게 고른 결과입니다.
첫 실행은 변환한 갈매기 모양 네 개를 한 정사각형에 놓습니다. 두 번째는 right-split을 재귀적으로 정의합니다. 왼쪽 절반에는 원래 페인터를 두고 오른쪽 절반에는 작은 복사본 두 개를 위아래로 쌓습니다. 깊이 3에서 선분 세 개짜리 기본 페인터는 옮긴 선분 45개를 만듭니다. 시각화는 리스펙스가 출력한 SEG 줄만 그리며 브라우저 렌더링 제한은 런타임 자체의 실행 한도와 구분됩니다.
(begin
(define (make-vect x y) (list x y))
(define (xcor-vect vector) (car vector))
(define (ycor-vect vector) (cadr vector))
(define (add-vect left right)
(make-vect (+ (xcor-vect left) (xcor-vect right))
(+ (ycor-vect left) (ycor-vect right))))
(define (sub-vect left right)
(make-vect (- (xcor-vect left) (xcor-vect right))
(- (ycor-vect left) (ycor-vect right))))
(define (scale-vect factor vector)
(make-vect (* factor (xcor-vect vector))
(* factor (ycor-vect vector))))
(define (make-segment start end) (list start end))
(define (start-segment segment) (car segment))
(define (end-segment segment) (cadr segment))
(define (make-frame origin edge1 edge2)
(list origin edge1 edge2))
(define (origin-frame frame) (car frame))
(define (edge1-frame frame) (cadr frame))
(define (edge2-frame frame) (caddr frame))
(define (frame-coordinate-map frame)
(lambda (vector)
(add-vect
(origin-frame frame)
(add-vect
(scale-vect (xcor-vect vector) (edge1-frame frame))
(scale-vect (ycor-vect vector) (edge2-frame frame))))))
(define (map-segment coordinate-map segment)
(make-segment
(coordinate-map (start-segment segment))
(coordinate-map (end-segment segment))))
(define (segments->painter segment-list)
(lambda (frame)
(let ((coordinate-map (frame-coordinate-map frame)))
(map (lambda (segment)
(map-segment coordinate-map segment))
segment-list))))
(define (transform-painter painter origin corner1 corner2)
(lambda (frame)
(let* ((coordinate-map (frame-coordinate-map frame))
(new-origin (coordinate-map origin))
(mapped-corner1 (coordinate-map corner1))
(mapped-corner2 (coordinate-map corner2)))
(painter
(make-frame
new-origin
(sub-vect mapped-corner1 new-origin)
(sub-vect mapped-corner2 new-origin))))))
(define (beside left right)
(let ((paint-left
(transform-painter
left
(make-vect 0.0 0.0)
(make-vect 0.5 0.0)
(make-vect 0.0 1.0)))
(paint-right
(transform-painter
right
(make-vect 0.5 0.0)
(make-vect 1.0 0.0)
(make-vect 0.5 1.0))))
(lambda (frame)
(append (paint-left frame)
(paint-right frame)))))
(define (below bottom top)
(let ((paint-bottom
(transform-painter
bottom
(make-vect 0.0 0.0)
(make-vect 1.0 0.0)
(make-vect 0.0 0.5)))
(paint-top
(transform-painter
top
(make-vect 0.0 0.5)
(make-vect 1.0 0.5)
(make-vect 0.0 1.0))))
(lambda (frame)
(append (paint-bottom frame)
(paint-top frame)))))
(define (flip-horiz painter)
(transform-painter
painter
(make-vect 1.0 0.0)
(make-vect 0.0 0.0)
(make-vect 1.0 1.0)))
(define (flip-vert painter)
(transform-painter
painter
(make-vect 0.0 1.0)
(make-vect 1.0 1.0)
(make-vect 0.0 0.0)))
(define (rotate180 painter)
(transform-painter
painter
(make-vect 1.0 1.0)
(make-vect 0.0 1.0)
(make-vect 1.0 0.0)))
(define (emit-segment segment)
(let ((start (start-segment segment))
(end (end-segment segment)))
(display "SEG ")
(display (xcor-vect start))
(display " ")
(display (ycor-vect start))
(display " ")
(display (xcor-vect end))
(display " ")
(display (ycor-vect end))
(newline)))
(define (paint painter frame)
(let ((segments (painter frame)))
(for-each emit-segment segments)
(list 'segments (length segments))))
(define chevron
(segments->painter
(list
(make-segment (make-vect 0.1 0.12)
(make-vect 0.5 0.9))
(make-segment (make-vect 0.5 0.9)
(make-vect 0.9 0.12))
(make-segment (make-vect 0.25 0.42)
(make-vect 0.75 0.42)))))
(define quartet
(below
(beside chevron (flip-horiz chevron))
(beside (flip-vert chevron) (rotate180 chevron))))
(paint quartet
(make-frame (make-vect 0.04 0.04)
(make-vect 0.92 0.0)
(make-vect 0.0 0.92)))
)- 출력
- —
- 값
- —
- 진단
- —
네 부분 합성은 SEG 줄 열두 개를 출력하고 (segments 12)를 반환합니다. 깊이 3 right-split은 SEG 줄 45개를 출력하고 (segments 45)를 반환합니다. 두 SVG 모두 선택한 리스펙스 transcript에서 만들어집니다.
기본 페인터가 선분을 옮기기 전에 transform-painter가 하위 프레임을 만드는 각 호출을 찾으세요. right-split에서는 재귀 깊이를 따라 반환되는 페인터 프로시저를 본 뒤 프로시저 합성과 paint 시점의 유한 선분 생산을 구분하세요.
힌트를 보기 전에 프로그램을 바꿔 보세요.
원래 페인터를 아래에 두고 작은 복사본 두 개를 옆으로 놓아 그 위에 쌓는 up-split을 정의하세요. 깊이 3을 그리기 전에 선분 수를 예상하세요.
힌트 하나 보기
below painter (beside smaller smaller)를 사용하세요. 양의 깊이마다 현재 복사본 하나와 작은 복사본 두 개를 만들므로 선분 수 점화식은 right-split과 같습니다.