File: l-systems.lisp

package info (click to toggle)
cl-pdf 117-1
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 1,860 kB
  • ctags: 844
  • sloc: lisp: 8,897; makefile: 39
file content (165 lines) | stat: -rw-r--r-- 7,083 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165


;; This an adaptation to cl-pdf of the postscript Lindenmayer System generator
;; code posted by Frank Buss in comp.lang.lisp.
;;
;; The original post is here:
;; http://groups.google.de/group/comp.lang.lisp/browse_thread/thread/05631fa93379bca8/248b67466ca2aa7d
;; 
;; Frank Buss wrote:
;; 
;; I've implemented a Lindenmayer System generator, with postscript output.
;; A L-System is a fractal, which can be used to describe plants. The
;; program implements the commands described here:
;; 
;; http://www.biologie.uni-hamburg.de/b-online/e28_3/lsys.html
;; 
;; Some example outputs (converted to PDF, because it is shorter)
;; 
;; http://www.frank-buss.de/tmp/bush.pdf
;; http://www.frank-buss.de/tmp/snow.pdf
;; http://www.frank-buss.de/tmp/dragon.pdf
;; 
;; The code is below. But it is too slow. I think it is the function
;; "starts-with" and the string handling, perhaps it could be faster with
;; lists, because then the substitute step could be implemented faster.
;; 
;; How can I profile the program? I know the "time"-macro, but would be
;; nice to have an utility, which writes for every function or every line
;; the time after program termination.
;; 
;; And it would be nice to test new rules interactive, without the need to
;; produce a postscript file, first. Is this possible with LTK or some other
;; GUI interface?
;; 
;; Another idea: Enhancing it to 3D output and color, like this program:
;; http://home.wanadoo.nl/laurens.lapre/lparser.htm

(defun starts-with (string search start)
  "Returns true, if the string 'search' is at 'start' in 'string'."
  (let ((string-len (length string))
        (search-len (length search)))
    (if (> (+ start search-len) string-len)
        nil
        (equal (subseq string start (+ start search-len)) search))))

(defun search-longest (rules string start)
  "Returns the successor and predecessor length, if a predecessor
   is found in 'string' at 'start'. Returns the longest match."
  (let ((found-successor nil)
        (found-predecessor-length 0))
    (loop for (predecessor . successor) in rules do
          (when (starts-with string predecessor start)
            (let ((predecessor-length (length predecessor)))
              (when (>= predecessor-length found-predecessor-length)
                (setq found-predecessor-length predecessor-length)
                (setq found-successor successor)))))
    (values found-successor found-predecessor-length)))

(defun apply-rules (rules axiom)
  "Returns the next iteration, starting with the string in 'axiom'."
  (let ((result ""))
    (loop for i from 0 to (1- (length axiom)) do
          (multiple-value-bind (successor predecessor-length)
              (search-longest rules axiom i)
            (if successor
                (progn
                  (setq result (concatenate 'string result successor))
                  (setq i (1- (+ i predecessor-length))))
                (progn
                  (setq result (concatenate 'string result (subseq axiom i (1+ i))))))))
    result))

(defun l-system (rules axiom depth)
  "Returns 'depth' iterations, starting with 'axiom' and applying the 'rules'."
  (let ((result axiom))
    (loop repeat depth do
          (setq result (apply-rules rules result)))
    result))

(defun forward (point len angle)
  "Returns a new point by starting from 'point' and adding the polar coordinates 'len' and 'angles'."
  (let ((x (car point))
        (y (cdr point))
        (rad (* (/ pi 180.0) angle)))
    (cons (+ x (* (sin rad) len)) (+ y (* (cos rad) len)))))

(defun do-l-system (commands len angle fun)
  "Calls 'fun x0 y0 x1 y1' for every command in the 'commands' string."
  (let ((point-stack '())
        (angle-stack '())
        (current-point '(0e0 . 0e0))
        (current-angle 0e0))
    (loop for i from 0 to (1- (length commands)) do
          (let ((command (elt commands i)))
            (cond ((eq command #\f)
                   (setq current-point (forward current-point len current-angle)))
                  ((eq command #\F)
                   (let ((next-point (forward current-point len current-angle)))
                     (funcall fun (car current-point) (cdr current-point) (car next-point) (cdr next-point))
                     (setq current-point next-point)))
                  ((eq command #\+)
                   (setq current-angle (+ current-angle angle)))
                  ((eq command #\-)
                   (setq current-angle (- current-angle angle)))
                  ((eq command #\[)
                   (push current-point point-stack)
                   (push current-angle angle-stack))
                  ((eq command #\])
                   (setq current-point (pop point-stack))
                   (setq current-angle (pop angle-stack))))))))

(defun pdf-l-system (rules axiom length angle depth &key (x0 0)(y0 0) (file #P"/tmp/l-system.pdf"))
  "Calculates and prints a Lindenmayer System as pdf."
  (pdf:with-document ()
    (pdf:with-page ()
      (pdf:translate (+ x0 287) (+ y0 400))
      (pdf:set-line-width 0)
      (do-l-system (l-system rules axiom depth) length angle
                   (lambda (x0 y0 x1 y1)
                     (pdf:move-to x0 y0)
                     (pdf:line-to x1 y1)))
      (pdf:stroke))
    (pdf:write-document file)))
    
;; this one takes longer but adjusts the scaling and centering so that it fits on the page
(defun pdf-l-system-centered (rules axiom angle depth &key (file #P"/tmp/l-system.pdf"))
  "Calculates and prints a Lindenmayer System as pdf."
  (pdf:with-document ()
    (pdf:with-page ()
      (pdf:set-line-width 0)
      (let ((commands (l-system rules axiom depth))
            (min-x 1e30)
            (min-y 1e30)
            (max-x -1e30)
            (max-y -1e30))
        (do-l-system commands 1 angle
                     (lambda (x0 y0 x1 y1)
                       (when (< x0 min-x) (setq min-x x0))
                       (when (< y0 min-y) (setq min-y y0))
                       (when (< x1 min-x) (setq min-x x1))
                       (when (< y1 min-y) (setq min-y y1))
                       (when (> x0 max-x) (setq max-x x0))
                       (when (> y0 max-y) (setq max-y y0))
                       (when (> x1 max-x) (setq max-x x1))
                       (when (> y1 max-y) (setq max-y y1))))
        (let* ((length (/ 500.0 (max (- max-y min-y) (- max-x min-x)))) 
               (dx (* 0.5 (- 595 (* (+ max-x min-x) length))))
               (dy (* 0.5 (- 841 (* (+ max-y min-y) length)))))
          (pdf:translate dx dy)
          (do-l-system (l-system rules axiom depth) length angle
                       (lambda (x0 y0 x1 y1)
                         (pdf:move-to x0 y0)
                         (pdf:line-to x1 y1)))
          (pdf:stroke))))
    (pdf:write-document file)))
    
;;; dragon, needs some minutes to calculate with depth 16
; (pdf-l-system-centered '(("FL"."FL+FR+") ("FR"."-FL-FR")) "FL" 90 16)

;;; snowflake
; (pdf-l-system-centered '(("F"."F+F--F+F")) "F--F--F" 60 7)

;;; bush
; (pdf-l-system-centered '(("F"."FF-[-F+F+F]+[+F-F-F]")) "+F" 23 5)