Chapter 3. Reading, writing, processing VRML scene graph

Table of Contents

3.1. TVRMLNode class basics
3.2. The sum of VRML 1.0 and 2.0
3.3. Reading VRML files
3.4. Writing VRML files
3.4.1. DEF / USE mechanism when writing
3.4.2. VRML graph preserving
3.5. Constructing and processing VRML graph by code
3.6. Traversing VRML graph
3.7. Geometry nodes features
3.7.1. Bounding boxes
3.7.2. Triangulating
3.8. WWWBasePath property
3.9. Defining your own VRML nodes
3.10. VRML scene
3.10.1. VRML shape
3.10.2. Simple tree of shapes
3.10.3. Events
3.10.4. Various comfortable routines
3.10.5. Caching
3.10.6. Events and ChangedField notifications

This and the following chapters will describe how our VRML engine works. We will describe used data structures and algorithms. Together this should give you a good idea of what our engine is capable of, where are it's strengths and weaknesses, and how it's all achieved.

In this document we should not go into details about some ObjectPascal-specific language constructs or solutions — this would be too low-level stuff, uninteresting from a general point of view. If you're an ObjectPascal programmer and you want to actually use my engine then you may find it helpful to study source code (especially example programs in examples subdirectories) and units reference while reading this document. If you only want to read this document, everything that you need is some basic idea about object-oriented programming.

3.1. TVRMLNode class basics

The base class of our engine is the TVRMLNode class, not surprisingly representing a VRML node. This is an abstract class, for all specific VRML node types we have some descendant of TVRMLNode defined. Naming convention for non-abstract node classes is like TNodeCoordinate class for VRML Coordinate node type.

Every VRML node has it's fields available in it's Fields property. You can also access individual fields by properties named like FdXxx, for example FdPoint is a property of TNodeCoordinate class that represents point field of Coordinate node.

VRML 1.0 children nodes are accessed by Children and ChildrenCount properties. For VRML 2.0 this is not needed, since you access all children nodes by accessing appropriate SFNode and MFNode fields. A convenience properties named SmartChildren and SmartChildrenCount are defined: for normal VRML 2.0 grouping nodes (this mostly means nodes with MFNode field named children) the SmartChildrenXxx properties operate on appropriate MFNode, for other nodes they operate on VRML 1.0 ChildrenXxx properties.

Because of DEF / USE mechanism each node may be a children (children both in the VRML 1.0 and 2.0 senses) of more than one node. This means that we cannot use some trivial destructing strategy. When we destruct some node's instance, we cannot simply destruct all it's children, because they are possibly used in other nodes. The simple solution to this is to keep track in each node about it's parents. Each node has properties ParentNodes and ParentNodesCount that track information about all the nodes that use it in VRML 1.0 style (i.e. on TVRMLNode.Children list). And properties ParentFields and ParentFieldsCount that track information about all the SFNode and MFNode fields referencing this node. The children node is automatically destroyed when it has no parents — which means that both ParentNodesCount and ParentFieldsCount are zero. Effectively, we implemented reference-counting. And as a bonus, ParentXxx properties are sometimes helpful when we want to do some bottom-to-top processing of VRML graph (although this should be generally avoided, top-to-bottom processing is much more in the spirit of the VRML graph).

Classes for VRML nodes specific to particular VRML version get a suffix _1 or _2 representing their intended VRML version. For example, we have TNodeIndexedFaceSet_1 (for VRML 1.0) and TNodeIndexedFaceSet_2 (for VRML 2.0) classes. Such nodes always have their ForVRMLVersion method overridden to indicate in what VRML version they are allowed to be used. For example, when parser starts reading IndexedFaceSet node, it creates either TNodeIndexedFaceSet_1 or TNodeIndexedFaceSet_2, depending on VRML version indicated in the file header line. Note that this separation between VRML versions is done only when reading VRML nodes from file. When processing VRML nodes graph by code you can freely mix VRML nodes from various VRML versions and everything will work, including writing nodes back to VRML file (although if you mix VRML versions too carelessly you may get VRML file that can only be read back by my engine, and not by other engines that may be limited to only VRML 1.0 or only VRML 2.0). More on this later in Section 3.2, “The sum of VRML 1.0 and 2.0”.

The result of parsing any VRML file is always a single TVRMLNode instance representing the root node of the given file. If the file had more than one root node [7] then our engine wraps them in an additional Group node. More precisely, additional instance of TVRMLRootNode is created. It descends from TNodeGroup_2 (but is suitable for all VRML/X3D versions). This way it can always be treated as 100% normal Group nodes. At the same time, VRML writing code can take special precautions to not record these fake group nodes back to VRML file.



[7] Multiple root nodes are allowed in VRML 2.0 specification. Our engine also allows them for VRML 1.0 because it's an extension often expected by VRML 1.0 creators (humans and programs).