Table of Contents
Turtle Graphics in Lisp .
Lisp is a language for building languages.
Using global state, we can write special purpose sublanguages with little effort.
Two choices:
- use plain Lisp
- use macro system
I go with the first one.
Scenes
A scene is a collection of shapes that are written to a json file and then processed by the rust renderer.
(require "asdf") (require :generative) (defclass scene () ((shapes :initform '()))) (defun make-scene () (make-instance 'scene)) (defmethod add-shape ((s scene) shape) (push shape (slot-value s 'shapes))) (defmethod encode-json ((s scene) out) (encode-json-object s '(shapes) out)) (defvar *scene* (make-scene)) (defmacro with-scene (&rest body) `(let ((*scene* (make-scene))) ,@body))
Turtle
A global variable is used to store a 2D graphics turtle, and
(defclass turtle () ((position :initarg :position :accessor turtle-position) ;; Angle in radians (direction :initarg :direction :accessor turtle-direction) (scale :initarg :scale :initform 1.0))) (defun make-turtle () (make-instance 'turtle :position (vec2 0.0) :direction 0.0)) (defvar *turtle* (make-turtle))
(defmethod copy-turtle ((turtle turtle)) (with-slots (direction position scale) turtle (make-instance 'turtle :position position :direction direction :scale scale))) (defmethod forward (by &optional drawing) (with-slots ((p position) (d direction) (s scale)) *turtle* (let ((p-new (add p (mult (vec2-from-angle d) (* by s))))) (if drawing (add-shape *scene* (make-line p p-new))) (setf p p-new)))) (defmethod backward (by &optional drawing) (with-slots ((p position) (d direction) (s scale)) *turtle* (let ((p-new (sub p (mult (vec2-from-angle d) (* by s))))) (if drawing (add-shape *scene* (make-line p p-new))) (setf p p-new)))) (defmethod turtle-scale (by) (with-slots ((s scale)) *turtle* (setf s (* s by)))) (defmethod rotate-left (by) (with-slots (direction) *turtle* (setf direction (mod (+ direction (rad by)) PII)))) (defmethod rotate-right (by) (with-slots (direction) *turtle* (setf direction (mod (- direction (rad by)) PII))))
Helper Macros
(defmacro with-copy (&rest body) `(let ((*turtle* (copy-turtle *turtle*))) ,@body)) (defmacro with-scale (by &rest body) `(let ((*turtle* (copy-turtle *turtle*))) (turtle-scale ,by) ,@body))
Branching System
(defvar leaf-angle 30) (defvar leaf-bend 30) (defvar leaf-step 0.5) (defvar leaf-div 8) (defvar branch-angle 5) (defun leaf () (with-copy (rotate-left leaf-bend) (repeat (1+ leaf-div) (forward leaf-step t) (rotate-right (/ (* 2 leaf-bend) leaf-div)))) (with-copy (rotate-right leaf-bend) (repeat (1+ leaf-div) (forward leaf-step t) (rotate-left (/ (* 2 leaf-bend) leaf-div))))) (defun branch (steps angle) (repeat steps (with-copy (with-copy (rotate-right leaf-angle) (leaf))) (forward 1.0 t) (with-copy (rotate-left leaf-angle) (leaf)) (forward 1.5 t) (rotate-right angle) (turtle-scale 0.95)) (leaf)) (setq *turtle* (make-turtle)) (with-scene (rotate-right 180) (branch 8 4) (with-output (out "/tmp/out.json") (encode-json *scene* out)) (sb-ext:run-program "/home/leon/.cargo/bin/plot_2d" (list "/tmp/out.json" "/home/leon/org/pages/images/turtle_graphics/branch.svg" "-a" "--type" "svg" "--format" "20,20") :output *standard-output*))
Recursion
(defun branch2 (steps angle) (repeat steps (with-copy (with-copy (rotate-right leaf-angle) (branch steps angle))) (forward 1.0 t) (with-copy (rotate-left leaf-angle) (branch steps angle)) (forward 1.5 t) (rotate-right angle) (turtle-scale 0.95)) (leaf)) (setq *turtle* (make-turtle)) (with-scene (rotate-right 180) (branch2 8 4) (with-output (out "/tmp/out.json") (encode-json *scene* out)) (sb-ext:run-program "/home/leon/.cargo/bin/plot_2d" (list "/tmp/out.json" "/home/leon/org/pages/images/turtle_graphics/branch2.svg" "-a" "--type" "svg" "--format" "20,20") :output *standard-output*))