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 GroIMP example explorer (File/Example projects) under Point cloud/branch.
In order to proper ship this project, the used point cloud is added as a file to the GroIMP file explorer.
Therefore we first need to add the ply file to the file explorer. With clicking on Objects/new/Add file in this explorer, a file dialog is opened. In this dialog
select “All files” as type, to show files that can be edited in the text editor(see image).
After load the ply file, it can now be loaded in RGG as an input Stream with the function getInputStreamFromProject(“leaf.ply”)
and this stream can be used in the loadNodeFromStream
function to interpret the file according to the given mime type (explained in I/O Point Cloud). This interpretation will then provide you the root of the created graph structure as a node. The so created node can be used like all other nodes in GroIMP, for instance in a rewriting query as shown below:
Node x =Utils.loadNodeFromStream(getInputStreamFromProject("leaf.ply"),"model/x-grogra-pointcloud-graph+ply"); [ A ==> x; ]
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. In order to minimize computational power, a two step process is used, first the point cloud is loaded once and moved to 0,0,0 and than this moved cloud is cloned to replace the other “placeholder-leaves”.
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() { // load ply graph from Node x =Utils.loadNodeFromStream(getInputStreamFromProject("leaf.ply"),"model/x-grogra-pointcloud-graph+ply"); [ r:RL(80) m:M s:Scale(10) Rotate Parallelogram ==> r m s Rotate(-120,-50,-40) x; ] derive(); // apply the changes to the graph move_toZero(); [ r:RL(80) m:M s:Scale Rotate Parallelogram ==> r m s Rotate(-120,-50,-40) cloneSubgraph(x); r:RL(-80) m:M s:Scale Rotate Parallelogram==> r m s Rotate(270,-100,-50) cloneSubgraph(x); ] }
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(); (*Imp3dCloud*).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.