3.10. VRML scene

If you want to operate on the VRML graph, for some purposes it's enough to load your scene to a TVRMLNode instance. This way you know the root node of the scene. Each node points (within it's Children property and SFNode and MFNode fields) to it's children nodes, so if you know the root node of the scene, you know the whole scene. TVRMLNode class gives you many methods to operate on the nodes graph, and sometimes this is all you need.

However, some operations cannot be implemented in TVRMLNode class. The basic reason is that the node doesn't know the state of VRML graph where it is used. Node's state is affected by other nodes that may be it's parents or siblings. Moreover, a node may be used many times in the same scene (by DEF / USE mechanism), so it may occur many times in a scene with different states. That's why many TVRMLNode methods (like Triangulate and BoundingBox methods described in Section 3.7, “Geometry nodes features”) require a parameter State: they are not able to figure it out automatically.

These are the reasons why an additional class, called TCastleSceneCore, was created. It is essentially just a wrapper around a VRML root node (kept inside it's RootNode property) adding a lot of useful and comfortable methods to operate and investigate the scene as a whole.

3.10.1. VRML shape

First, let's introduce a building block for our scene class: a shape. Instance of TShape class. Shape is basically two pieces of information: a geometry node (TVRMLGeometryNode) and it's state (TX3DGraphTraverseState). For VRML >= 2.0, this usually corresponds to a single instance of actual VRML Shape node, that's the reason for it's name.

Shape contains absolutely all the information needed to render and generally deal with this piece of VRML graph. It's completely independent from other shapes.

For VRML 2.0, some shape features were already available. That's because of smart definitions of children fields of grouping nodes, as explained earlier in Section 1.5.1, “Why VRML 2.0 is better”: we don't need so much state information in VRML 2.0 and we can pick children of grouping nodes in any order. Still, our shape provides the more complete solution: it includes also accumulated transformation matrix and global properties (fog and active lights).

3.10.2. Simple tree of shapes

This is the main property of TCastleSceneCore. The idea is simple: to overcome the problems with VRML state, we can just use Traverse method from the root node (see Section 3.6, “Traversing VRML graph”) and store every geometry node (descendant of TVRMLGeometryNode, see Section 3.7, “Geometry nodes features”) along with it's state. As a result we get a simple list of shapes. This list is, to some extent, an alternative flattened representation of the VRML graph.

Actually, we can't really have a completely flat list of shapes. Instead, we create a simple, usually quite flat tree of shapes, in TCastleSceneCore.Shapes. Reason: some things, like Switch node, require some processing each time we want to browse the tree (this way, we keep track of shapes in inactive Switch children, which allows us very fast switching of Switch.whichChoice, that is: replacing/adding/removing large parts of VRML graph).

So we take VRML nodes graph, and transform it into another graph (shapes tree)... But the resulting tree is really much simpler, it's just as simple representation of VRML visible things as you can get.

This way we solve various problems mentioned in Section 1.5, “VRML 1.0 state”: we get full accumulated VRML state readily available for each shape. Also, given a tree of shapes, we can pick our shapes in any order, and we can pick any of them. This is crucial for various OpenGL rendering features and optimizations.

Additional advantage of looking at our shapes tree is that resources completely not used (for example Texture2 node not used by any node in VRML 1.0) are not present there. They don't occur in a state of any shape. So unused textures will not be even loaded from their files.

Finally, remember that in Section 1.5, “VRML 1.0 state” we mentioned a practical problem of simple VRML 1.0 implementation in OpenGL: OpenGL stack sizes are limited. Our scene solves this, because there is no unlimited push/pop hierarchy anymore. Features of nodes like VRML 1.0 Separator and TransformSeparator are already handled at this stage. And they are handled without using any OpenGL stacks, since this unit can't even depend on OpenGL. Features of VRML 2.0 Transform nodes that apply transformation to all it's children are already handled here too.

3.10.3. Events

TCastleSceneCore is responsible for implementing most of the events mechanism of VRML / X3D. Just set ProcessEvents property to true.

Some underlying parts of events mechanism are in fact implemented at the lower level, that is inside TVRMLNode class and friends. For example, event routes are instantiated when reading VRML file and they become attached to VRML graph. So passing events through routes is already working at this point. Also, exposed events are implemented directly inside TX3DField. So setting an exposed field by eventIn causes appropriate behavior (changing field's value and generating proper eventOut).

However, without TCastleSceneCore.ProcessEvents, all these routes and exposed events are useless, since nothing initially fires the event. Routes and exposed events are mechanisms to process events, but they cannot generate events on their own, that is they generate events only when other events push them to it. The way to make an initial event in VRML / X3D is to use sensor nodes. Various sensor nodes emit events at specified situations, for example

  • TimeSensor fires events continuously when time changes,

  • KeySensor fires events when user presses a key within VRML browser,

  • TouchSensor and others from pointing device sensor component in X3D fire events when user clicks / drags with mouse,

  • ProximitySensor and TransformSensor fire events on collision (of viewer or normal objects within VRML world) with user-defined boxes in space, thus allowing collision detection to VRML authors.

By setting TCastleSceneCore.ProcessEvents to true (and updating TCastleSceneCore.WorldTime, TCastleSceneCore.KeyDown and others) you make sensors work. Thus initial events are generated when appropriate, and routes and exposed events take care of spreading them, changing VRML graph as necessary.

3.10.4. Various comfortable routines

Numerous other features are available in our scene class:

  • Methods to calculate bounding box, vertexes count and triangles count of the whole scene. They work simply by summing appropriate results of all shapes.

  • Methods to calculate triangles list (triangulate all shapes in the scene) and to build octrees for the scene. There are also comfortable properties to store the build octree associated with given scene — although our engine doesn't limit how you manage the constructed octrees, you can create as many octrees for given scene as you want and store them where you want.

    More about octrees in Chapter 4, Octrees.

  • Methods to find Viewpoint or camera nodes, transform them, and calculate simple (position, direction, up) triple describing camera setting.

  • Methods to find Fog node and calculate it's transformation.

3.10.5. Caching

Some scene properties are quite time-consuming to calculate. Calculating the tree of shapes requires traversing whole scene graph. Calculating scene bounding box is even more difficult, since for each shape we must calculate it's bounding box (in fact calculation of scene bounding box as implemented simply uses the shapes tree). Obviously we cannot repeat these calculations each time we need these values. So the results are cached inside TCastleSceneCore instance.

Most of the properties are cached: shapes, bounding boxes, vertexes and triangles counts, fog properties. Even triangles' lists may be cached if you want.

Also various properties for single shapes are cached inside TShape instance: bounding box, bounding sphere and triangle and vertexes counts. After all, some of these operations are quite time-consuming by themselves. For example to calculate bounding box of IndexedFaceSet we have to iterate over all it's coordinates.

Direct changes to actual VRML nodes are not automatically detected. In other words cache is not automatically cleared on changes. Instead you have to manually call TCastleSceneCore.ChangedField (or eventually TCastleSceneCore.ChangedAll) after changing some parts of the scene. Scene analyzes how this change affects the rendered scene, and invalidates as few as possible parts of the cache.

For example changes to VRML 1.0 nodes like Texture2 or Material will affect only the shapes that have these nodes in their state. So the whole shapes tree doesn't need to be regenerated, also the information about other shapes (like their bounding boxes) is still valid.

For simple scene changes, you can also use TX3DField.Send methods. This will change the value of the field, and automatically notify all interested scenes. You can also just send events instead of directly modifying fields, see the next section.

In Section 6.4, “VRML scene class for OpenGL” we will introduce the TCastleScene class that descends from TCastleSceneCore. It adds various OpenGL methods and caches various OpenGL resources related to rendering particular scene parts. This means that our ChangedField method will have even greater impact.

3.10.6. Events and ChangedField notifications

At the low level, passing events works by TX3DEvent.Send method and TX3DEvent.OnReceive callbacks. Both input and output events can be send and received: for input events, it's the outside world (routes, scripts) that sends the event, and handling of the event is specific to containing node. For output events, it's the other way around: sending the event is specific to containing node, and the event is received by connected routes.

When exposed fields are changed through events, TCastleSceneCore takes care to automatically internally call appropriate ChangedField methods. This means that events mechanism automatically updates everything as necessary, and you don't have to worry about it — the VRML world inside TCastleSceneCore will just magically change by itself, assuming TCastleSceneCore.ProcessEvents is on. This also means that ChangedField methods implement the cherry-picking optimizations when VRML graph is changed: they know about what changed, and they know how it affects the rest of the VRML graph, and so they decide what needs to be recalculated. For example, when Coordinate node changed through event, we know that only geometry using this coordinate node has changed, so only it's resources need to be recomputed. There are a lot of possibilities to optimize here by using knowledge about what specific node does, what it possibly affects etc. VRML 2.0 things are easier and probably more optimized in this regard — reasons were given in Section 1.5, “VRML 1.0 state” and Section 1.5.1, “Why VRML 2.0 is better”.

So we have three methods of changing the field value. Do it directly, like

Field.Value := 666;
Scene.ChangedField(Field);

or do it by sending event, like

Field.EventIn.Send(666);

or use the simplest TX3DField.Send method, that sends an event (or directly changes value, if events processing is turned off), like

Field.Send(666);

This will trigger all event callbacks, so the field value will change, and everyone interested will be notified about this: output event of exposed field will be generated and sent along the routes, and TCastleSceneCore will be notified about the change.