User Tools

Site Tools


tutorials:simple-tomato-model

A simple tomato plant model

In this tutorial we will create a simple model of a tomato plant architecture.

Preliminary steps

We start a new project by opening a new RGG template model (File → New → RGG Project).

The simple model that we are going to create will have the following structure:

// modules
 
// parameters
 
protected void init()
[
    Axiom ==> 
        // initial structure
    ;
]
 
public void run()
[
    // rewriting rule
]

Organs

As first, we specify a base module Organ, having some properties that all type of organs in a plant will share. This module has an attribute length, inherited from a module M. This way, every type of organ derived from the Organ module will have attribute length. It also has an attribute rank that we will use to describe the position of organs from the bottom of the plant to the top.

module Organ(super.length) extends M(length)
{
    int rank;
 
    {
        rank = 1;
    }
}

Now we can specify a number of modules for different organ types that we observe in a tomato plant.

module Apex extends Organ;
module Internode extends Organ;
module Leaf extends Organ;
module Truss extends Organ;

Model parameters

Outside of the modules, we define model parameters that will be used later to specify the shape of organs.

// leaf parameters
const double LEAF_LENGTH = 0.40;
const double PETIOLE_WIDTH = 0.01;
const double PHYLLOTAXIS_ANGLE	= 137.51;
const double LEAF_ANGLE = 60;
 
// internode parameters
const double INTERNODE_LENGTH = 0.08;
const double INTERNODE_WIDTH = 0.02;
 
// truss parameters
const int NB_FRUITS = 5;
const double TRUSS_LENGTH = 0.1;
const double TRUSS_ANGLE = 60;
const double FRUIT_RADIUS = 0.02;

Specifying organ geometry

We extend the modules' definition by adding geometric representation for each organ type. The geometry of organs will be specified using instantiation rules ==>.

An Apex will be represented as a red sphere.

module Apex extends Organ
==>
    Sphere(0.01).(setShader(RED))
;

An Internode will be represented as a yellow cylinder. The length and width of the Internode will be specified inside the module, using the constants we defined above.

module Internode extends Organ 
{
    double width;
 
    {
        length = INTERNODE_LENGTH;
        width = INTERNODE_WIDTH;
    }
}
==>
    Cylinder(length, width/2).(setShader(YELLOW))
;

For a leaf, we will start with a very simple representation. It will be made of a petiole (a green cylinder) and a rectangular blade. A turtle command RL will be used to specify the angle of the leaf to the plant stem.

module Leaf extends Organ 
{
    double width;
    double angle;
    double petioleLength;
    double petioleWidth;
 
    {
        length = LEAF_LENGTH;
        width = 0.5*LEAF_LENGTH;
        angle = LEAF_ANGLE;
        petioleLength = 0.2*LEAF_LENGTH;
        petioleWidth = PETIOLE_WIDTH;	
    }
}
==> 
    RL(angle)
    // petiole
    Cylinder(petioleLength, petioleWidth/2).(setShader(GREEN))
    // leaf blade
    Parallelogram(length-petioleLength, width)
;

Truss will be made of multiple fruits (NB_FRUITS), each represented by a red sphere, connected to the main segments of the truss (yellow cylinders) by small segments (blue cylinders). Different colours are used to help with understanding how the segments carrying the fruits are specified in the code. Turtle commands RL, RH, and RU will be used to add some curvature to the segments of the truss and to place the fruits left and right of the main segments of the truss. (In the truss figure, the individual segments are slightly longer than specified in the code example, for illustration purpose.)

module Truss extends Organ 
{
    {length = TRUSS_LENGTH;}
}
==>
    RL(TRUSS_ANGLE) RH(90)
    for (int i:1:NB_FRUITS) (
        Cylinder(length/NB_FRUITS,0.001).(setShader(YELLOW))  
        [ 
            if(i%2==0) (
                RH(90)
            ) else (
                RH(-90)
            )
            RU(60) 
            Cylinder(0.02,0.001).(setShader(BLUE)) 
            // fruit
            Sphere(FRUIT_RADIUS).(setShader(RED)) 
        ]
        RU(-20)
    )
;

The initial condition

The initial structure, the axiom, is an apex.

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

Rewriting rules

Now we can specify rewriting rules for apex.

First, we specify rewriting rules for vegetative development of the primary shoot in tomato.

