====== Leaf triangulation ======
Triangulation is the process where a (ordered) set of triangles is generated out of a (unordered) set of points. The result is a set of triangles, now called faces, that can be directly drawn.
Let's see the following set of 2D point:
s={(0, 0), (-2.85, 4), (0, 10), (2.85, 3.1)}
{{ :tutorials:leaflet1-01.png?direct&400 |}}
When connecting the points (P_0, P_1, P_2) and (P_1, P_3, P_2) we obtain two triangles. And by doing so, we already did our first (2D) triangulation. In computer graphics such a set of triangles - from now on called faces - is called mesh.
Note: Triangulation is not unique. Meaning there can be different triangulations for the same set of points.
{{ :tutorials:leaflet1-02.png?direct&400 |}}
To draw a mesh in GroIMP, all we need to do is to define the point and the faces. The used geometrical object we need to visualize the so-called //MeshNode//.
The code to generate a simple two faces mesh could look like this:
import de.grogra.xl.util.IntList;
import de.grogra.xl.util.FloatList;
const float[] p0 = {0,0,0};
const float[] p1 = {-2.85,4,0};
const float[] p2 = {0,10,0};
const float[] p3 = {2.85,3.1,0};
const FloatList vertexDataLeaflet = new FloatList(new float[] {
//left
p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2],//T1
//right
p0[0], p0[1], p0[2], p2[0], p2[1], p2[2], p3[0], p3[1], p3[2]//T2
});
const PolygonMesh polygonMesh = new PolygonMesh();
static {
int[] tmp = new int[vertexDataLeaflet.size()/3];
for(int i = 0; i Scale(0.007) MeshNode.(setPolygons(polygonMesh), setShader(GREEN));
]
The output of the above code could then look like this. Note: The blue lines to highlight the edges are just added to increase understanding - they will be not shown when running the above code.:
{{ :tutorials:leaf_s.png?direct&400 |}}
For convex shapes, the //Library// function //leaf3d// can be used to generate a triangulation like the above one. It assumes that the first point is the origin of all faces and builds a series of triangles from this point on. In XL this could look like this:
const float[] pointlist = new float[] {0,0,0,-2.85,4,0,0,10,0,2.85,3.1,0};
protected void init() [
Axiom ==> leaf3d(pointlist);
]
Coming back to the //PolygonMesh// version, the //Library// function //getMesh// takes a //FloatList// of triangulated points and does the whole conversion to //PolygonMesh// and //MeshNode// for us.
static MeshNode LeafletMesh;
static {
LeafletMesh = getMesh(vertexDataLeaflet);
}
protected void init() [
Axiom ==> Scale(0.007) LeafletMesh.(setShader(GREEN));
]
Now, let's take a tomato leaflet for example,
{{ :tutorials:leaflet.png?direct&200 |}}
and when placed behind our points, the above triangulation could be already a very rough simplification of this particular leaflet.
{{ :tutorials:leaflet1-03.png?direct&400 |}}
Well, a very rough indeed. So, let's add some more points and also add the the third dimension to give the leaflet some 3D shape.
{{ :tutorials:leaflet1-04.png?direct&400 |}}
For the seek of laziness/simplification, the right side of triangles is assumed to be mirror symmetric to the left hand side.
To do so within GroIMP, we just need to add the new points and apply some values for the Z-dimension. A simple curve like this may do:
{{ :tutorials:leaflet1-06.png?direct&400 |}}
The complete code to generate the leaflet mesh looks like this:
import de.grogra.xl.util.IntList;
import de.grogra.xl.util.FloatList;
const float[] p0 = {0,0,0};
const float[] p1 = {-0.1,-0.066,0.3};
const float[] p2 = {-0.2,0.06,0};
const float[] p3 = {-0.25,0.2,0};
const float[] p4 = {0,0.2,-0.4};
const float[] p5 = {-0.285,0.4,0};
const float[] p6 = {0,0.4,-0.5};
const float[] p7 = {-0.22,0.53,0};
const float[] p8 = {0,0.53,-0.3};
const float[] p9 = {0,1,1.0};
const FloatList vertexDataLeaflet = new FloatList(new float[] {
//left
p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2],//T1
p0[0], p0[1], p0[2], p2[0], p2[1], p2[2], p3[0], p3[1], p3[2],//T2
p0[0], p0[1], p0[2], p3[0], p3[1], p3[2], p4[0], p4[1], p4[2],//T3
p3[0], p3[1], p3[2], p4[0], p4[1], p4[2], p5[0], p5[1], p5[2],//T4
p4[0], p4[1], p4[2], p5[0], p5[1], p5[2], p6[0], p6[1], p6[2],//T5
p5[0], p5[1], p5[2], p6[0], p6[1], p6[2], p7[0], p7[1], p7[2],//T6
p6[0], p6[1], p6[2], p7[0], p7[1], p7[2], p8[0], p8[1], p8[2],//T7
p7[0], p7[1], p7[2], p8[0], p8[1], p8[2], p9[0], p9[1], p9[2],//T8
//right
-p0[0], p0[1], p0[2], -p1[0], p1[1], p1[2], -p2[0], p2[1], p2[2],//T9
-p0[0], p0[1], p0[2], -p2[0], p2[1], p2[2], -p3[0], p3[1], p3[2],//T10
-p0[0], p0[1], p0[2], -p3[0], p3[1], p3[2], -p4[0], p4[1], p4[2],//T11
-p3[0], p3[1], p3[2], -p4[0], p4[1], p4[2], -p5[0], p5[1], p5[2],//T12
-p4[0], p4[1], p4[2], -p5[0], p5[1], p5[2], -p6[0], p6[1], p6[2],//T13
-p5[0], p5[1], p5[2], -p6[0], p6[1], p6[2], -p7[0], p7[1], p7[2],//T14
-p6[0], p6[1], p6[2], -p7[0], p7[1], p7[2], -p8[0], p8[1], p8[2],//T15
-p7[0], p7[1], p7[2], -p8[0], p8[1], p8[2], -p9[0], p9[1], p9[2]//T16
});
static MeshNode LeafletMesh;
static {
LeafletMesh = getMesh(vertexDataLeaflet);
}
protected void init() [
Axiom ==> Scale(0.07, 0.07, 0.01) LeafletMesh.(setShader(GREEN));
]
The output of the above code could then look like this:
{{ :tutorials:leaflet_model.png?direct&400 |}}
The last step would now be to pack the leaflet mesh into a module and integrate it further into a leaf module (not shown here). For the first aspect, we can define a module like this:
module Leaflet(float length) ==>
Scale(length, length, 0.01) MeshNode.(setPolygons(polygonMesh), setShader(GREEN));
After integration in to a leaf module the final 3D result could look like this
{{ :tutorials:fig06a4.png?direct&400 |}}
as it was used within Zhang //et al.// 2021 (High resolution 3D simulation of light climate and thermal performance of a solar greenhouse model under tomato canopy structure; //Renewable Energy//, 160, 730-745, doi: [[https://doi.org/10.1016/j.renene.2020.06.144]]).
GroIMP provides a few predefined 3D leaves that can be accessed using the Library function leaf3d(id). There are shapes for: "artificial", "maple", "undefined", "poplar", "cotton", "birch", "undefined", "oak". The identifier starts from zero for the first "artificial" shape and goes up to seven for the oak leaf.
The code below will generate them one by one next to each other.
protected void init() [
Axiom ==>
for(int i=0; i
The predefined 3D leaf shapes will look as shown below:
{{ :tutorials:leaf3d.png?direct&625 |}}