Sunday 12 December 2010

L-System - Render Method

Render Method

One of the critical algorithms to consider is the rendering function of the RuleManager class. Below is a proposed solution and justification for the method. The method will be recursive. Although this can often be more intensive on the CPU, it is important that every time we process a rule, we want to perform the same steps. We need to delve into the rules definition and perform the same process for every character. Given that the complexity of the solution depends on the users ruleset and the levels of branching they require, recursion is, I think, the most elegant and flexible solution.

Here is the function signature:
void render( const string & stringToProcess );

psuedoCode:

foreach char in stringToProcess

  if char is in CoreRules
         tell engine to draw / roll etc.
  elseif char is in UserRules AND currentRecursionLevel < branches
    ++recursionLevel
    render( UserRules[char] ) // recursive call
    --recursionLevel
  else
    cout << "Cannot find rule: " + char

L-System Plan

Below is an initial plan for a command-line L-Sytem modelling tool for my 2010-2011 Animation Software Development project. This is simply the thoughts I have had so far on the system, not a complete and perfect plan for the best plant modelling tool ever seen!

Here's the brief


Develop an interface that allows L-systems based modelling or plant like structures using OpenGL. The L-system grammar should be specified from a text file.


Requirements

The first iteration of the system will be a command line program but will be designed with the flexibility and scope for future developments as a GUI app.

The user should be able to run the program in the command line, passing in arguments, including a file path referencing the location of a text file which will define their custom L-System grammar. This text format will be called a L-System Rule Base file, or .lrb

Here is a rough idea of the file format that the system should be able to interpret



The top 4 variables will be used to set up the state of the drawing engine. The fourth value 'cylinderwidth' will only be expected when the option -c is passed into the program. If -c is passed and this field is missing, it will default to 1. Each value will have static default values. This will ensure the program will run regardless. I will however, issue some fairly strong warnings to the console if the variables aren't present.

Branches is the levels of recursion we want to be limited to (we don't want infinite madness)
Stepsize is the unit size of each branch.
Angle is the size, in degrees, of the angle upon each turn.

The only other necessary field is the 'axiom' field. This will specify the starting point of the rendering.

With branches set to 2, the above LRB file would be re-written to:


AB
FF[+FA]F//FF
FF[+FFF[+FA]]F//FF

In summary, the requirements of the system are that the user should be able to define simple rules in a .lrb file that will be passed to the program. These rules should set up the system variables as the user desires as well as outline the conventional grammar of their L-System.

Implementation

Although initially the app will not be a GUI app, it will be built in the Model-View-Controller design pattern. A DrawEngine will be the Model, handling all of the OpenGL heavy lifting. A RuleManager will handle the string passing and user rules. In this instance the view will simply be an OpenGL context held in a QT Main Window.

The user will communicate (through their .lrb) to the RuleManager, which will pass their rules into internal dynamic data structures for rendering. The RuleManager will then communicate with the DrawEngine telling it what to draw.

The DrawEngine

The focus of this class is to setup the shaders and vertex-buffer objects ready to be drawn. In addition to this it will hold the TransformationStack and the core functions of the L-System.

Firstly, as my task is only to allow modelling of plant-like structures. I require only basic shaders, the most important feature of which is moving vertex position. Secondly, the drawing engine will create and draw lines by default, as the user is able to choose cylinder drawing mode, it will also create a ngl::VBOPrimitive Cylinder.

In rendering, the engine will keep track of the current transformation matrix on a stack. (starting at identity) The other methods in the class are the core L-System utility functions.

How is it actually drawn?!


Here is the key to how the DrawEngine will work to give the user the results they want.

If a user uses an "F" in their ruleset, it will draw a line/cylinder from the current position along the current heading (held in the transform stack). The current position in the matrix will then be changed to reflect this 'forward' move.

If a user uses a ' / ' character in their ruleset the system will alter the current matrix to rotate around the positive Z axis, by the amount of the angle variable (defined in .lrb). Here is a simple example of this in action:


This is obviously extremely simple, but is a good example of the kind of operations the engine will handle. The user can only manipulate the engines core functionality.

The RuleManager

One of the key decisions with this class is how we store the string data needed for the system. My first decision in this respect was how the core engine rules (F f / \ + etc.) should be stored. I opted for a std::map. The rationale for this is so that I can store a character (rule) and bind it to a corresponding function pointer of the DrawEngine. For example, F will be bound to the function forwardDraw() and the ' / ' character to the roll() function.

For the user defined rules I will have a std::map of type <string, string>. The rule name will be the key and the rule definition will be the value.

The two main purposes of this class are to read the users .lrb into internal data structures and system variables and also to tell the DrawEngine to execute the commands held in the users L-System grammar.

The first task will involve using regular expressions and matching strings. This suggests that using a flexible library like boost for working with strings would be a good idea.

The second task will be handled by a method called render(). This method will be the focus of the next section on algorithmic considerations as it arguably the most critical and potentially intensive parts of the system. It is responsible for reading the axiom and from that, instructing the DrawEngine what to draw.
     
Algorithms and Class Diagrams to follow