; Drawing fancy curves with higher-order procedures

(require (lib "math.ss"))
(require (lib "graphics.ss" "graphics"))

(open-graphics)

(define size 400)

(define w (open-viewport "FRACTALS" size size))

(define (graphics-draw-line window start-pt end-pt)
  ((draw-line window) (make-posn (* size (posn-x start-pt))
                                 (- size (* size (posn-y start-pt))))
		      (make-posn (* size (posn-x end-pt))
		                 (- size (* size (posn-y end-pt))))))

(define (graphics-draw-pixel window point)
  ((draw-pixel window) (make-posn (* size (posn-x point))
                                  (- size (* size (posn-y point))))))

                               ;Unit-Interval = {x: Sch-Num | 0 <= x <= 1}
                               ;Curve = Unit-interval --> Point

                               ;;SOME CURVES

(define (unit-circle t)
  (make-posn (sin (* 2 pi t))
             (cos (* 2 pi t))))

(define (unit-line t)
  (make-posn t 0))

(define (alternative-unit-circle t)
  (make-posn (sin (* 2 pi (square t)))
             (cos (* 2 pi (square t)))))

                                        ;Curve-Transform = (Curve --> Curve)

                              ;;SOME CURVE-TRANSFORMS


(define (rotate-pi/2 curve)
  (lambda (t)
    (let ((ct (curve t)))
      (make-posn
       (- (posn-y ct))
       (posn-x ct)))))

                           ;;CONSTRUCTORS OF CURVE-TRANSFORMS

;;; TRANSLATE is of type (Sch-Num, Sch-Num --> Curve-Transform)

(define (translate x0 y0)               
  (lambda (curve)
    (lambda (t)
      (let ((ct (curve t)))
        (make-posn (+ x0 (posn-x ct))
                   (+ y0 (posn-y ct)))))))

;;; ROTATE-AROUND-ORIGIN is of type (Sch-Num --> Curve-Transform)

(define (rotate-around-origin theta)
  (let ((cth (cos theta))
        (sth (sin theta)))
    (lambda (curve)
      (lambda (t)
        (let ((ct (curve t)))
          (let ((x (posn-x ct))
                (y (posn-y ct)))
            (make-posn
             (- (* cth x) (* sth y))
             (+ (* sth x) (* cth y)))))))))

(define (scale-x-y a b)
  (lambda (curve)
    (lambda (t)
      (let ((ct (curve t)))
        (make-posn (* a (posn-x ct))
                   (* b (posn-y ct)))))))

(define (scale s)
  (scale-x-y s s))

;;; SQUEEZE-RECTANGULAR-PORTION translates and scales a curve
;;; so the portion of the curve in the rectangle
;;; with corners xlo xhi ylo yhi will appear in a display window
;;; which has x, y coordinates from 0 to 1.
;;; It is of type (Sch-Num,Sch-Num,Sch-Num,Sch-Num --> Curve-Transform).

(define (squeeze-rectangular-portion xlo xhi ylo yhi)
  (compose (scale-x-y (/ 1 (- xhi xlo))
                      (/ 1 (- yhi ylo)))
           (translate (- xlo) (- ylo))))

;;; PUT-IN-STANDARD-POSITION is a Curve-Transform.
;;; A curve is in "standard position" if it starts at (0,0) ends at (1,0).
;;; A curve is PUT-IN-STANDARD-POSITION by rigidly translating it so its
;;; start point is at the origin, then rotating it about the origin to put
;;; its endpoint on the x axis, then scaling it to put the endpoint at (1,0).

(define (put-in-standard-position curve)
  (let* ((start-point (curve 0))
         (curve-started-at-origin
          ((translate (- (posn-x start-point))
                      (- (posn-y start-point)))
           curve))
         (new-end-point (curve-started-at-origin 1))
         (theta (atan (posn-y new-end-point) (posn-x new-end-point)))
         (curve-ended-at-x-axis
          ((rotate-around-origin (- theta)) curve-started-at-origin))
         (end-point-on-x-axis (posn-x (curve-ended-at-x-axis 1))))
    ((scale (/ 1 end-point-on-x-axis)) curve-ended-at-x-axis)))

                                ;Binary-transform = (Curve,Curve --> Curve)

;;; CONNECT-RIGIDLY makes a curve consisting of curve1 followed by curve2.

(define (connect-rigidly curve1 curve2)
  (lambda (t)
    (if (< t (/ 1 2))
	(curve1 (* 2 t))
	(curve2 (- (* 2 t) 1)))))

;;; CONNECT-ENDS makes a curve consisting of curve1 followed by
;;;  a copy of curve2 starting at the end of curve1

;;;(define (connect-ends curve1 curve2) ...)

                          ;;FRACTAL CURVES

;;; GOSPERIZE is a Curve-Transform

(define (gosperize curve)
  (let ((scaled-curve ((scale (/ (sqrt 2) 2)) curve)))
    (connect-rigidly ((rotate-around-origin (/ pi 4)) scaled-curve)
                     ((translate .5 .5)
                      ((rotate-around-origin (/ (- pi) 4)) scaled-curve)))))


;;; GOSPER-CURVE is of type (Sch-Num --> Curve)

(define (gosper-curve level)
  ((repeated gosperize level) unit-line))

                      ;;DRAWING GOSPER CURVES

(define (show-connected-gosper level)
  ((draw-connected w 200)
   ((squeeze-rectangular-portion -.5 1.5 -.5 1.5)
    (gosper-curve level))))

                      ;;PARAMETERIZED GOSPER

;;; PARAM-GOSPER is of type ((Sch-Num,(Int --> Sch-Num)) --> Curve)

(define (param-gosper level angle-at)
  (if (= level 0)
      unit-line
      ((param-gosperize (angle-at level))
       (param-gosper (- level 1) angle-at))))

(define (param-gosperize theta)
  (lambda (curve)
    (let ((scale-factor (/ (/ 1 (cos theta)) 2)))
      (let ((scaled-curve ((scale scale-factor) curve)))
        (connect-rigidly ((rotate-around-origin theta) scaled-curve)
                         ((translate .5 (* (sin theta) scale-factor))
                          ((rotate-around-origin (- theta)) scaled-curve)))))))

;******************************************************************************
; Some Utility functions



(define (compose f g)
  (lambda (x)
    (f (g x))))

(define (identity t) t)

(define (repeated f n)
  (if (= n 0)
      identity
      (compose f (repeated f (- n 1)))))

(define (square x) (* x x))

(define (draw-connected window n)
  (let ((1/n (/ 1 n)))
    (lambda (curve)
      (define (iter p-old count)
        (let ((t (* count 1/n)))
          (let ((p-new (curve t)))
              (graphics-draw-line window p-old p-new)
              (if (>= count n)
                  'done
                  (iter p-new (+ count 1))))))
      ((clear-viewport window))
      (let ((c0 (curve 0)))
        (iter c0 1)))))

(define (draw-points-on window n)
  (let ((1/n (/ 1 n)))
    (lambda (curve)
      (define (iter p-old count)
        (let ((t (* count 1/n)))
          (let ((p-new (curve t)))
              (graphics-draw-pixel window p-new)
              (if (>= count n)
                  'done
                  (iter p-new (+ count 1))))))
      ((clear-viewport window))
      (let ((c0 (curve 0)))
        (graphics-draw-pixel window c0)
        (iter c0 1)))))
