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.
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).
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.
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
andTransformSensor
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.
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.
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.
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.