The simplest implementation of L-Systems requires term-rewriting, and maybe turtle graphics. We already have arbitrary computation on the execution end, but what if we could add computation on the term rewriting phase? We can get a lot more flexibility without adding an infinite number of rules.
Also the last L-System post has had its code fixed.
We can start by copying the old L-System code over to a new file(Or not, if you don’t care). But we should probably address some issues that were there before. Namely that we can do a lot better for ourselves by declaring accessor functions such as rule-name, rule-replacement and rule-execution to allow us to change the representation if we want.(I learned it from Peter Norvig and he seems to have done alright with it)
(defun define-rule(name execution replacement)
(push `(,name . (,execution . ,replacement)) *rules*))
(defun rule-name(item)
(if (listp item)
(car item)
item))
(defun rule-execution(item)
(cadr (assoc (rule-name item) *rules*)))
(defun rule-replacement(item)
(caddr (assoc (rule-name item) *rules*)))
And we need to change run-replacements and execute-rules to handle argument lists in the input tape.
(defun run-replacements(state)
(loop for i in state
for (_name . (_execution . replacement)) = (or (assoc (rule-name i) *rules* ) i)
do(print i)
when (functionp replacement)
appending (apply replacement (cdr i))
else appending replacement))
(defun execute-rules(state)
(loop for i in state
for (_name . (execution . _replacement)) = (assoc (rule-name i) *rules*)
when(listp i)
do(apply execution (cdr i))
else
do(funcall execution)))
So let’s try a modification of the Barnsley fern rule as listed on wikipedia. In this we add a rule called A that takes two parameters and generates a certain number of tokens based on that input, as well as a call to itself.
(define-rule 'X (lambda()) '(F + #\[ #\[ X #\] - X #\] - F #\[ - F X #\] + X ))
(define-rule 'F (lambda()(forward *forward-amount* t)) '(F F))
(define-rule #\[ (lambda() (push-turtle)) '(#\[))
(define-rule #\] (lambda() (pop-turtle)) '(#\]))
(define-rule '+ (lambda() (turn (deg2rad 25.0))) '(+))
(define-rule '- (lambda() (turn (deg2rad -25.0))) '(-))
(define-rule 'A (lambda(_a _b))
(lambda(x y) (append (make-list x :initial-element 'X) '(- #\[)
`((A ,(max (1- x) 0) ,(max (1- y) 0)))
'(#\])
(make-list y :initial-element 'X))))
And then we run (iterate-n-then-execute 5 '((A 3 3))) to get
The next step to replicating concepts from L-Lisp is the environmentally sensitive L-Systems(necessary to have procedurally generated ivy cover a wall with L-Systems, for example).
Source Listing
(require :img-genner)
(defparameter *image* (img-genner:make-image 640 480))
;; The turtle's stack
(defparameter *stack* nil)
(defparameter *lines* nil)
(defparameter *turtle-x* 320.0)
(defparameter *turtle-y* 0.0)
(defparameter *turtle-angle* (coerce (/ pi 2) 'single-float))
(defun reset-turtle()
(loop for i from 0 below (array-total-size *image*)
do(setf (row-major-aref *image* i) 0))
(setf *stack* nil *lines* nil *turtle-x* 320.0 *turtle-y* 0.0
*turtle-angle* (coerce (/ pi 2) 'single-float)))
(defun deg2rad(n)
(* n (/ (coerce pi 'single-float) 180)))
(defun turn(d)
(incf *turtle-angle* d))
(defun forward(amount line)
(let ((sx *turtle-x*) (sy *turtle-y*))
(setf *turtle-x* (+ *turtle-x* (* amount (cos *turtle-angle*)))
*turtle-y* (+ *turtle-y* (* amount (sin *turtle-angle*))))
(when line
(push (list sx sy *turtle-x* *turtle-y*) *lines*))
)
)
(defun push-turtle()
(push (list *turtle-x* *turtle-y* *turtle-angle*) *stack*))
(defun pop-turtle()
(destructuring-bind (x y a) (pop *stack*)
(setf *turtle-x* x *turtle-y* y *turtle-angle* a)))
(defun stroke-drawing(stroker)
(loop for (x1 y1 x2 y2) in *lines*
do(stroke-line *image* x1 y1 x2 y2 stroker)
))
(defparameter *rules* nil)
(defparameter *forward-amount* 10.0)
(defun define-rule(name execution replacement)
(push `(,name . (,execution . ,replacement)) *rules*))
(defun rule-name(item)
(if (listp item)
(car item)
item))
(defun rule-execution(item)
(cadr (assoc (rule-name item) *rules*)))
(defun rule-replacement(item)
(caddr (assoc (rule-name item) *rules*)))
(defun run-replacements(state)
(loop for i in state
for (_name . (_execution . replacement)) = (or (assoc (rule-name i) *rules* ) i)
when (functionp replacement)
appending (apply replacement (cdr i))
else appending replacement))
(defun execute-rules(state)
(loop for i in state
for (_name . (execution . _replacement)) = (assoc (rule-name i) *rules*)
when(listp i)
do(apply execution (cdr i))
else
do(funcall execution)))
(define-rule 'X (lambda()) '(F + #\[ #\[ X #\] - X #\] - F #\[ - F X #\] + X ))
(define-rule 'F (lambda()(forward *forward-amount* t)) '(F F))
(define-rule #\[ (lambda() (push-turtle)) '(#\[))
(define-rule #\] (lambda() (pop-turtle)) '(#\]))
(define-rule '+ (lambda() (turn (deg2rad 25.0))) '(+))
(define-rule '- (lambda() (turn (deg2rad -25.0))) '(-))
(define-rule 'A (lambda(_a _b))
(lambda(x y) (append (make-list x :initial-element 'X) '(- #\[)
`((A ,(max (1- x) 0) ,(max (1- y) 0)))
'(#\])
(make-list y :initial-element 'X))))
(defparameter ffmpeg (uiop:launch-program "ffmpeg -r 20 -f png_pipe -i - -y -vf \"minterpolate='me=umh:search_param=32:fps=30'\" -b:v 3M -preset placebo L-system.webm" :input :stream))
(defun finish()
(uiop:close-streams ffmpeg)
(uiop:wait-process ffmpeg))
(defun iterate-n-then-execute(n tape)
(loop repeat n
with stroker = (img-genner:static-color-stroker (img-genner:rgb 255 0 0))
for state = tape then (run-replacements state)
do(reset-turtle)
finally(progn
(execute-rules state)
(loop for (sx sy ex ey) in (reverse *lines*)
do(img-genner:stroke-line *image* sx sy ex ey stroker)
do(img-genner:stroke-line *image* (1+ sx) sy (1+ ex) ey stroker)
do(img-genner:save-image *image* (uiop:process-info-input ffmpeg))))))
Leave a Reply
You must be logged in to post a comment.