L-systems can get very complex very quickly. If you want full control over exactly how the plant grows and what its final shape looks like, you’re in for a long haul. There are a few ways to get “artistic” control over an L-system, like using metaballs to influence rules depending on the turtle’s position inside or outside the volume, but in general a single L-system has to control everything just with variables, which gets tedious quickly. It gets even worse when you want complex branching structures.

I was following along with the excellent L-systems tutorial (part 2) from cmiVFX, which offers a way to nest L-systems using some creative use of the Copy SOP. If you’ve checked out the tutorial, it’s in an older version of Houdini which historically crashed when you tried to connect L-systems together as Leaf inputs. While this isn’t the case anymore with Houdini 12, you still miss out on the big advantage of the Copy SOP method, which is manipulating point attributes and using these to set properties of the branch L-system using copy stamping.

I won’t go into the finer details of setting up this method; you really should just watch the tutorial I linked to in order to understand how the Copy SOP method of nesting L-systems works. Basically there is a “trunk” L-system, which has rules that call for a “J” leaf. The “J” leaf receives a simple curve with three points, one at the center, another straight above the center (+Y), and another along the positive Z axis. After this curve is copied onto all the J inputs in the L-system, you can use the difference in position of these points relative to each other to determine a normal and up vector for each instance of the curve, and write these vectors to the points. Then the points other than the center points are deleted, along with the trunk, leaving you with a bunch of points from which branches will sprout, each with attributes N and up. These attributes are automatically used by the Copy SOP to orient an instance of a second “branch” L-system to each point. Again, this is a very quick write-up of a complex network, and you should watch the tutorial. Hit the jump for more…

A problem that I quickly ran into using this method, though, is that each branch is actually being transformed and placed onto the main trunk. Because each branch is no longer in world space due to this transformation, they can’t predictably behave exactly as if they were part of the trunk L-system. This is especially evident if you try to use the tropism (T) command on the branching system; each branch copy thinks that “down” is slightly different, because they have all likely been rotated (see Fig. 1).

Fig. 1: The nested L-system (left) and the source branch (right). The trunk system is colored red. Note that tropism points the source branch straight down, but the copied branches bend inwards instead of down because they have been transformed.

The only way to fix this problem is not to rotate or orient the branch copies (by turning off “Transform Using Template Point Attributes” on the Copy SOP). How, then, to orient the branches properly?

The trick is to use the L-systems language to handle the orientation. It requires a few additions to your L-systems rules, but it’s not a ton of extra work. We’ll just need to make use of that up vector attribute that’s created using the Copy SOP nesting method.

On the Copy SOP that’s copying your branches onto the trunk, stamp three variables with values $UPX, $UPY and $UPZ. We’ll tell the branch L-system to use these values to re-orient the turtle to the correct up vector on its first generation. Add three variables to your branch L-system; mine are called lm and n. Set their values to be equal the stamped $UPX, $UPY and $UPZ attributes from the Copy SOP, using stamp expressions.

Now you need a rule on this branching system that orients the turtle to these stamped values, but only on the first generation (we won’t get very far if we try to re-orient the turtle for every generation). Here’s what the rules look like for this system:
X(h): (h=0) = $(l,m,n) ^(0.1) FX(h+1)
X(h): (h>0) = T FX(h+1)

The $(x,y,z) operator sets the up vector for the turtle. By setting this vector to match the up vector of the original L-system at the first generation of each branch, we can get the branches to start orienting themselves as if they were really part of the trunk L-system. The ^(0.1) operator pitches the branch slightly so that the tropism command has some idea of which way to go; otherwise, the branch would be pointing straight up and unable to bend. If your setup works, it should look something like Fig. 2.

Fig. 2: The branching L-system is getting its up vector from the original L-system’s branch points, and orienting to it on the first generation for each copy.

If for whatever reason your branches are facing the wrong way, it could be that your up vector isn’t facing the right direction (the cmiVFX tutorial reverses the up and normal vectors in their example, in order to better work with the Copy SOP). You can compensate for this by pitching the branch up or down by 90 degrees, either in the trunk system just before the J operator is used, or on the first generation rule of the branch system.

There are a ton of other things you can do with nested L-systems using the Copy SOP. You can stamp variables from the original trunk system and apply them to the branch system, and modify them using VOPs before they’re sent to the Copy SOP. For example, you could use a ramp parameter to determine the silhouette of the branches as a whole, by dividing the maximum number of generations of the trunk by the birth generation of each branch, mapping that value to a ramp, and then multiplying by a scalar. The result is sent to create a new point attribute, which is then stamped to the branch L-system in the Copy SOP.

I realize this probably makes little sense, so I’m attaching an example scene that shows all of this behavior. There are three example nested L-systems of increasing complexity, with notes. You can download the .HIP file here.

Please feel free to post here if you have any questions or comments!

Categories: Houdini