Demo 3 - LSystem Tree

This is an example of generating trees based on an L-system grammar (called the Tree example). The demo shows several advanced features of GIGL that are used to control the stochastic rule expansions, which are beyond the L-system grammar itself.

n = 1 (8x)

n = 2 (4x)

n = 5

The results of the Tree example when applying the L-system rule deterministically up to some depth n, shown with both plain rendering and rich rendering.

L-System tree generation with a less constricted randomization

L-System tree generation with a better tuned randomization

The Tree Example. Other C++ source files omitted.

View the GIGL source file in the Git repo

Instructions:

    • As a demo, the first step is to try compiling and running the given project. For instructions and tips about demos please refer to [Here]. This demo needs OpenGL and SDL.

    • The C++ code are hard coded with three versions of tree generation and two rendering styles, which need commenting/un-commenting code in source files.

    • To switch between the three versions of tree generation, there are two group of code in "main.cpp" that are of concern, Line 137 - Line 139 and Line 172 - 174. Only one from each of the group of the three lines should be activated at any instant.

      • For generating deterministically up to a depth, only activate Line 137 and 172.

      • For generating with a less constricted randomization, only activate Line 138 and 173.

      • For generating with a better tuned randomization, only activate Line 139 and 174.

    • To switch between the two version of rendering, please visit Line 10 of "TreeSegment.cpp".

      • For plain rendering, activate Line 10.

      • For rich rendering, deactivate Line 10.

    • When running the demo, it will first ask for an integer as a random seed, then ask for an integer the depth limit for the recursive expansion, and finally a number as the size (length) of each unit segment of trees. It then will open a window for rendering.

    • The operations in the new rendering window (please click on that window to focus) include:

      • Pressing enter, which re-generates a new tree with the same setup, and the seed for the new one will be in the original command line window.

      • Moving the camera with key J (left), L (right), K (down), I (up), which is only effective when the generated tree is larger than the window.

    • The same random seed might not give the exactly same result across different environments (difference in C++ library for RNG). The results shown in the figure is done on a Windows machine with Visual Studio 2015, with the trees in the less constricted figure be with random seeds 2244, 5216, 55108 respectively, and the trees in the better tuned figure be with random seeds 55455, 89828, 17907 respectively, all with 5 as the depth limit and 15 as the unit size (random seeds do not matter for the deterministic setup). In addition, the development of GIGL might also change some of the ordering of getting random numbers which might also affect the concrete results. Nevertheless, statistically, the results should be of the same distribution, as the semantics of the GIGL code does not change.

    • After the demo is working, you may want to try further reading the GIGL code and/or the notes below to get a better understanding of GIGL's syntax and semantics.

Comments/Notes:

    • Here the (parametrizable) item grammar encoded is

    • TreePart := ntTree(TreePart, TreePart, TreePart, TreePart)

    • | termTree(TreeSegment),

    • where "TreeSegment" is a terminal type (albeit a pointer) defined in some other (C++) code. This corresponds to the LSystem grammar F := F[-F][+F]F.

    • There are two variable attributes "start_pos" and "end_pos" (Line 14) for storing the starting point and ending point of current segment, and a functional attribute "Draw" (Line 15) for rendering.

    • There are many usage of the lambda configuration feature here. Besides the usage of limiting depth (similar to the previous Quiz example), another interesting example is the GetRandFloat(30.0, 60.0) on Line 51 and Line 60, which is for setting the branching angle ("branch_deg" declared on Line 22). Because those expressions are evaluated when generating each node in the item tree, each instance of rule expansion will likely use a different branching angle between 30 degrees to 60 degrees, generated by the RNG.

    • Normally, configure parameters like "branch_deg" are only evaluated once upon entering the generator of each node. However, here as it is declared with the dynamic specifier (Line 22), which push the feature of lambda expression even further, i.e. it gets evaluated on each instance it appears in the generator. This is useful here because having a different branching angle for left and right branch (Line 26 and Line 27) from the same instance of rule expansion can facilitate breaking the symmetry.

    • This example contains another feature called maybe-semantics. In fact, any child of a rule that is of nonterminal (nonterminal pointer) type represent maybe it is a nonterminal of that type or maybe it is nothing. This correspond very well to pointers in C++. When a pointer is a null pointer it represents nothing, when it points to some allocated memory corresponding to the correct type of variable it represents some actual content. This is how maybe semantics is implemented in GIGL. It is used in this example on Line 26 and Line 27, where the left and right branch has "branch_prob" probability to expand, with the rest probability be assigned to null pointers.