User Tools

Site Tools


tutorials:architecture-model

Growth models step by step

In this tutorial we will create a growth model for a crown architecture. The model considers only the branch structure, without leaves, to provide an easy entry point. It may be helpful to understand the basic RGG code structure first, a tutorial is provided here.

The model is based on an annual scale, meaning that each run of the growth function simulates one year of growth. This way of simulating a tree in GroIMP is only one of many, and this tutorial focuses on understanding the development.

Organs

As mentioned above, the model only considers the branching structure. Therefore it needs only two organs, shoots and buds.

Shoots

In this tutorial, a shoot is always an annual shoot, i.e. the part of a branch that has grown in one year. We do not take into account the bending of a shoot, so we can describe a shoot as a cylinder.

In GroIMP, our shoot will therefore be a module that extends the turtle command F, which draws a cylinder.

module Shoot extends F;

Buds

Each bud considered in this tutorial will generate a shoot, as the other organs that emerge from buds are not considered in the model.

In our GroIMP model, a bud is represented by a small sphere. This means that our bud module extends the sphere object with a parameter for its diameter.

module Bud extends Sphere(0.1);

The initial condition

In this model we assume that the simulation starts with a bud. Of course in reality it would start with a seed, but for now we will keep it simple.

As described in the code structure tutorial, the initial state of the simulation is described in the init function. In this case we replace the axiom of our model with a bud.

protected void init ()
[
 Axiom ==> Bud;
]

Apical growth

Looking at our two organs, we can define our very first rule: a bud becomes a shoot, or more precisely every bud becomes a shoot. Writing this in an rewriting rule is quite straightforward: Bud ==> Shoot . But with this rule the fun would soon be over, because we can only apply our rule once, after that there are no more buds in our scene.

This means that our rule has to be a bit more complex, not just for fun, but to keep up with real plant growth. In many cases a bud will not only produce a shoot but also new buds on the shoot. Our example tree will have a bud right at the end of the shoot, this bud is called apical and describes apical growth. So our rule is really every bud becomes a shoot and a new bud.

As an XL query this can be described as Bud ==> Shoot Bud and in our model we can now create a first growth function:

public void grow ()
[
 Bud ==>Shoot Bud;
]

Applying our new rule three times would always replace the bud with a new shoot and a bud, leaving the old shoots underneath the bud untouched. The yellow bud turns into a yellow shoot and a red bud, the red bud then turns into a red shoot an a green bud and so on…

Parameters

With our newly created model we can now create a chain of shoots step by step, basically a branch 8-) . However, the growth of our model is completely linear and therefore quite unnatural. Even for a very simple model we would like to add some decrease in the length of the shoots over time, as we know it from most trees. So we would like the behaviour shown in the figure below:

Formally, we want our bud to produce a shoot that is shorter than the shoot below it. This leads to two problems: firstly, how can we define the length of the shoot, and secondly, how can the bud know this length? A first step brings us back to our organs. A shoot is no longer just a cylinder, it is now a cylinder of a certain length. In GroIMP we describe this as follows:

 module Shoot(float l) extends F(l);

This describes that a Shoot can handle a float parameter and also that this parameter is passed to the turtle command F as a length. This would allow us to do XL queries like : Bud ==> Shoot(2) Bud , which would produce shoots twice as long as before. This solves the first problem. For the second, we will look at our other organ, the bud. Since the bud creates the shoot, it makes sense to let it “know” the length of the shoot. So we add a new parameter to our bud organ:

module Bud(float len) extends Sphere(0.1);

This parameter is not used in the bud, it is just stored as knowledge (a variable).

Assuming that the length of all shoots is still 1, the initial rule would be Axiom ==> Bud(1)

and our growth rule

Bud ==> Shoot(1) Bud(1) .

In addition, we can use the parameter of the bud as information for the rule:

Bud(x) ==> Shoot(x)Bud(x)

This means that each bud in our model is now replaced by a shoot the length of that bud. And the same for the new bud. Finally, we can simply reduce the size for the next generation:

Bud(x) ==> Shoot(x)Bud(x*0.8) This means that if the initial (yellow) bud has a length parameter of 1, the yellow shoot will be of size 1, but the red bud will have a length parameter of 0.8. So in the next step the red shoot will be of size 0.8 and the green bud will have a length parameter of 0.64…

Lateral growth

Although our little model is improving, it can still only grow in one direction, which is not typical for a tree. Therefore, we will now add new shoots to the side of our main branch (trunk). The picture on the right shows what we would like to see after two stages of growth. This branches on the side are called lateral.

