XdkWrl: Traversing a VRML2 tree

Home | Hierarchy | Members | Alphabetical | Related Pages

# Traversing a VRML2 tree

As said in the introduction, the XdkWRL library just loads the VRML file into a C++ hierachical structure. Then is is your task to parse this structure and retain whatever you want in it. Usually, you will build another hierarchical structure, for example one in which the root note as a `draw()` function so you can display it.

Let me insist on this again: when loading a VRML file you will just be given the content of the file in a C++ structure. No other computations are done. For example, the wrl::Group standard node has field wrl::Group::bboxCenter and wrl::Group::bboxSize. A VRML file might specify an bbox for a given group. But if is not the case, this field will be set to the default value specified y the standard and will not be computed by the loader. So you cannot rely on its value.

## Using a traverser class

So how do you traverse a VRML tree? Here is a possible design that I found useful. You define a Traverser class that knows how to traverse each node and you "apply" it to the wrl::Scene that you loaded. Here is an example of such a traverser for which we define behaviour for only a subset of nodes

    #ifndef TRAVERSER_H
    #define TRAVERSER_H

    class Traverser
    {
    public:
      void operator()(wrl::Node* node);
    protected:
      void treat(wrl::Group*);
      void treat(wrl::Shape*);
      void treat(wrl::Sphere*);
      void treat(wrl::Switch*);
      void treat(wrl::Transform*);
    };

    #endif // TRAVERSER_H

The fundamental method in this class is the `void operator()(wrl::Node* node);` one. Here is what its implementation looks like:

This line is to open the wrl namespace so you can write `Node` as a "shortcut" for the true (completely qualified) name `wrl::Node`.

    void Traverser::operator()(Node* node)
    {
      if (node == NULL) return;
      {
        Group* n = dynamic_cast<Group*>(node);
        if (n!=NULL) { treat(n); return; }
      }
      {
        Shape* n = dynamic_cast<Shape*>(node);
        if (n!=NULL) { treat(n); return; }
      }
      {
        Sphere* n = dynamic_cast<Sphere*>(node);
        if (n!=NULL) { treat(n); return; }
      }
      {
        Switch* n = dynamic_cast<Switch*>(node);
        if (n!=NULL) { treat(n); return; }
      }
      {
        Transform* n = dynamic_cast<Transform*>(node);
        if (n!=NULL) { treat(n); return; }
      }
    }
    // end of void Traverser::operator()(Node* node)

This function does the dispatching of treatment functions in the hierarchy. Indeed, VRML nodes refers to their children through wrl::SFNode pointers and therefore do not know their types. Hence the need for a `dynamic_cast`.

After you have written this dispatching function, you just need to write the appropriate code in the relevant `treat` method. For example, here is what the usual treatment is for a wrl::Group node:

    void Traverser::treat(Group* g)
    {
      for (MFNode::const_iterator iter = g->children.begin();
           iter != g->children.end();++iter)
      {
        (*this)(*iter);
      }
    }
    // end of void Traverser::treat(Group* g)

Another type of node that has a pretty standard way of being traversed is the wrl:Switch node:

For any other type of standard node, you will have to define the appropriate `treat()` method in you traverser class. Don't forget to also add the corresponding lines in the dispatching function.

## Using the default wrl::Traverser

Since it is errorprone to redefine you Traverser class for every kind of traversal you might want to write, the XdkWrl library provides you with a base wrl::Traverser class that has a default `treat()` method for all standard node, and a complete dispatcher. The default method does nothing except for the grouping nodes listed below:

- wrl::Group
- wrl::Switch
- wrl::Transform

for which children are recursively traversed (actually it does a little bit more, as you will see in page Using the Transformator class, but for the moment it is not important).

Remarks:
  There are other grouping nodes (nodes with children) in the VRML language, as listed below, but their semantics is more complex and children traversal is defined according to the current viewpoint (Billboard, LOD or Collision) or the browser mouse position (Anchor). So the default behaviour does not go down along these nodes.
  - wrl::Anchor
  - wrl::Billboard
  - wrl::Collision
  - wrl::LOD

To define a new traversal, derive your own class from this base class and override the different treatments. Here is an example of a traverser that simply counts the number of spheres present in a VRML file. Let's first look at the header file:

    #ifndef SPHERECOUNTER_H
    #define SPHERECOUNTER_H

    #include <xdkwrl/tools/traverser.h>

    class SphereCounter : public wrl::Traverser
    {
    public:
      SphereCounter();
      unsigned int nbSpheres() const;
    protected:
      virtual void treat(wrl::Sphere*);
    private:
      unsigned int nbSpheres_;
    };

    #endif // SPHERECOUNTER_H

It is pretty straight forward: we derive from wrl::Traverser, which requires to include the header `xdkwrl/tools/traverser.h`. Then we override the `treat(wrl::Sphere*) method`.

The implementation is no that much complex:

    #include "spherecounter.h"

    using namespace wrl;

    SphereCounter::SphereCounter()
      : nbSpheres_(0)
    {
    }
    unsigned int
    SphereCounter::nbSpheres() const
    {
      return nbSpheres_;
    }
    void
    SphereCounter::treat(wrl::Sphere*)
    {
      ++nbSpheres_;
    }

Finally, we put all this together, defining a `main` function to load a VRML file (see Section Loading a VRML file for an explanation) and "apply" a sphere counter to its nodes.

    #include "spherecounter.h"

    #include <xdkwrl/scene.h>
    #include <xdkwrl/tools/argstream.h>

    #include <iostream>
    #include <string>

    using namespace std;

    int
    main(int ,char** argv)
    {
      string fileName(argv[1]); // should do better checks!!

      wrl::Scene scene;
      scene.load(fileName);

      SphereCounter sc;
      for_each(scene.nodes.begin(),scene.nodes.end(),sc);
      cout<<fileName<<" contains "<<sc.nbSpheres()<<" spheres"<<endl;

      return 0;
    }

The reason why we say "apply" is that since we used the `operator()` syntax for the wrl::Traverser class, it is a valid function object, that is it can be used exactly as a function. Therefore, we can use the STL algorithm `for_each` to simply apply this "function" to all nodes in the scene. If you are not familiar with the STL, I strongly advice that you get some interest into it. Meanwhile, here is how you could achieve the same result without `for_each`:

      for (unsigned int i=0;i<scene.nodes.size();++i)
      {
        sc(scene.nodes[i]);
      }

## Conclusion

It is pretty simple to define a traversal on a VRML tree. However, depending of what you want to do, the actual implementations of the `treat()` methods might be complex. But this is not of the concern of this library since it highly depends on what you want to do with the VRML file.

However, very often, you would like to retrieve the geometry in the file. One complex thing in that case is to track the transformation hierarchy during the traversal. If you use the traversal to build an adjacent hierarchy (for example for ray-tracing) that contains also transformation nodes, then it is pretty straightforward. However, if you want to "flatten" out the hierarchy, that is retrieve all the geometry expressed in a single frame coordinate, it is more tricky. Hopefully, the XdkWrl library can still help you a little bit if this is what you need to do, by providing wrl::Transformator and a wrl::TransformatorHierarchy classes. For more information, read the page .

Generated on 5 Jan 2007 with version 1.5.1. |