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.
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:
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.
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.
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.
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:
Inline nodes load their
referenced content as their children
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.