Core Scene Graph Data Structures in C++


Loading and Saving

Several operations, including the file loaders, the file savers, and the rendering loop require methods to depth-first traverse the scene graph. In the case of the file loaders, the scene graph is also constructed as the file is incrementally parsed. To simplify these tasks as implemented in the LoaderWrl C++ class, the VRML file loader assumes some restrictions on the VRML syntax. That is, the input files to the DGP2023 application are valid VRML files, but the file loader is guaranteed to parse only those which satisfy the restrictions listed below. In addition, the file loader is able to parse all the files written by the file saver, as implemented in the SaverWrl class.

After the first header line "#VRML V2.0 utf8" the VRML file is only allowed to have a sequence of nodes, concatenated one after the other. Only the Group, Transform, and Shape nodes are allowed at this level. We have also implemented the DEF/USE syntax as defined in the VRML’97 Standard to assign names to nodes and to reuse nodes. In this implementation of the SceneGraph data structure, we are able to load VRML files containing node names, and we are able to save the names to the output files.

All these nodes are saved in the children field of the SceneGraph node. Even though the SceneGraph node is implemented as a subclass of Graph, the tokens “children” “[“ and “]” are not written in the file to delimit its children. Instead, all the nodes parsed after the header line are considered children of the SceneGraph node. The end of the file marks the end of the list of children of the SceneGraph node. If the application is successful parsing the whole file without errors, it should also save the filename in the SceneGraph field “filename”.

The nodes “Appearance”, “Material”, “ImageTexture”, and “IndexedFaceSet” can be found only while parsing a Shape node. They are not allowed to be listed as children of a Group or Transform node, and they cannot be children of the top level SceneGraph node either. In all cases, after recognizing the type of node from the parsed token, the file loader constructs an instance of the node, attach it to the proper place (children of a Group, Transform, or SceneGraph node; or specific field of another node), and parses the node starting from the expected “{“ token until the matching “}” token. One way to organize the loader code is to write a separate function to parse each different node class. For example, our implementation has the following structure

void load(string filename, SceneGraph& wrl) {
  try {
    // open file
    ifstrm istrm(filename);
    // read header line and verify
    // ...
    // create Tokenizer from open input stream Tokenizer tokenizer(istrm);
    // parse SceneGraph children while(tokenizer.get()) {
    if(tokenizer==”Shape”) {
      Shape* shape = new Shape();
      Wrl.addChild(shape);
      parseShape(istrm,*shape);
    } else if(tokenizer==”Graph”) {
      Graph* graph = new Graph();
      wrl.addChild(graph);
      parseGraph(istrm,*graph);
    } else if(tokenizer==”Transform) {
      Transform* transform = new Transform();
      wrl.addChild(transform);
      parseTransform(istrm,*transform);
    }
    wrl.setUrl(filename);
  } catch(MyException* e) { wrl.clear();
    cerr<< “ERROR | “ << e-­‐>what() << endl; delete e;
  }
}

Again, note that after a token such as “Shape” is detected, an instance of the Shape class is constructed, added to its parent as a child, and then the parser method for the Shape class is invoked. This parser parses from the “{“ token expected to follow the “Shape” token, until the matching “}” token is detected. While parsing the Shape node, the parseShape() method may encounter a number of other nodes which can only appear in specific places within a Shape node. For example, if a “Material” token is found, then a parseMaterial() method is be called, which parses from the “{“ token expected to follow the “Material” token, until the matching “}” token is detected.

When the load() method described above encounters a “Graph” token, it creates an instance of the Graph class, adds it to its parent as a child, and calls the parseGraph() method, which parses from the “{“ token expected to follow the “Graph” token, until the matching “}” token is detected. Within these limits the parseGraph() method may encounter the “children” token, which should be followed by the “[“ token, an arbitrary number of “Shape”, “Transform”, or “Graph” nodes, and a closing “]” token. For each of the children nodes, the parseGraph() calls the corresponding node parser. The process continues recursively in this way, resulting in a depth-­‐first traversal of the SceneGraph structure.