Title: Use improved L-systems to draw more fractals in Python
The post Use L-systems to draw fractals in Python explained how to use L-systems in Python to draw fractals. Often L-systems also include the [ and ] characters. Then the program is drawing an L-system script and it reaches the [ character, it pushes the current position and direction onto a stack. When it reaches a ] character, the program pops those values off of the stack. This allows the fractal to return to a previously saved position.
To allow the program to have a little more flexibility, I also added a length scale that the program can use to make each segment shorter than the previous one. This is mostly useful for trees, but it sometimes produces some interesting results for other curves.
I also added an initial direction for the fractal so you can make trees and ferns start pointing upward. You can probably achieve a similar result by beginning the axiom with some - or + characters, but this is slightly cleaner.
get_l_system_paths
The following code shows the new get_l_system_paths method that generates the fractal's points.
def get_l_system_paths(depth, theta, axiom, ignore, rules, dtheta,
initial_theta, length_scale):
'''Apply an L-system.'''
# Build the final script.
script = axiom
for i in range(depth):
new_script = ''
for ch in script:
if ch in rules:
new_script += rules[ch]
else:
new_script += ch
script = new_script
# print(script)
# Create the points.
p1 = (0, 0)
length = 100
paths = [] # A list of paths
path = [p1] # The current path
theta = initial_theta
waypoints = [] # Places we will go back to
wayangles = [] # Angles for the waypoints
waylengths = []
for ch in script:
if ch in ignore:
pass
elif ch == '-':
theta -= dtheta
elif ch == '+':
theta += dtheta
elif ch == '[':
waypoints.append(path[-1])
wayangles.append(theta)
waylengths.append(length)
elif ch == ']':
if len(path) > 1: paths.append(path)
p1 = waypoints.pop()
path = [p1]
theta = wayangles.pop()
length = waylengths.pop()
else:
x = p1[0] + length * math.cos(theta)
y = p1[1] + length * math.sin(theta)
p1 = (x, y)
path.append(p1)
length *= length_scale
if len(path) > 1:
paths.append(path)
# Return the points.
return paths
The method starts much as before, using the L-system's axiom and rules to build the final script. Things get more interesting when it's time to generate the fractal's points.
Because the fractal can now jump back to a previously saved location, the result cannot be a single polyline. To handle that, the method creates a paths list to hold one or more paths. (The fractals drawn with the previous example still work, they just use one path.)
The code also creates the waypoints, wayangles, and waylengths lists to hold saved positions.
Next, the code loops through the script's characters as before. It's very similar to the previous version except when it reaches a [ or ] character.
When the code reaches a [ character, it saves the current point, angle, and length in the waypoint lists.
When the code reaches a ] character, it first checks the current path. If that path contains at least two points, the method adds that path to the paths list. The code then pops the position, angle, and length values off of their lists and restores them. It also adds the restored point to a new path.
If the code encounters a character that isn't in the ignore list (see the previous example) and isn't one of the special characters -, +, [, or ], the code draws a segment. It then multiples the current length by the length scale factor to shorten the next segment if desired. (Just set that parameter to 1 if you don't want to scale the segments.)
After it has processed the script, the method again checks the length of the current path and, if it contains at least two points, the code adds it to the paths list.
Finally, the method returns the paths.
Other Changes
The program requires a couple of other changes. First, the draw method needs to be modified slightly to draw multiple paths. Second, the fit_curves_to_canvas method must be changed to fit a group of paths to the canvas instead of working with a single path. Those changes are relatively straightforward so they aren't shown here. Download the example to see the details.
Conclusion
Including the [ and ] characters lets L-systems save a position, draw, and then return to the saved position. Probably the simplest use of this is the following system, which draws the tree shown on the right.
Rule: | F: F[-F][+F] |
Axiom: | F |
Dtheta: | 90° |
Initial Angle: | 45° |
Length Scale: | 0.75 |
The single rule basically means: Draw forward and save the position. Then turn left, recursively draw another level of the fractal, and return to the saved position. Then turn right, recursively draw another level of the fractal, and return to the saved position.
The fern fractal shown on the right is generated by this system:
Rule: | X: F+[[X]-X]-F[-FX]+X |
Rule: | F: FF |
Axiom: | X |
Ignore: | X |
Dtheta: | -25° |
Initial Angle: | -50° |
Length Scale: | 1 |
You can read more about this fern on Wikipedia.
Download the example to see additional details and to experiment with the program.
|