public void run()
[
    a:Apex ==> 
        Internode
        [Leaf]
        RH(PHYLLOTAXIS_ANGLE)
        a
    ;
]

The turtle command RH is used to create the phyllotactic pattern of leaves (resulting in their placement around the stem).

Now that we have specified the Axiom inside the init() method and a rewriting rule for the Apex inside the run() method, we can run the model by clicking on the run button in the RGG Toolbar, placed above the 3D View.

By applying the above rule to an apex 7 times, we create a small plant made of 7 internodes and 7 leaves.

In tomato, after a certain number of phytomers (internodes with leaves), specified by a parameter NB_VEG_PHYTOMERS, primary shoot turns into a sympodial shoot, that bears trusses with fruits. For this sympodial shoot, a repetitive pattern with 3 leaves and 1 truss with fruits is typical in many (high-wire) tomato cultivars.

We first specify a new parameter for the number of phytomers in primary shoot:

const int NB_VEG_PHYTOMERS = 8;

Afterwards, we can add a condition inside our rule, as a switch between primary shoot and sympodial shoot.

public void run()
[
    a:Apex ==> 
        Internode
        // in primary shoot, every internode has a leaf
        if (a[rank] <= NB_VEG_PHYTOMERS) (
            [Leaf]
        // in sympodial shoot, only the first in every four 
        // internodes has a truss, otherwise it has a leaf
        ) else (
            if ((a[rank] - NB_VEG_PHYTOMERS) % 4 == 1) (
                [Truss]
            ) else (
                [Leaf]
            )
        )
        RH(PHYLLOTAXIS_ANGLE)
        a
        {a[rank]++;}
    ;
]

Note, that in this simple model, we assumed that tomato plants don't form side shoots (in the greenhouse production of high-wire tomatoes, the side shoots are normally removed).

Improving leaf structure

Having captured the basic architecture of tomato plants, we can now improve the leaf structure. Tomato has a composed leaf, with a number of leaflet pairs and a terminal leaflet. We also add leaf curvature, by slightly bending down each segment of the leaf.

const int NB_LEAFLET_PAIRS = 3;

module Leaf extends Organ 
{
    ...
}
==> 
    RL(angle)
    // petiole
    Cylinder(petioleLength, petioleWidth/2).(setShader(GREEN))
 
    { double segmentLength = (length-petioleLength)/(NB_LEAFLET_PAIRS+1); }
 
    // leaflet pairs
    for (int i:1:NB_LEAFLET_PAIRS) (
        [RU(-80) RL(-30) Cylinder(0.04, 0.003) RL(40) Parallelogram(0.1, 0.05)]
        [RU( 80) RL(-30) Cylinder(0.04, 0.003) RL(40) Parallelogram(0.1, 0.05)]
        RL(20)
        Cylinder(segmentLength, 0.003)
    )
    // terminal leaflet
    Parallelogram(segmentLength, 0.05)
;

Improving leaflet shape

So far, we used a simple parallelogram to approximate the shape of a leaflet. We can further refine the leaflet shape, for example by using a MeshNode instead.

For an example on how to generate a leaflet mesh, see the tutorial Leaf triangulation .

We use the code from the leaf triangulation tutorial to create a leaflet mesh MeshNode.(setPolygons(polygonMesh)), where the polygons (polygonMesh) are created from a list of predefined vertices (vertexDataLeaflet).

To use the leaflet mesh in our tomato model, we create a new module for leaflet:

module Leaflet(double length)
==>
    RL(90) Scale(length, length, 0.01) MeshNode.(setPolygons(polygonMesh), setShader(GREEN))
;

Then we replace Parallelogram-s in the Leaf module by the newly defined Leaflet-s:

module Leaf extends Organ 
{
    ...
}
==> 
    ...
 
    // leaflet pairs
    for (int i:1:NB_LEAFLET_PAIRS) (
        [RU(-80) RL(-30) Cylinder(0.04, 0.003) RL(40) Leaflet(0.1)]
        [RU( 80) RL(-30) Cylinder(0.04, 0.003) RL(40) Leaflet(0.1)]
        RL(20)
        Cylinder(segmentLength, 0.003)
    )
    // terminal leaflet
    Leaflet(segmentLength)
;
tutorials/simple-tomato-model.txt · Last modified: 2024/12/07 16:02 by ksmolen