src/looms/004-turtle-curves.lisp @ 308e324d06da

Add turtle curves
author Steve Losh <steve@stevelosh.com>
date Thu, 15 Feb 2018 00:02:15 -0500
parents (none)
children 3e7390e1f690
(in-package :flax.looms.004-turtle-curves)

;;;; Turtle Graphics ----------------------------------------------------------
(defvar *step* 0.1)
(defvar *angle* 1/4tau)
(defvar *starting-angle* (- 1/4tau))
(defvar *color* nil)

(defstruct turtle
  (x 0.5)
  (y 0.5)
  (angle *starting-angle*))

(define-with-macro turtle x y angle)


(defun rot (angle amount)
  (mod (+ angle amount) tau))

(define-modify-macro rotf (amount) rot)


(defgeneric perform-command (turtle command))

(defmethod perform-command (turtle (command (eql 'f)))
  (with-turtle (turtle)
    (list (flax.drawing:path
            (list (coord x y)
                  (progn (perform-command turtle 's)
                         (coord x y)))
            :color *color*))))

(defmethod perform-command (turtle (command (eql 'fl)))
  (perform-command turtle 'f))

(defmethod perform-command (turtle (command (eql 'fr)))
  (perform-command turtle 'f))

(defmethod perform-command (turtle (command (eql 's)))
  (with-turtle (turtle)
    (incf x (* *step* (cos angle)))
    (incf y (* *step* (sin angle))))
  nil)

(defmethod perform-command (turtle (command (eql '-)))
  (rotf (turtle-angle turtle) *angle*)
  nil)

(defmethod perform-command (turtle (command (eql '+)))
  (rotf (turtle-angle turtle) (- *angle*))
  nil)


(defun find-bounds (paths)
  (iterate (for path :in paths)
           (for (p1 p2) = (flax.drawing:points path))
           (maximizing (x p1) :into max-x)
           (maximizing (x p2) :into max-x)
           (maximizing (y p1) :into max-y)
           (maximizing (y p2) :into max-y)
           (minimizing (x p1) :into min-x)
           (minimizing (x p2) :into min-x)
           (minimizing (y p1) :into min-y)
           (minimizing (y p2) :into min-y)
           (finally (return (list min-x min-y max-x max-y)))))

(defun scale (paths)
  (iterate
    (with (min-x min-y max-x max-y)  = (find-bounds paths))
    (with factor = (min (/ (- max-x min-x))
                        (/ (- max-y min-y))))
    (with x-padding = (/ (- 1.0 (* factor (- max-x min-x))) 2))
    (with y-padding = (/ (- 1.0 (* factor (- max-y min-y))) 2))
    (with offset-x = (+ (- min-x) x-padding))
    (with offset-y = (+ (- min-y) y-padding))
    (for path :in paths)
    (for (p1 p2) = (flax.drawing:points path))
    (zapf
      (x p1) (* factor (+ offset-x %))
      (y p1) (* factor (+ offset-y %))
      (x p2) (* factor (+ offset-x %))
      (y p2) (* factor (+ offset-y %)))))

(defun turtle-draw (commands)
  (let ((paths (mapcan (curry #'perform-command (make-turtle)) commands)))
    (scale paths)
    paths))


;;;; L-Systems ----------------------------------------------------------------
(defun expand (word productions)
  (mappend (lambda (letter)
             (ensure-list (or (getf productions letter) letter)))
           word))

(defun run-l-system (axiom productions iterations)
  (iterate
    (repeat iterations)
    (for word :initially axiom :then (expand word productions))
    (finally (return word))))


(defmacro define-l-system (name-and-options axiom &body productions)
  (destructuring-bind (name &key (angle 1/4tau))
      (ensure-list name-and-options)
    `(defun ,name (iterations)
       (values (run-l-system ',(ensure-list axiom)
                             ',productions
                             iterations)
               ,angle))))

(define-l-system quadratic-koch-island-a (f - f - f - f)
  f (f - f + f + f f - f - f + f))

(define-l-system quadratic-koch-island-b (f - f - f - f)
  f (f + f f - f f - f - f + f + f f - f - f + f + f f + f f - f))

(define-l-system quadratic-snowflake (- f)
  f (f + f - f - f + f))

(define-l-system islands-and-lakes (f + f + f + f)
  f (f + s - f f + f + f f + f s + f f - s + f f - f - f f - f s - f f f)
  s (s s s s s s))

(define-l-system unnamed-koch-a (f - f - f - f)
  f (f f - f - f - f - f - f + f))

(define-l-system unnamed-koch-b (f - f - f - f)
  f (f f - f - f - f - f f))

(define-l-system unnamed-koch-c (f - f - f - f)
  f (f f - f + f - f - f f))

(define-l-system unnamed-koch-d (f - f - f - f)
  f (f f - f - - f - f))

(define-l-system unnamed-koch-e (f - f - f - f)
  f (f - f f - - f - f))

(define-l-system unnamed-koch-f (f - f - f - f)
  f (f - f + f - f - f))

(define-l-system dragon-curve fl
  fl (fl + fr +)
  fr (- fl - fr))

(define-l-system (sierpinski-gasket :angle (/ tau 6)) fr
  fl (fr + fl + fr)
  fr (fl - fr - fl))

(define-l-system (hexagonal-gosper-curve :angle (/ tau 6)) fl
  fl (fl + fr + + fr - fl - - fl fl - fr +)
  fr (- fl + fr fr + + fr + fl - - fl - fr))


;;;; Main ---------------------------------------------------------------------
(defun loom (seed filename width height)
  (nest
    (with-seed seed)
    (destructuring-bind (l-system min-iterations max-iterations)
        (random-elt '((quadratic-koch-island-a 2 5)
                      (quadratic-koch-island-b 2 4)
                      (quadratic-snowflake 3 7)
                      (islands-and-lakes 1 4)
                      (unnamed-koch-a 3 5)
                      (unnamed-koch-b 3 6)
                      (unnamed-koch-c 3 6)
                      (unnamed-koch-d 2 5)
                      (unnamed-koch-e 5 7)
                      (unnamed-koch-f 5 7)
                      (dragon-curve 7 16)
                      (sierpinski-gasket 4 10)
                      (hexagonal-gosper-curve 3 6))
                    #'rand))
    (let ((*starting-angle* (rand tau))
          (bg (hsv (rand 1.0) (rand 1.0) (random-range 0.0 0.2 #'rand)))
          (*color* (hsv (rand 1.0)
                        (random-range 0.5 1.0 #'rand)
                        (random-range 0.8 1.0 #'rand)))
          (iterations (random-range-inclusive min-iterations
                                              max-iterations
                                              #'rand))))
    (flax.drawing:with-rendering (image filename width height :background bg))
    (multiple-value-bind (shapes *angle*) (funcall l-system iterations))
    (progn (-<> shapes
             (turtle-draw <>)
             (flax.drawing:render image <>))
           (list l-system iterations))))

;; (time (loom nil "out.png" 1000 1000))