Wiki

Clone wiki

sig / Scene Graph

SIG implements a scene graph structure which allows to organize shapes and transformations in multiple ways.

Scene Nodes

All classes starting with Sn implement scene nodes. Class SnNode is the base class for all scene nodes. An instance of it cannot be directly created because it contains a pure virtual method for processing traversal actions. Every scene node class that can be inserted in a scene graph has to derive SnNode and implement that virtual method.

Class SnNode inherits GsShareable, therefore all nodes of a scene graph can be shared as smart pointers controlled by reference counting, a process controlled by methods ref() and unref(). When a node is added to the scene graph, it will later self-delete when no more references to it exist. If your program needs to keep it valid for example after the scene graph is destroyed, just call ref() for it and your program will keep a reference(). When it is no longer needed unref() has to be called.

There are 5 scene node classes directly deriving SnNode, each providing a specific way of interpreting node functionality:

  • SnShape is a base class for nodes that are responsible for drawing shapes. This class also has pure virtual methods to be customized by specific shapes, so it cannot be directly instanced and used. Instead, multiple classes deriving SnShape are available, each for representing specific types of shapes, for example: SnModel can load and represent generic triangle-based boundary representation objects, SnLines can represent line segments or polygonal lines, SnLines2 is a specialized version for 2D lines, etc.

  • SnGroup is a class that holds several scene nodes as children nodes. When a scene traversal is called for a SnGroup, the traversal will proceed to each children node, from the child index 0 onwards. Class SnGroup is the class that allows creating a hierarchy of scene nodes. A group can contain any other scene node, including additional groups, therefore allowing the creation of tree and graph hierarchies. Note that cycles in the hierarchy must not be created and there is no mechanism to prevent their creation. If cycles are created scene traversals will not terminate. A group also has an important role in organizing transformations. It can be set to isolate transformations applied to its children from the rest of the scene graph by calling method separator(true), making the group behave as a separator, as further explained below.

  • SnTransform is a node that stores a 4x4 3D homogeneous transformation matrix in line-major format. Every time a scene traversal is executed an identity matrix is initialized as the current transformation to be applied to all shapes encountered during the traversal. If a SnTransform is encountered during the traversal, its transformation is multiplied to the current one and will therefore affect all the following shapes encountered. However, transformations can affect members of a group differently: if a group is set to behave as a separator, then after traversing all nodes in the group, the current transformation is restored to the transformation that existed before starting to traverse the group members.

  • SnEditor is a special type of base class for nodes which need to react to user events such as key presses and mouse movements. It has specific virtual methods to handle such events in order to allow interactivity. It serves as the base class for SnPolyEditor which is a class for editing 2D polygons, and SnManipulator which is a class that displays a bounding box around any object inserted in it with method child(), and by clicking on the sides of the box the user can manipulate the position of the object.

  • SnMaterial has the special behavior of overwriting the material in the next shapes encountered during a scene traversal. This allows to achieve a same SnShape object inserted multiple times in the scene graph and have its color altered without changing the shape object.

Scene Actions

Classes starting with letters Sa are scene actions, which are classes performing a scene traversal in order to achieve some computation on the scene graph. For example, SaBBox computes the bounding box of the scene under a given scene node, SaRenderMode changes the rendering mode flag of all shapes under a given node of the hierarchy, and GlRenderer renders the scene from the given scene root node. Note that GlRenderer does not start with Sa because it makes calls to OpenGL and thus it is implemented in the sigogl library. The OpenGL renderer is therefore independent of the scene description, as it should be.

Groups and Transformations

When a rendering traversal starts a stack of transformations is initialized with the identity transformation on the top. The top transformation affects every shape node encountered during the traversal. Every time a SnTransform node is encountered, its transformation matrix is multiplied to the one on the top of the stack, therefore accumulating the current transformation. Every time a group node acting as a separator is encountered, three operations are performed:

  • first, a copy of the current transformation is pushed to the top of the transformation stack,
  • then, all the children nodes are traversed, and
  • finally, the transformation from the top of the stack is popped before leaving the group.

Therefore groups are used to control how transformations are accumulated during a scene traversal. This same control is used for representing from complex kinematic chains to simply position objects in a scene.

Examples

The example below builds a scene graph where two primitive objects might move during the application and therefore a transformation is placed before each primitive, with both nodes under a separator group. This allows each transformations to affect only the object placed in the same group as the transformation. In the viewer class, member variables providing direct access to the transformation nodes are saved so that there is fast direct access to update the transformations as needed.

#!c++
class MyViewer : public WsViewer
{  private :
    SnTransform* _t1;
    SnTransform* _t2;
    ...
   public :
    ...
    void build_scene ();
    ...
};

void MyViewer::build_scene ()
{   
    // Create object 1:
    GsModel* obj1 = new GsModel; // Object 1 (this could be your own node)
    obj1->make_cylinder ( GsPnt(0,0,0), GsPnt(0,1,0), 0.2f, 0.2f, 25, true );

    // Put object 1 in scene graph:
    SnGroup *g1 = new SnGroup;
    g1->separator ( true );
    g1->add ( _t1=new SnTransform ); // _t1 is our class member variable
    g1->add ( new SnModel(obj1) );
    g1->top<SnModel>()->color ( GsColor::red );
    _t1->get().translation ( 0.5f, 0, 0 ); // set initial translation

    // Create object 2:
    GsModel* obj2 = new GsModel;
    obj2->make_capsule ( GsPnt(0,0,0), GsPnt(0,1,0), 0.2f, 0.2f, 25, true );

    // Put object 2 in scene graph:
    SnGroup *g2 = new SnGroup;
    g2->separator ( true );
    g2->add ( _t2=new SnTransform ); // _t2 is our other class member variable
    g2->add ( new SnModel(obj2) );
    g2->top<SnModel>()->color ( GsColor::blue );
    _t2->get().translation ( -0.5f, 0, 0 ); // set initial translation

    // Finally add nodes to the scene root:
    rootg()->add(g1);
    rootg()->add(g2);
}

The next example builds a scene graph for a particle system displaying many spheres and in this case it makes sense to share the shape node describing the sphere. However we need to define one specific transformation and color per sphere. As before we can use transformations under separator groups, but in addition we will use node SnMaterial to assign a different color to the same shared sphere.

#!c++
void MyViewer::build_scene ()
{
    // Create the sphere to be shared:
    SnModel* sphere = new SnModel;
    sphere->model()->make_sphere ( GsPnt::null, _sphereradius, nfaces, true );

    // Nodes to be used:
    SnGroup* g;     // one group per particle will be created
    SnTransform* t; // first group node will be a translation 
    SnMaterial* m;  // then we set the desired color (the shared sphere will come next)

    // Build scene graph:
    // (assumes positions stored in position_array of size psize)
    for ( int i=0; i<psize; i++ )
    {   rootg()->add ( g=new SnGroup );
        g->separator(true);           // set transformation to only affect nodes in group g
        g->add ( t=new SnTransform ); // sphere position will be set here
        g->add ( m=new SnMaterial );  // color will be set here
        g->add ( sphere );            // now add shared sphere geometry
        t->get().translation ( position_array[i] ); // set position
        m->material().diffuse = GsColor::random();  // set color
    }
}

The difference between placing new shape instances in each group or sharing a single instance is illustrated in the diagram below:

nodesharing.png

(T=Transformation node, M=Model node, Mtl=Material node)

Updated