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.
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 ifauto-resize?
is true. default 300 300:facing
float, the initial facing direction of the pen. default 0:canvas-function
described above. defaultidentity
(do nothing):auto-resize?
boolean, whether or not to automatically fit the drawing to the canvas. defaulttrue
: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.
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