Knowing that our first rule is to turn a bud into a shoot and a bud, we can estimate what the first step towards the branched tree would have looked like. This can be seen in the picture on the left.

This brings us to the task of updating our rule. We now want to replace a bud with a shoot, a bud at the top and two buds at the sides.

Turtle Geometry

For this new rule we need to look at how GroIMP interprets the results of our rules. We have already defined above that a shoot is a cylinder and a bud is a sphere, and that our rule defines a chain of cylinders with a bud at the end.

This chain of cylinders and bud is stored in GroIMP in a graph, the project graph. This graph contains almost all information about the project and defines the core of a GroIMP simulation. It is also the basis for the visualisation of our model. As we can see in the left image below, each shoot in the graph is drawn as a cylinder in the 3D scene. The placement of these objects is based on the concept of turtle geometry. We can imagine that a “turtle” starts at position (0,0,0) in the 3D scene and then follows the instructions given by the nodes in the graph. Reading the graph starts at the top at Node.0 and goes down through the nodes. The first two nodes (Node.0 and RGGRoot) have no effect on the turtle, but a shoot tells the turtle to draw a cylinder and move to the end of that cylinder. So the second shoot starts at the end of the first shoot and the bud is placed at the end of the second cylinder.

Left: Our apical shoot as a drawn image, a GroIMP 2d Graph and the GroIMP visualization, Right: the changes in the graph when the rule is applied

GroIMP comes with a set of commands to manipulate this turtle by rotating, scaling, moving or chaining default values for color and size. (The most important ones can be found here: turtle_commands)

A first lateral bud

To get a feel for this turtle geometry, we will create a rule that turns the bud into a shoot with a bud on the right side. First we need the shoot and then we add the side bud. Knowing that the turtle will be at the end of the shoot after we draw it means that we have to move back from that position. So we can use the move command: M(distance).

Assuming our shoot has a length of $l$, the turtle will move l up with Shoot(l) to get to the middle of the shoot, we can just move back half the way with M(-0.5*l). This will bring us to the right position. Now we have to rotate our turtle so that the bud is pointing to the right.

You can rotate the turtle along the three axes x, y and z. We can see an example of the rotation tool on the right with the possible rotations in colors: x=red, y=blue and z=green. In the turtle geometry we can rotate these axes with the commands RL(angle), RU(angle) and RH(angle). RL rotates around the local X-axis (the red circle in the picture), RU around the Y-axis (the blue circle) and RH around the Z-axis. If we imagine to look from the perspective of the turtle, the names make more sense: RL rotates to the lleft (with negative values to the right), RU rotates up and RH rotates around the hhead axes.

For our little shoot with the apical bud, this means that we have to rotate it on the y-axis (up, if the branch would lie on the side). Therefore we need the RU command and the following rule:

Bud(x) ==> Shoot(x) M(-0.5*x) RU(30) Bud(x*0.8)

which, after 3 iterations, produces the “plant” below by drawing a shoot, moving back half the length rotation 30 degrees around the y-axis, and then starting over twice.

Growing in several directions

We already have two possible rules to create our little plant, we can either grow upwards or to the right. In the next step we want to do both in one rule. Doing this with a turtle moving back and forth would be very hard (sooner or later impossible).

In most L-Systems, and also in GroIMP, this is solved by using a stack of turtle states (the current position, rotation, scale color, etc.). This stack can be controlled by using square brackets [ ].

“[” means to add the current state to the stack of remembered states and “]” means to go back to the last remembered state. This concept allows us to let our turtle do little detours that combine our rules into one rule of replace a bud by a shoot, remember this state, move back half the length, rotate 30 degrees up, add a new bud, go back to the last state and add the apical bud. Or in XL:

Bud(x) ==> Shoot(x) [M(-0.5*x)RU(30) Bud(x*0.8)] Bud(x*0.8) The order of the elements (why we go back before adding the apical bud) becomes clear if we imagine that we apply the rule a second time. In this case, we want the second shoot to be added after the detour and not before (see the graphic below), otherwise the lateral shoot would be moved up with the new shoot.

In GroIMP's project graph, the “detour” part is added to the same node (in our case the shoot), but with a different edge (the connection between the nodes). The two different types of edges are called successors (the “main edges”) and branches (for the “detours”), in the 2D graph (see image below) these two edges are visualized by either solid or dashed arrows. GroIMP supports more and also custom types of edges, but these two are the main ones.

As mentioned above, the turtle state is stored in a stack, so we can move multiple turtle stacks into it. In our case, this allows us to add some more lateral buds around the shoot (rotated around the head axes):

