Parametric 2L-systems integrated with Scheme. The L-systems defined with this library take a significantly different form from the string-based L-systems described by Lindenmayer. linden-scheme uses a class-based system for defining L-systems.
L-systems in linden-scheme are comprised of two parts: L-systems and rules, which are conceptually analogous to classes and methods, respectively.
- (define-rule [CLASS] (RULE-NAME [ARGS] ...) BODY ...)syntax
Defines a rule similar to defining a function, but with an optional CLASS. If CLASS is omitted, the rule will be used if no more specific rules are defined – i.e. as a fallback rule. Multiple rules with the same RULE-NAME may be defined, provided they have different classes (otherwise the previous rule with the same name will be over-written).
Rules should return a list of rules in (RULE [ARGS] ...) form. Any non-list value, when returned, will make evaluation of the rule treated as a no-op. In other words, when the rule is evaluated and a list is returned, the rule being evaluated in the current L-systems (by step-l-system) will be replaced by the given list of rules. If no list is provided, the rule being evaluated will remain the same.
The special form branch may be included in the list of rules returned by a rule. branch should contain one or more rules. When branch is used, the state of the rules contained in the branch is split off from that of the parent of the branch.
For example, a list returned by a rule may look like:
'((leaf 1) (branch (leaf 1) (stem 1) (apex)) (stem 1) (apex))
which describes a leaf, a branch (containing a leaf, stem, and apex), a stem, and an apex.
- (define-render-rule [CLASS] (RULE-NAME [ARGS] ...) BODY ...)syntax
define-render-rule behaves much like define-rule, to be used for the rendering of an L-system. Render rules operate by side-effect only. The parameter render-target is provided to track the object that the L-system is rendering to. See render-l-system and Turtle graphics.
- (define-l-system CLASS (SUPER-CLASSES ...) (RULE [ARGS] ...))syntax
Defines the class of L-systems named CLASS. These L-systems will use rules defined for CLASS, and if none are available, they will inherit those from the classes SUPER-CLASSES. The rules defined for the super-classes are chosen in order of appearance in the super-classes list.
Additionally a function named CLASS is defined that, when called, returns an L-system of CLASS with the initial rule RULE.
- step-l-system SYSTEMprocedure
Returns a new L-system, created by evaluating each rule in SYSTEM according to the rules defined by define-rule corresponding to the class of the L-system. Any rule that has not been defined by define-rule for the class or super-classes of the system is ignored. When rules are being evaluated, any state is branched according to branch statements in the system (see Manipulating state).
- render-l-system SYSTEM RENDER-TARGETprocedure
Evaluates each rule in the SYSTEM, in order, given their meanings defined by define-render-rule. Any rule that has not been defined by define-render-rule for the class or super-classes of the system is ignored. As with step-l-system, state is branched according to the branch statements in the system. The parameter render-target is set to RENDER-TARGET at the start of evaluation (see Turtle graphics). The value of render-target is returned.
- (context (TEST BODY ...) ...)syntax
Similar to a cond form, but conditional to the context of a given rule. Evaluates BODY when the context given in TEST matches the current context of the rule being evaluated. Each TEST should be of the form:
((PREVIOUS-RULE [ARGS] ...) (NEXT-RULE [ARGS] ...) [: GUARD])
When PREVIOUS-RULE and NEXT-RULE match the names of the previous and next rules to the rule being currently evaluated, the associated BODY is evaluated at the exclusion of all other BODYs. The supplied ARGS are symbols that are bound to the values of the arguments of their respective rules. Either (PREVIOUS-RULE [ARGS] ...) or (NEXT-RULE [ARGS] ...) may be replaced with a *, indicating that any rule (including none) may match.
If desired, a GUARD form may be supplied, preceded by a :. This guard acts as an additional test before a BODY is evaluated. The guard may use any variables given as ARGS (as well as any other variables in scope).
The last TEST may consist of the symbol else, which is unconditionally evaluated.
(define-rule (apex) (context (((stem len) * : (> len 2)) '((leaf 1) (branch (leaf 1) (stem 1) (apex)) (stem 1) (apex))) (((stem len) *) #f) (else '((leaf 1) (stem 1) (apex)))))
defines a rule (apex) that creates a new branch when preceded by a stem whose first parameter (len) greater than 2, does nothing when preceded by a stem of length less than or equal to 2, and otherwise creates a new leaf and stem.
- (probability (PROBABILITY BODY ...) ...)syntax
Similar to a cond form, but evaluates a clause based on a given probability. PROBABILITY is expected to be a number less than 1.0, or the symbol else. The sum of all probabilities should add up to 1.0, or less than 1.0 if an else clause is used. Otherwise there is a chance that no clauses will be evaluated (in the case that the probabilities add to less than 1.0 and no else is used) or a clause may never be evaluated (in the case that the probabilities add to more than 1.0).
(probability (0.1 '((branch (flower)) (apex))) (0.7 #f) (else '((branch (flower)) (branch (flower)) (apex)))
describes a 10% chance of creating a branch with a flower, a 70% chance of doing nothing, and a 20% chance of creating two branches with flowers.
While rendering L-systems, it is often desirable to track the state of a number of variables, following the branches in the L-system. In this manner, one can implement any type of turtle graphics system. linden-scheme provides this mechanism through the following three functions:
- define-state VAR DEFAULTprocedure
Creates a new state variable named VAR with the default value DEFAULT.
- get-state VARprocedure
Returns the value of the state variable VAR. This is only useful when called within a rule.
- set-state VAR VALUEprocedure
Sets the value of the state variable VAR to VALUE. This is only useful when called within a rule.
linden-scheme provides a set of functions (often also render rules) that implement the standard turtle-graphics commands typically associated with graphical L-systems. These commands are intended to be a standard foundation for turtle graphics, but do not provide any graphical output mechanism themselves. Rather, they provide a convenient parameter for holding some sort of rendering target, as well as a set of procedures that are used to modify and access the usual geometric qualities: namely translation/rotation matrices and thickness. Two state variables are therefore defined for the turtle graphics system: transform-matrix, rotation-matrix, and thickness, although facilities are provided so they do not need to be accessed through get-state or set-state.
Used to hold whatever the render rules are rendering to. Is set by render-l-system.
- pitch ANGLEprocedure
Transform the current transformation matrix by a rotation of ANGLE degrees around the x-axis. Also defined as a render rule.
- roll ANGLEprocedure
Transform the current transformation matrix by a rotation of ANGLE degrees around the y-axis. Also defined as a render rule.
- turn ANGLEprocedure
Transform the current transformation matrix by a rotation of ANGLE degrees around the z-axis. Also defined as a render rule.
- move X Y Zprocedure
Transform the current transformation matrix by a translation of (X Y Z). Also defined as a render rule.
- move-forward DISTANCEprocedure
Transform the current transformation matrix by a translation of (0 DISTANCE 0). Also defined as a render rule.
- thickness #!optional Xprocedure
- grow SCALEprocedure
Multiplies the value of the state variable thickness by SCALE. Also defined as a render rule.
The following is an example of a L-system of a crocus taken from The Algorithmic Beauty of Plants (Prusinkiewicz, Lindermayer. 1990), section 3.1.3.
Note that three different classes (crocus, plant, and none) are used in this example. If, for example, a new rule stem were defined for the class crocus, it would be used over the stem rule that is defined for plant.
(use linden-scheme srfi-1) (define developmental-switch-time 3) (define leaf-growth-limit 4) (define flower-growth-limit 2) (define-rule crocus (apex time) (cond ((< time developmental-switch-time) `((stem 1) (branch (pitch -30) (leaf 0)) (roll 138) (apex ,(add1 time)))) (else '((stem 20) (flower 0))))) (define-rule crocus (flower size) (cond ((< size flower-growth-limit) `((flower ,(add1 size)))))) (define-rule plant (stem length) (cond ((< length 4) `((stem ,(add1 length)))))) (define-rule (leaf size) (cond ((< size leaf-growth-limit) `((leaf ,(add1 size)))))) (define-l-system crocus (plant) (apex 1)) (for-each (lambda (i) (print (step-l-system-times i (crocus)))) (iota 5 1))
For another, more visual example corresponding to the image at the top of this document, see the examples directory.
24 April 2016
- Add rotation-matrix
- Initial release
Source available on GitHub.
Bug reports and patches welcome! Bugs can be reported via GitHub or to alex.n.charlton at gmail.