User Tools

Site Tools


tutorials:qsm:to-growable

Turning a QSM into a growable model

In the following tutorial we will take a small QSM file, transform the existing cylinders into modules and add additional organs(Buds, Leaves) in order to simulate simple forgoing growth. This tutorial requires knowledge about the programming language RGG (see: RGG Code structure introduction) as well as basic XL-queries (see: XL queries and operators).

For this tutorial we will use this very small QSM: tiny_tree2.qsm. This dataset was originally measured with the fastrak stylus and is later translated into a QSM

Shoots

In order to add more variables or functions to the individual shoots, we first turn our imported shoots into our own kind of shoots. For now the module MyShoot can be very simple (basically just a brown cylinder):

module MyShoot(super.length, super.diameter) extends F{{setShader(EGA_6);}}

To use these new shoots we need a rule to turn all imported F's into MyShoot's of the same length and diameter.

f:F ==> MyShoot(f.length,f.diameter);

Yet there is an important addition here, that is different to normal L-System rule: we need to keep the local transformation that keeps the cylinders in place.

We can do this by applying an expression directly to the newly created shoot using '.()'. In this expression we set the transform value of the new object(represented by the $ sign) to the value of the old object.

f:F ==> MyShoot(f.length,f.diameter).($[transform]=f[transform]);

This is for now the core of our update function, which we can also extend by using the qsm library function jumpAllAxis(); to minimize the gaps between the imported shoots:

step1.rgg
import static de.grogra.qsm.utils.Library.*;
module MyShoot(super.length, super.diameter) extends F{{setShader(EGA_6);}}
 
public void update()[
{ jumpAllAxis();}
 f:F ==> MyShoot(f.length,f.diameter).($[transform]=f[transform]);
]

Adding buds

In order to create new shoots we first need new buds, therefore the module “MyBud” is created:

module MyBud(int order, float length) extends Sphere(0.002){{setShader(EGA_3);}}

This module does not do much more than being a place holder for next years shoots. It contains the knowledge of the order as well as the proposed length of the new shoot. To improve the visibility the bud is displayed by a turquoise sphere.

Apical

To enable further apical growth, we will place a bud of the same order as the shoot on each shoot that does not have a apical successor.

We can estimate this by testing if the RTREND (reverse Trend) is 0. In that case we replace the F not only by a new MyShoot but additionally by a new MyBud with the same order and proposed length of 70% of the original f. :

f:F,(f[RTREND]==0) ==> MyShoot(f.length,f.diameter).($[transform]=f[transform])MyBud(f[BO],f.length*0.7);

Lets pick for now a very simple growth rule: every bud of order 0 or 1 is replaced by a shoot of the proposed length with a diameter estimated based on the length(5%) and a new bud of the same order and a new 70% of the proposed length of the old bud:

MyBud(o,l),(o<2) ==> MyShoot(l,l/25) MyBud(o,l*0.7);

These changes leads to the following RGG code:

step2.rgg
import static de.grogra.qsm.utils.Library.*;
import static de.grogra.qsm.utils.Descriptors.*;
module MyShoot(super.length, super.diameter) extends F{{setShader(EGA_6);}}
module MyBud(int order, float length) extends Sphere(0.002){{setShader(EGA_3);}}
public void update()[
	{
 jumpAllAxis();
	}
	f:F,(f[RTREND]==0) ==> MyShoot(f.length,f.diameter).($[transform]=f[transform])MyBud(f[BO],f.length*0.7);
	f:F ==> MyShoot(f.length,f.diameter).($[transform]=f[transform]);
]
 
 
public void grow()[
	MyBud(o,l),(o<2) ==> MyShoot(l,l/20) MyBud(o,l*0.7);
 
]

This model applied on our small example tree should result in the following output:

Imported tree updated tree 1. growth step 2. growth step

Lateral

For the lateral buds we again use a simplified assumption: Each of the shoot has two lateral buds placed in opposite position at 2/3th of the shoot. These buds have a branch order one higher than the parent shoot and a proposed length of 50% of the parent shoot.

Since GroIMP works with local transformation and the buds are added at the end of the new shoot we move backwards not forwards.

f:F,(f[RTREND]==0) ==> MyShoot(f.length,f.diameter).($[transform]=f[transform])
 [
  M(-0.3*f.length)  
  [
    RL(65)MyBud(f[BO]+1,f.length*0.5)
  ]
  [
    RL(-65)MyBud(f[BO]+1,f.length*0.5)
  ]
 ]
 MyBud(f[BO],f.length*0.7);

