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:

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.
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 24 Feb 2005 with doxygen version 1.3.9.1. Valid HTML 4.0! Valid CSS!