Bud(x) ==> Shoot(x) [
 M(-0.5*x) [
  RU(30) Bud(x*0.8)
 ] RH(120) [
  RU(30) Bud(x*0.8)
 ] RH(120)[
  RU(30) Bud(x*0.8)
 ]
]Bud(x*0.8);

This gives us a nice little plant-like structure:

You can now play around with the values to change the shape of the tree, e.g. make the side branches shorter (x*0.5 instead of x*.0.8) or change the rotation angels etc.

Branching orders

If we look at trees, we can see that most of them have different growth behavior on the trunk and on the branches. To simulate this, we will use the concept of branching order. In this concept, the branches of a tree are separated into orders based on their parent. We define the trunk as order 0, and then each side branch on the trunk is order 1, and each side branch on order 1 is order 2, and so on. This can be seen in the image to the right.

We, like many other modelers before us, will use this concept to give our tree more structure by defining different growth rules for the different orders. Let us define just three for now:

  • A bud on the trunk (of order 0) creates a shoot, a new apical bud of order 0, and three lateral buds of order 1 around it.
  • A bud of order 1 creates a shoot, a new apical bud of order 1, and two lateral buds of order 2 to the left and right.
  • A bud of order 2 creates a shoot and a new bud of order 2.

To use these rules, we first need to know the branching order for each bud. To do this, we need to add an additional parameter to the bud module, as shown below:

 module Bud(int order, float len) extends Sphere(0.1);

And now we learn a new trick, because on the left side of an XL rule you can also specify which value a parameter of the selected node should have: Bud(0,x) will only rewrite buds of order 0. This allows us to specify our rule from above for order 0:

Bud(0,x) ==> Shoot(x) [
 M(-0.5*x) [
  RU(80) Bud(1,x*0.8)
 ] RH(120) [
  RU(80) Bud(1,x*0.8)
 ] RH(120)[
  RU(80) Bud(1,x*0.8)
 ]
]Bud(0,x);

We now replace only the buds of order 0 (the stem) and give the new buds the correct orders, the lateral buds get order 1 and the apical bud gets 0.

For the first-order branches, the new rule is as follows, with two buds rotated to the left and right.

Bud(1,x)==> Shoot(x)[
 M(-0.5*x)
 [
  RL(70) Bud(2,x*0.6)
 ]
 [
  RL(-70) Bud(2,x*0.6)
 ]
]Bud(1,x*0.8);

Finally, the second order is just as we have known it from the beginning.

 Bud(2,x) ==> Shoot(x)Bud(2,x*0.6);

These three rules result in a pretty nice looking structure. (Don't be confused, I resized the radius of the buds a bit)

Diameter

Now, in the last real step of this tutorial, we want to look at the diameters of our little tree. In the F class that our shoot extends, the diameter can be given as a second parameter: F(length,diameter). So we can just do the same as we did with the length and have a parameter d in our shoot that will be passed to the F:

 module Shoot(float l,float d) extends F(l,d);

To define this value in our rules, we will use a very simple estimation. Let us assume that the ratio between the length of a shoot and its diameter is 10:1. This means that in all our rules we can simply change the Shoots to Shoot(x,x/10).

Even though this gives our tree a nicer initial shape, there is something else we want to do with the diameter: secondary thickness growth. So we add a new rule which says that each shoot will be replaced by a new shoot of the same length but with a slightly larger diameter.

 Shoot(l,d)==>Shoot(l,d+0.02);

With the diameter, our tree now looks like this:

Final

That is basically it for this tutorial. Below you will find the code for this little tree. Now you can start playing around with it, adding more parameters and rotations to suit your needs.

 module Shoot(float l,float d) extends F(l,d);
 module Bud(int order, float len) extends Sphere(0.04);
 
 protected void init()[
 	 Axiom ==> Bud(0,1);
 ]
 
 public void grow()[
 
Bud(0,x) ==> Shoot(x,x/10) [
 M(-0.5*x) [
  RU(80) Bud(1,x*0.7)
 ] RH(120) [
  RU(80) Bud(1,x*0.7)
 ] RH(120)[
  RU(80) Bud(1,x*0.7)
 ]
]Bud(0,x*0.8);
 Bud(1,x)==> Shoot(x,x/10)[
 	 M(-0.5*x)
 	    [
 	 	 RL(70) Bud(2,x*0.6)
 	    ]
 	    [
 	 	 RL(-70) Bud(2,x*0.6)
 	    ]
 ]Bud(1,x*0.8);
 
 Bud(2,x) ==> Shoot(x,x/10)Bud(2,x*0.6);
 
 
 Shoot(l,d)==> Shoot(l,d+0.02);
 ]
tutorials/architecture-model.txt · Last modified: 2024/11/19 13:06 by tim