lsystems.turtle

Everything to do with drawing the L-system to the screen.

We use the excellent Clojure2D to do the rendering, and we implement a simple version of turtle graphics–where a pen is controlled by movement commands and lines are drawn out along its movement path.

The central object is the pen state, produced by (new-pen-state ...), which stores the current pen position and orientation, whether or not the pen is down, a stack for pushing and popping the pen position and orientation, and a list for storing the generated line segments, which are added by the function (forward [pen-state by-pixels]) if the pen is down.

The actual rendering is done by the (draw-lines [canvas line-segments]) function.

Line segments are of the form of a map { :from { :x :y } :to { :x :y } }.

draw-lines

(draw-lines canvas line-segments)
Draw the line segments given by `line-segments` onto the canvas.

execute-state-with-rules

(execute-state-with-rules state rules pen-state)
Take the L-system state, a rules map from L-system characters to functions that take and return
the current pen state, and the initial pen state, and executes each character of the L-system
state in turn, returning the pen state containing the finished line segments in (pen-state :lines).

fit-line-segments-to-screen

(fit-line-segments-to-screen width height line-segments & {:keys [padding], :or {padding 0}})
Resize and move all line segments so that they fit on screen. Can optionally pass :padding keyword
to pad figure on every side of screen.

TODO: can I use transducers to speed this up? I need to profile it first.

forward

(forward pen-state by-pixels)
Move the pen forward by `by-pixels` in the direction specified by (pen-state :facing), adding a line segment
to (pen-state :lines) if the pen is down. Returns the updated pen-state.
If (pen-state :continue-line-segment?) is true the last line segment will be extended rather than creating a new one.
Sets (pen-state :continue-line-segment?) to true.

get-pos-and-angle

(get-pos-and-angle pen-state)
Get the position and facing angle from the pen state.

make-canvas

(make-canvas width height)

new-line-segment

(new-line-segment from-x from-y to-x to-y)
Create a new line segment.

new-pen-state

(new-pen-state x y & {:keys [facing pen-is-down?], :or {facing 0, pen-is-down? true}})
Creates a new pen state object. Keeps track of the pens current position, its orientation, whether or not it is
down, a stack for pushing and popping the current position and orientation, and a list for storing calculated line
segments to be rendered later.

pen-down

(pen-down pen-state)
Forward will draw lines, as well as moving the cursor. TODO: test

pen-up

(pen-up pen-state)
Forward will no longer draw lines, it will just move the cursor. TODO: test

pop-pos-and-angle

(pop-pos-and-angle pen-state)
Pop the pens position and facing direction, off the stack into the pen state's current values.
Sets (pen-state :continue-line-segment?) to false.

push-pos-and-angle

(push-pos-and-angle pen-state)
Push the pens position and facing direction onto the stack.
Sets (pen-state :continue-line-segment?) to false.

render-to-canvas-by-executing-state-with-rules

(render-to-canvas-by-executing-state-with-rules state rules f & {:keys [width height initial-x initial-y facing canvas-function auto-resize? auto-resize-padding], :or {width 600, height 600, initial-x 300, initial-y 300, facing 0, canvas-function identity, auto-resize? true, auto-resize-padding 100}})

Setup a window and canvas and pen-state with given options and execute a given L-system state, and function f that takes the rendered canvas as an argument.

The :canvas-function key should be a function that takes a canvas object and can be used to do extra drawing or changing the canvas settings before rendering the lines.

Use the :auto-resize? key along with :auto-resize-padding to automatically resize the drawn figure to fit on the screen.

Description of all keys:

  • :width :height integer, width and height in pixels of the canvas, default 600 600
  • :initial-x :initial-y integer, initial position of the pen. Does nothing if auto-resize? is true. default 300 300
  • :facing float, the initial facing direction of the pen. default 0
  • :canvas-function described above. default identity (do nothing)
  • :auto-resize? boolean, whether or not to automatically fit the drawing to the canvas. default true
  • :auto-resize-padding integer, number of pixels padding when fitting the drawing to the canvas. default 100

render-to-canvas-grid

(render-to-canvas-grid num-columns num-rows width height f states-and-rules & {:keys [padding canvas-function], :or {padding 50, canvas-function identity}})

Take num columns, num rows, canvas width, canvas height, a function that uses the resulting canvas and a list of maps with shape { :state :rules }, and lays the figures out in a grid.

Maps in the states-and-rules list can also have a couple of other optional keys to change the behaviour of each figure:

  • :facing which sets the initial facing direction in degrees
  • :canvas-function which is a function that takes the canvas and allows for changing settings before drawing the lines

This function also optionally takes a :canvas-function key which is the same but happens before any of the figures are drawn.

See here for an example of how to use this function.

TODO: clean this up a bit

rotate

(rotate pen-state by-angle)
Rotate the pen's facing direction and return the updated state.
Sets (pen-state :continue-line-segment?) to false.

save-canvas-to-file

(save-canvas-to-file canvas filename)

show-window

(show-window canvas window-name)

standard-rule-set

(standard-rule-set x delta)
A standard set of rules as used in http://algorithmicbotany.org/papers/abop/abop-ch1.pdf

F: move forward by `x` units
+: rotate clockwise by `delta` degrees
-: rotate anti-clockwise by `delta` degrees
[: push pos and angle to stack
]: pop pos and angle from stack