If we use the same code not only for the reconstructed shoots but for the new created once as well, and increase the limit on which order buds can grow to 3 (so we actually see lateral branches emerge), we end with the following code:

lateralBuds.rgg
import static de.grogra.qsm.utils.Library.*;
import static de.grogra.qsm.utils.Descriptors.*;
 
module MyShoot(super.length, super.diameter) extends F{{setShader(EGA_6);}}
module MyBud(int order, float length) extends Sphere(0.002){{setShader(EGA_3);}}
public void update()[
 {
  jumpAllAxis();
 }
 f:F,(f[RTREND]==0) ==> MyShoot(f.length,f.diameter).($[transform]=f[transform])
   [
    M(-0.3*f.length)  
    [
     RL(65)MyBud(f[BO]+1,f.length*0.5)
    ]
    [
     RL(-65)MyBud(f[BO]+1,f.length*0.5)
    ]
   ]
   MyBud(f[BO],f.length*0.7);
 
 f:F ==> MyShoot(f.length,f.diameter).($[transform]=f[transform]);
]
 
 
public void grow()[
 MyBud(o,l),(o<3) ==> MyShoot(l,l/20) 
  [
   M(-0.3*l)  
   [
    RL(65)MyBud(o+1,l*0.5)
   ]
   [
    RL(-65)MyBud(o+1,l*0.5)
   ]
  ]
  MyBud(o,l*0.7);
]
Imported tree updated tree 1. growth step 2. growth step

Adding Leaves

As final organ for this tutorial we want to add leaves to our little tree. The leaves are represented by simple parallelograms,with an additional M to shifted them a bit to make them not appear to grow on the inside of the shoot:

 module Leaf ==> M(0.001) Parallelogram(0.012,0.01).(setShader(GREEN));	

We assume that our Leaves have a fixed size, based on the proportions of the original tree.

Lets place two of them each at 1/4 and 3/4 length on our shoot, with one pair oriented to the left and right and one pair up and down:

with_leaves.rgg
import static de.grogra.qsm.utils.Library.*;
import static de.grogra.qsm.utils.Descriptors.*;
module MyShoot(super.length, super.diameter) extends F{{setShader(EGA_6);}}
module MyBud(int order, float length) extends Sphere(0.002){{setShader(EGA_3);}}
module Leaf ==> M(0.001) Parallelogram(0.012,0.01).(setShader(GREEN));	
public void update()[
 {
  jumpAllAxis();
 }
 f:F,(f[RTREND]==0) ==> MyShoot(f.length,f.diameter).($[transform]=f[transform])
  [
   M(-0.3*f.length)  
   [
    RL(65)MyBud(f[BO]+1,f.length*0.5)
   ]
   [
    RL(-65)MyBud(f[BO]+1,f.length*0.5)
   ]
  ]
  [
  M(-0.25*f.length)
  [
   RL(80)Leaf()
  ]
  [
   RL(-80)Leaf()
  ]
 ]
 [
  M(-0.75*f.length)RH(90)
  [
   RL(80)Leaf()
  ]
  [
   RL(-80)Leaf()
  ]
 ]
 MyBud(f[BO],f.length*0.7);
 
 f:F ==> MyShoot(f.length,f.diameter).($[transform]=f[transform]);
]
 
 
public void grow()[
 MyBud(o,l),(o<3) ==> MyShoot(l,l/20) 
  [
   M(-0.3*l)  
   [
    RL(65)MyBud(o+1,l*0.5)
   ]
   [
    RL(-65)MyBud(o+1,l*0.5)
   ]
  ]
  [
   M(-0.25*l)
   [
    RL(80)Leaf()
   ]
   [
    RL(-80)Leaf()
   ]
  ]
  [
   M(-0.75*l)RH(90)
   [
    RL(80)Leaf()
   ]
   [
    RL(-80)Leaf()
   ]
  ]
  MyBud(o,l*0.7);
]
Imported tree updated tree 1. growth step 2. growth step

Outlook

Possible next steps would be:

  • To add an age to the leaves (so they die after a year or two)
  • To add secondary growth to the shoots (so they stretch a bit over the years)
  • To add image shades to the different organs
  • To set up a light model and implements a simple photo synthesis model.
tutorials/qsm/to-growable.txt · Last modified: 2025/10/23 15:52 by Tim