Industrial Data Science
in C# and .NET:
Simple. Fast. Reliable.

ILNumerics - Technical Computing

Modern High Performance Tools for Technical

Computing and Visualization in Industry and Science


Custom Scene Graph Objects

For many situations the existing collection of available scene graph objects in ILNumerics Visualization Engine is sufficient for creating interactive visualizations of arbitrary data and models. There are cases, however, which require the user to create more complicated user interfaces, highly interactive models, new plotting types or to simply wrap individual configurations of custom plots into a new class for easier reusage.

ILNumerics Visualization Engine gives full freedom for the creation of custom objects. It allows a user to wrap rather complicated objects into an individual class for easier reusage. Those classes usually derive from Group and are the root of a subtree with arbitrary scene graph objects and individual configurations.

The following options exist for creating custom scene graph objects. The most simple option does not involve any overhead next to the actual setup of predefined objects. Depending on the requirements of the resulting custom object the user can choose to implement more program logic in order to enable more features at runtime.

  1. Simple custom objects derive from Group and add predefined scene graph objects to its children collection. The children are individually configured. The configuration is reused for every instance of the custom object.
  2. The simple custom object from (1.) is extended to support interactive scenarios. This will enable to object to fit into the model of synchronized scenes. The user can implement program logic as class member which is called and executed at runtime. A typical example is the implementation of custom mouse or keyboard interaction functionality.
  3. The simple custom object from (1.) is extended to enable the object to participate on the efficient copying scheme implemented by ILNumerics Visualization Engine. One instance of the object can be added to a scene multiple times, copying only 'cheap' scalar properties and automatically reusing larger vertex buffer data.

Simple Custom Objects

The most simple approach to creating a custom object is described in this section. We start by showing the creation of a simple compound object without using a dedicated custom object first and do the transition to a real custom scene graph object than.

Let's say we want to create a brick. A brick basically corresponds to a cube with typically 6 faces. In this example we will give all the faces the same color. in addition to that, the edges are emphasized by individual line segments.

One could create such an object with the following straight forward setup:

Often one needs to arrange a large number of such 'bricks' in order to visualize volume data of complex models. One example application is the visualization of FEM simulation results. In the naive attempt shown above, we would simply take the group object with all children and add it multiple times to the scene. Each time we add our small subtree a shallow copy is created and returned which allows us to alter certain configuration values as color, transformation matrices a.s.o. Here, we give each brick an individual transform in order to arrange the bricks in rows and columns:

Remember that in the scene by now there are only default scene graph objects which are provided by ILNumerics Visualization Engine out of the box:

Bricks ILNumerics

Now, consider our brick would be more 'intelligent'. We could implement methods to alter the color of the faces interactively, to count only one example. We could achieve this quite comfortably even in this simple example by utilizing anonymous methods as event handlers on the Triangles shape:

Until now we have only utilized properties and events of the existing base classes, resp. existing shape objects (MouseClick). Even if we have accessed the MouseClick event via a specialized class, copies of the triangles shape will also copy the attached event handler. As the result, one can click on individual shapes in the matrix of the bricks and each clicked brick will toogle its color individually:

Synchronized Custom Scene Graph Objects ILNumerics

All this configuration has been done "from the outside" of the scene objects. However, according to OO design principles the right place to put such code would be a 'brick' class. Using a dedicated class for your own custom objects brings a number of advantages:

  • The code belonging to a custom object will be tagged to the object.
  • The class can maintain additional attributes used to configure the object.
  • The class can expose a convenient API, abstracting away the implementation details.
  • Even more complicated objects can be composed out of simple default scene graph objects and custom objects, therefore helping to reduce complexity with a modularized design.

Simple Custom Scene Graph Object Example

The only drawback of having dedicated classes for scene graph objects is a potentially higher implementation complexity. However, this is not true for simple cases like our bricks example above. The implementation is pretty straightforward. Starting with a nearly naive approach:

This class basically consists of a single constructor. The constructor adds and configures the children of the brick. We must not forget to call the base class here and give it a speaking tag for the new Brick instance. This will make the brick instance easily identifyable in the scene graph later. The same is true for tags of the child objects. It is always a good advise to store the tags as private fields within the class in order to ease the bookkeeping of the tags.

Since we have a class for the brick, we can implement a more convenient API: Fill

 and Border expose the child shapes as individual properties of Brick which makes it much easier to configure the brick.

The new Brick class is used in a very similar way as before. However, this time we can utilize the more convenient API on the class:

Again, by adding the same instance of Brick to the existing scene shallow copies of the brick are made. Note, how we cast the brick class instance down to its base class Group in line 8 prior to adding it to the scene. This is necessary, since the base class does not know about Brick (yet). From the Group point of view our new class is nothing else than an Group instance with some children. This is what the base class will create once a copy of the class is requested.

Right now we have a very simple wrapper class which collects all code necessary to create a brick. It allows us to collect and reuse that code and to add some more convenient API to the class. However, instances of the class still lack some capabilities when it comes to copying and interaction at runtime. For example, iterating over the bricks in the scene will not give the expected result: 

We will improve this in the next section...