;; Koch snowflake fractal using using teachpack draw.ss
;;
;; also includes a GUI based control panel (used to create 
;;  koch snowflakes).


;; Some geometric stuff used to determine the endpoints of lines.
;; distance between 2 points
(define (dist p1 p2)
  (sqrt (+ 
		 (expt (- (posn-x p1) (posn-x p2)) 2)
		 (expt (- (posn-y p1) (posn-y p2)) 2))))
;;
;; given three points p0, p1 and p2, rotate p2 about p1 relative to
;; the angle determined by the direction from p0 to p1.

(define (rotate p0 p1 p2 angle)
  (local
   ((define hyp01 (dist p0 p1))
	(define hyp12 (dist p1 p2))
	(define angle01 (atan 
					 (- (posn-y p1) (posn-y p0)) 
					 (- (posn-x p1) (posn-x p0)))))
   (make-posn
	(+ (posn-x p1) (* hyp12 (cos (+ angle01 angle))))
	(+ (posn-y p1) (* hyp12 (sin (+ angle01 angle)))))))

(define PI 3.141593)

;;
;; draw-koch-line consumes two posn and a number and returns true.
;;
;; (draw-koch-line start end depth) draws a factal line. The line is
;; divided into three equal parts. The middle part is replace with two
;; lines of the same length as the 1/3 they replace, but at an angle.
;; depth controls whether or not the lines are draw as actual lines
;;  (depth=0) or as koch-lines.
;;
;;  no support for color here - always uses black...

(define (draw-koch-line start end depth)
  (cond 
   ;; depth 0 - we just draw the line.
   [(= 0 depth)
	 (draw-solid-line start end 'black)]
   [else
	(local
	 ;; compute the points 1/3 and 2/3rd of the way between
	 ;;    start and end.
	 ((define p1
	   (make-posn
		(+ (posn-x start)
		   (/ (- (posn-x end) (posn-x start)) 3))
		(+ (posn-y start)
		   (/ (- (posn-y end) (posn-y start)) 3))))
	 (define p2
	   (make-posn
		(+ (posn-x start)
		   (* (- (posn-x end) (posn-x start)) 2/3))
		(+ (posn-y start)
		   (* (- (posn-y end) (posn-y start)) 2/3))))
	 ;; compute the 3rd endpoint used to draw a koch line,
	 ;;   this involves rotating a point....
	 (define pmid (rotate start p1 p2 (- 0 (* PI 1/3 )))))
	 ;; draw the 4 lines that make up a koch line
	 ;;  (each is drawn recursively)
	 (and
	  (draw-koch-line start p1 (- depth 1))
	  (draw-koch-line p2 end (- depth 1))
	  (draw-koch-line p1 pmid (- depth 1))
	  (draw-koch-line pmid p2 (- depth 1))))]))


;;
;; draws a kock snowflake of specified depth.
;; 
;; p1 is the starting point - upper left of the initial triangle.
;;
(define (draw-koch-snowflake p1 len depth)
  (local
   ((define p2 (make-posn (+ len (posn-x p1)) (posn-y p1)))
	(define p3 (make-posn (/ (+ (posn-x p1) (posn-x p2)) 2)
						  (+ (posn-y p1) (* .866 len)))))
  (and
   (draw-koch-line p1 p2 depth)
   (draw-koch-line p2 p3 depth)
   (draw-koch-line p3 p1 depth))))


;; test: depth 8 koch snowflake
;;(start 400 400)
;;(draw-koch-snowflake (make-posn 100 100) 200 8)

;; GUI control system

(define (koch-control windowsize)
  (local
   ((define depth (make-text "Depth:"))
	(define width (make-text "Width:"))
	;; buton handler - called when the user presses the "Create" button
	(define (create-handler e)
	  (and
	   ;; create new window
	   (start windowsize windowsize)
	   ;; draw the right size snowflake in the window
	   (draw-koch-snowflake 
		(make-posn 50 100 ) 
		(string->number (text-contents width))
		(string->number (text-contents depth))))))
   (create-window
	(list
	 (list (make-message "Koch Snowflake Control Panel"))
	 (list depth)
	 (list width)
	 (list (make-button "Create" create-handler))))))

(koch-control 300)