This tutorial uses a ply file of a leaf with points and meshes to simulate a small branch. The leaf is transformed and added several times to a branch model, to simulate leaf growth and light absorption.
The example model can be found in the Gallery
Since the point cloud is used as an changeable asset in this model, we will use the GraphExplorer plugin to import it as a GraphObject.
To do so first open the Graph Objects explorer /Panels/Explorers/Graph Objects
.
In the newly opened panel an external file can be added through Objects/new/add
, the dialog that follows works similar to the one described here. Make sure to import the point cloud as a graph.
Afterwards the graph object can be referenced using the GraphObjectRef class and a clone can be added several times using XL rewriting rules.
GraphObjectRef x = new GraphObjectRef("random_leaf3_mesh.ply"); [ A ==> x.cloneGraph(); ]
In order to use the imported cloud as an organ it might be necessary to fit it in the right spot and size.
The first step is to make sure that the point that describes the “root” of the leaf, is at the local position of (0,0,0), meaning the leaf starts where the point cloud object was added. If you are lucky this was done on a previous step, if not you have to guess the root point. Guessing this point can be done the easiest by loading the point cloud, zooming in and selecting the point closest to the root. The translation of this point (highlighted in the image below), describes the current offset of the point cloud.
In order to cancel out this offset, we have to move all points to the opposite direction:
void move_toZero()[ p:Point::>{ p[x]-=-1.328728; p[y]-=19.368984; p[z]-=-4.860407; }
Even so this code moves the points to the right position, this is not enough for this tutorial. We need to move the connected meshes as well. To do so we use the structure of the imported CollectionCloud (as described here). Following this structure we know that each mesh is connected to three points by decomposition edges and since we already moved the points we can use there information to create a new mesh.
void redraw_Mesh()[ m:MeshNode ==> { float[] fl =new float[9]; int i=0; [ m /> p:Point::>{ fl[i]=p[x]; i++; fl[i]=p[y]; i++; fl[i]=p[z]; i++; } ] } getMesh(fl); ]
After these steps the point and mesh cloud should start at the location of the root node and can be easily transformed by turtle geometry.
The imported cloud will behave similar to other subset of the graph in GroIMP. Meaning if the point cloud is added after a transformation node or an object with an axis it will be moved, rotated or scaled regarding the previous nodes. (This also works with colors and other turtle attributes.)
The Rotation of a point cloud can be quite tricky depending on the the rotations in the measurement. A simple trick for the modelling is to add a Rotation
Node above the cloud and select this node. Then it is possible to change the angels in the properties editor and afterwards transfer the values in to the RGG code.
After the right rotation and scale is estimated, these values can be used on top of any turtle state. Therefore a leaf can be added in different angles using the same rotation. In the given example the model starts with simple “placeholder-leaves” that are already scaled and rotated to the wished location. Then with the loading of the point cloud these placeholders are replaced by not only the leaf but also the needed rotation estimated for the leaf.
protected void init () [ Axiom ==> P(4)RU(70)F(2) //creating a brown branch rotate by 70 [ P(2) M(-1.3)RL(80) M(0.05)Scale(10)Rotate(0,30,90)Parallelogram(0.16,0.07)] [ P(2) M(-.6)RL(-80) M(0.05)Scale(10)Rotate(0,30,90)Parallelogram(0.16,0.07)] ; ] public void loadPC() { GraphObjectRef x = new GraphObjectRef("random_leaf3_mesh.ply"); [ r:RL(80) m:M s:Scale Rotate Parallelogram==> r m s Rotate(-120,-50,-40) x.cloneGraph(); r:RL(-80) m:M s:Scale Rotate Parallelogram==> r m s Rotate(270,-100,-50) x.cloneGraph(); ] derive(); move_toZero(); }
Even so the growth described here is only increasing the width and height of the leaf by 10% and the length by 20%, it highlights the ability to transform the organ on a high resolution.
To do this change, each point is moved similar to the translation to 0,0,0 and then the meshes are redrawn again. Afterwards it is necessary to signal the cloud to update the mesh collector, otherwise the changes will only appear in higher resolution or rendering.
public void grow(){ [ p:Point::>{ p[x]*=1.1; p[y]*=1.2; p[z]*=1.1; } ] redraw_Mesh(); (*Cloud*).setUpdate(true); }
Using any of the GroIMP raytracer it is possible to receive the amount of intercepted energy for each mesh. This information can be used for detailed analysis or specific growth behavior. In this example the absorbed power per area is used to set the RGBAShader of each mesh in order to create a heat map like representation.
public void light(){ flm.compute(); float max = max(flm.getAbsorbedPower3d((*mn:MeshNode*)).getMax()/mn.getSurfaceArea()); //get the total maximum max-=500; // eliminate extream values [ mn:MeshNode::>{ // for each mesh float inp = flm.getAbsorbedPower3d(mn).getMax(); // get power inp= inp/mn.getSurfaceArea(); // per area // estimate RGB values inp=2*inp/max; int r = (int)(255*(inp-1)); r = (r>0)?r:0; int b = (int)(255*(1-inp)); b = (b>0)?b:0; int g = 255-r-b; // change shaders mn.setShader(new RGBAShader(r,g,b)); } ] }
In order to see the result of the scene needs to be rendered, since the 3d view is otherwise using the mesh collector.