2.4. Writing VRML files

SaveToStream method of TVRMLNode class allows you to save node contents (including children nodes) to any stream. Just like for reading, there are also more comfortable routines for writing called SaveToVRMLFile.

2.4.1. DEF / USE mechanism when writing

When writing we also keep track of all node names defined to make use of DEF / USE mechanism. If we want to write a named node, we first check NodeNameBinding list whether the same name with the same node was already written to file. If yes, then we can place a USE statement, otherwise we have to actually write the node's contents and add given node to NodeNameBinding list.

The advantages of above NodeNameBinding approach is that it always works correctly. Even for node graphs created by code (as opposed to node graphs read earlier from VRML file). If node graph was obtained by reading VRML file, then the DEF / USE statements will be correctly written back to the file, so there will not be any unnecessary size bloat. But note that in some cases if you created your node graph by code then some node contents may be output more than once in the file:

  1. First of all, that's because we can use DEF / USE mechanism only for nodes that are named. For unnamed nodes, we will have to write them in expanded form every time. Even if they were nicely shared in node graph.

  2. Second of all, VRML name scope is weak and if you use the same node name twice, then you may force our writing algorithm to write node in expanded form more than once (because you “overridden” node name between the first DEF clause and the potential place for corresponding USE clause).

So if you process VRML nodes graph by code and you want to maximize the chances that DEF / USE mechanism will be used while writing as much as it can, then you should always name your nodes, and name them uniquely.

It's not hard to design a general approach that will always automatically make your names unique. VRML 97 annotated specification suggests adding to the node name an _ (underscore) character followed by some integer for this purpose. For example, in our engine you can enumerate all nodes (use EnumerateNode method), and for each node that is used more than once (you can check it easily: such node will have ParentNodesCount + ParentFieldsCount > 1) you can append '_' + PtrUInt(Pointer(Node)) to the node name. The only problem with this approach (and the reason why it's not done automatically) is that you will have to strip these suffixes later, if you will read this file back (assuming that you want to get the same node names). This can be easily done (just remove everything following the last underscore in the names of multiply instantiated nodes). But then if you load the created VRML file into some other VRML browser, you may see these internal suffixes anyway. That's why my decision was that by default such behavior is not done. So the generated VRML file will always have exactly the same node names as you specified.

2.4.2. Determining VRML version when writing

Since our engine supports various VRML versions, there appears a question when writing VRML files: what VRML version to indicate in created file header ? One solution would be to save for this purpose in TVRMLNode version numbers of the original VRML file from where the node was read. But our engine must also allow easy construction of VRML files by code, so not every node is obtained from parsing some file. Adding some public fields to TVRMLNode or parameters for SaveToVRMLFile to explicitly indicate desired version is one simple solution, but it's tiresome — it's another piece of information that will have to be figured out and provided when constructing VRML files by code.

Finally the approach we take in our engine places the burden on implementation of each TVRMLNode descendant. If you only create nodes of already defined classes, everything should just magically work. Every TVRMLNode descendant can override SuggestedVRMLVersion method to indicate it's desired VRML version, and how strong is the preference (SuggestionPriority parameter). The idea is that node decides about the desired VRML version by collecting desired VRML version of all it's children and then adding his own preference. And there is a SuggestionPriority parameter, so that VRML files that use our engine extension that allows to mix VRML 1.0 and 2.0 constructions (see Section 2.2, “The sum of VRML 1.0 and 2.0”) will also be written correctly, i.e. using the closest VRML version for them. When writing VRML file, the SuggestedVRMLVersion method of root node is called and used to determine header line for the VRML file.

This means that if you have VRML nodes graph (either read from a file or constructed by code) using only VRML 1.0 features then it will be correctly written as VRML 1.0 file. Same for a file using only VRML 2.0 features. If you have a mixed file that is mostly VRML 1.0 and uses only some VRML 2.0 nodes that easily “match” into VRML 1.0 [9] then the written file will have VRML 1.0 header. Even though it will not be correct standard VRML 1.0 file — it's only correct for our engine. Of course for some ambiguous node graphs there will be no way to determine correct VRML version: for example if you used IndexedFaceSet nodes both in VRML 1.0 and 2.0 versions then the generated VRML version header is undefined (actually, it will depend on node order in the graph). This is one real problem of our “sum of VRML 1.0 and 2.0 approach”: VRML scene graph in memory is not necessarily valid for either VRML 1.0 or VRML 2.0.

To take into account the ambigous case of mixed VRML 1.0 and 2.0 features, we also store VRML version read from file inside TVRMLRootNode as a “strong suggestion” (in properties ForceVersionXxx). This means that you don't have to care about explicitly setting the version if you construct and process VRML file by your code, but at the same time when we read and then write the same VRML file, version is always exactly preserved.

2.4.3. VRML graph preserving

As was mentioned a couple of times earlier, we do everything to get the VRML scene graph in memory in exactly the same form as was recorded in VRML file, and when writing the resulting VRML file also directly corresponds (including DEF / USE mechanism and node names) to VRML graph in memory.

Actually, there are two exceptions:

  1. Inline nodes load their referenced content as their children

  2. When reading VRML file with multiple root nodes, we wrap them in additional Group node

... but we work around these two exceptions when writing VRML files. This means that reading the scene graph from file and then writing it back produces the file with the exact same VRML content. But whitespaces (including comments) are removed, when writing we reformat everything to look nice. So you can simply read and then write back VRML file to get a simple VRML pretty-printer.



[9] Examples of “easily matching in VRML 1.0” nodes from VRML 2.0 are Background and Fog nodes. They don't use any VRML 2.0 features like SFNode or MFNode, and they don't interact with other nodes state in complex way.