This component defines the basic nodes to operate on groups of other
nodes. Transform
node allows to additionally translate,
rotate or scale a group of nodes. Switch
node allows to
choose one children from a group of nodes (which is a useful tool
for various interactive animations).
Contents:
Group
(Pascal API: TGroupNode
)
Group of other X3D nodes.
Transform
(Pascal API: TTransformNode
)
Group of other X3D nodes, that additionally transforms (translates, rotates, scales) it's children.
This is the most basic X3D node to create a transformation hierarchy.
Build a hierarchy of Transform
nodes (one Transform
node
may be child of another, of course), and place Shape
nodes inside,
and you have a transformation hierarchy. You can use interpolation nodes
to animate it.
Note that a Transform
node that does not perform any transformation
(leaves translation, rotation, scale at default values) is equivalent
to the Group
node. If you don't plan to ever modify the transformation
of children, it's a little more efficient to use the Group
node.
Switch
(Pascal API: TSwitchNode
)
Group of other X3D nodes, of which only one (or none) is active at a given time.
"Active" means that a child (including all child's children) can be visible and colliding.
The field whichChoice
specifies the index of the active child,
negative value like -1
means that no child is active.
In a way, this node is like case
in Pascal or switch
in C-like languages: only one (or none) child is active.
Switch
can be used to choose one node from many,
e.g. a user selects something and you can display either a sword, or a shield, or a chest.
Switch
can also be used to toggle the existence of a single node.
In this case, you use Switch
node with only one node in the children
list, and you set whichChoice
to 0 (show the child) or -1 (don't show the child).
Note that to easily toggle the visibility of the shape you can alternatively use
boolean Shape.render field.
StaticGroup
(Pascal API: TStaticGroupNode
)
Group of nodes that is guaranteed to never change at runtime.
This node is similar to the Group
node, but it can never
change at runtime (e.g. due to X3D events or Pascal code).
This way it may work faster.
It is up to the developer to avoid changing the node contents,
it is undefined what will happen if a change occurs (maybe the change
will be ignored, maybe it will cause an error).
Currently CGE doesn't use the possible optimizations offered by this node.
It is treated exactly like Group
.
If you have a Group
of nodes and you will never change it at runtime,
we are actually already quite efficient at it.
Future version may bring e.g. "static batching" that could look at this node.
Note: The bboxCenter
and bboxSize
fields of X3D grouping nodes are right now ignored by CGE. Instead, we internally always calculate and update best bounding boxes (and bounding spheres) for collision. So there's no need to fill these X3D fields.
There are various properties in CGE that may specify the bounding box of something. Some of them are calculated by our engine, and updated (e.g. on animations) and are guaranteed to contain a useful value. Some of them do not — they may contain a value, useful for some optimizations, but it is not guaranteed.
To summarize:
To have a reliable bounding box of a scene (actually, any transform), that is calculated by CGE, and updated (e.g. in case of animations) by CGE, you should use one of these:
Use these to calculate bounding box of the entire scene (and all children scenes). See their docs for details about their coordinate systems.
To have a reliable bounding box of a shape, that is calculated and updated (e.g. in case of animations) by CGE, you should use one of these:
TShape.BoundingBox
. It is specified in the scene coordinates (transform by MyScene.WorldTransform
to get the bounding box in world coordinates).
TShape.LocalBoundingBox
. It is specified in the shape coordinates. Transform it by MyScene.WorldTransform * MyShape.OriginalState.Transformation.Transform
to get the bounding box in world coordinates.
To find the TShape
instance you are interested in,
find it among the Scene.Shapes
. For example search it using the MyScene.Shapes.Traverse
or MyScene.Shapes.TraverseList
.
TShape
has a lot of information to inspect what it is. E.g. TShape.Node
, that points to TShapeNode
, which name you can in turn check e.g. like MyShape.Node.X3DName
. We also store information about the parent nodes of shape, in TShape.GeometryParentNode
, TShape.GeometryGrandParentNode
, TShape.GeometryGrandGrandParentNode
. You can look at shape geometry in TShape.Geometry
.
The TAbstractGroupingNode.BBox
may specify the bounding box of a group of nodes.
This property has the value that was specified in the X3D file. Or a value that was manually set there by some Pascal code (e.g. when converting something to X3D nodes). But no code in CGE does this right now.
If provided, this value is given in local group coordinates.
CGE does not validate, or calculate, or ever update this value. It is also not used by CGE for anything, right now (but we could use it for optimizations in the future).
If the X3D file didn't specify any value, it will be an empty bounding box. You can check it by TBox3D.IsEmpty
.
Note: the BBox
underneath uses FdBBoxCenter
, FdBBoxSize
. We discourage from using these lower-level fields directly, and they don't provide any extra information.
The TAbstractShapeNode.BBox
may specify the bounding box of a given shape.
This property has the value that was specified in the X3D file. Or a value that was manually set there by some Pascal code (e.g. when importing glTF to X3D nodes). glTF importer right now sets this value.
If provided, this value is given in local shape coordinates.
CGE does not validate, or calculate, or ever update this value. But, if it is specified (provided in X3D file or by some Pascal import code) then we use it, for optimization.
If the X3D file didn't specify any value, it will be an empty bounding box. You can check it by TBox3D.IsEmpty
.
Note: the BBox
underneath uses FdBBoxCenter
, FdBBoxSize
. We discourage from using these lower-level fields directly, and they don't provide any extra information.
In summary:
TAbstractShapeNode.BBox
is similar to TAbstractGroupingNode.BBox
. There's no guarantee that it contains a useful value, it contains a value only if something was specified (in X3D file, or by importer).
But, in contrast to TAbstractGroupingNode.BBox
, the TAbstractShapeNode.BBox
is actually used by CGE for optimizations. And it is automatically set by the glTF importer. So if you load your model from glTF, you can count on TAbstractShapeNode.BBox
having a useful value.
Below fields are not implemented yet, but certainly planned and should be easy. Please report if you need any of these high-priority.
X3DGroupingNode.visible
(Note: we do support X3DShapeNode.visible
, so you can hide individual shapes.)
X3DGroupingNode.bboxDisplay
The example below builds a node hierarchy like this:
Group (root node) -> Group -> Transform (translate x = 1) -> Shape (red material) -> Box -> Transform (translate x = 2) -> Shape (green material) -> Box -> Transform (translate x = 3) -> Shape (blue material) -> Box -> Transform (translate to the right and down) -> Switch -> Shape -> Sphere -> Shape -> Cone -> Shape -> Cylinder
The Group
and Transform
simply arrange the nodes positions
on the screen.
Note that this node hierarchy could be encoded in X3D (in XML or classic encoding) as well, and only loaded from Pascal. This has some benefits (e.g. an X3D file can be tested by view3dscene). Below we construct everything in Pascal just as a demo, to show that it is possible.
Press the s
key to toggle what is displayed in the Switch
node:
one of the children, or nothing.
{ Demo of Group, Transform, Switch nodes. } uses SysUtils, CastleWindow, CastleScene, CastleViewport, CastleCameras, CastleUiControls, CastleColors, CastleVectors, CastleFilesUtils, X3DNodes, CastleKeysMouse; var Switch: TSwitchNode; function BuildRootNode: TX3DRootNode; var GroupBoxes: TGroupNode; Box: TBoxNode; BoxTransform: TTransformNode; BoxShape: TShapeNode; BoxMaterial: TMaterialNode; BoxAppearance: TAppearanceNode; TransformSwitch: TTransformNode; SwitchChildShape: TShapeNode; SphereAppearance: TAppearanceNode; begin Result := TX3DRootNode.Create; GroupBoxes := TGroupNode.Create; Result.AddChildren(GroupBoxes); { create red box } Box := TBoxNode.CreateWithTransform(BoxShape, BoxTransform); Box.Size := Vector3(0.75, 0.75, 0.75); { TBoxNode.CreateWithTransform is a shortcut for: Box := TBoxNode.Create; BoxShape := TShapeNode.Create; BoxShape.Geometry := Box; BoxTransform := TTransformNode.Create; BoxTransform.AddChildren(BoxShape); } BoxTransform.Translation := Vector3(1, 0, 0); BoxMaterial := TMaterialNode.Create; BoxMaterial.DiffuseColor := RedRGB; BoxAppearance := TAppearanceNode.Create; BoxAppearance.Material := BoxMaterial; BoxShape.Appearance := BoxAppearance; GroupBoxes.AddChildren(BoxTransform); { create green box } Box := TBoxNode.CreateWithTransform(BoxShape, BoxTransform); Box.Size := Vector3(0.75, 0.75, 0.75); BoxTransform.Translation := Vector3(2, 0, 0); BoxMaterial := TMaterialNode.Create; BoxMaterial.DiffuseColor := GreenRGB; BoxAppearance := TAppearanceNode.Create; BoxAppearance.Material := BoxMaterial; BoxShape.Appearance := BoxAppearance; GroupBoxes.AddChildren(BoxTransform); { create blue box } Box := TBoxNode.CreateWithTransform(BoxShape, BoxTransform); Box.Size := Vector3(0.75, 0.75, 0.75); BoxTransform.Translation := Vector3(3, 0, 0); BoxMaterial := TMaterialNode.Create; BoxMaterial.DiffuseColor := BlueRGB; BoxAppearance := TAppearanceNode.Create; BoxAppearance.Material := BoxMaterial; BoxShape.Appearance := BoxAppearance; GroupBoxes.AddChildren(BoxTransform); { create translated Switch node with children } TransformSwitch := TTransformNode.Create; TransformSwitch.Translation := Vector3(2, -2, 0); Result.AddChildren(TransformSwitch); Switch := TSwitchNode.Create; Switch.WhichChoice := 0; // initially TransformSwitch.AddChildren(Switch); TSphereNode.CreateWithShape(SwitchChildShape); SphereAppearance := TAppearanceNode.Create; SphereAppearance.Material := TMaterialNode.Create; // assign any material, to make it lit SwitchChildShape.Appearance := SphereAppearance; Switch.AddChildren(SwitchChildShape); TConeNode.CreateWithShape(SwitchChildShape); SphereAppearance := TAppearanceNode.Create; SphereAppearance.Material := TMaterialNode.Create; // assign any material, to make it lit SwitchChildShape.Appearance := SphereAppearance; Switch.AddChildren(SwitchChildShape); TCylinderNode.CreateWithShape(SwitchChildShape); SphereAppearance := TAppearanceNode.Create; SphereAppearance.Material := TMaterialNode.Create; // assign any material, to make it lit SwitchChildShape.Appearance := SphereAppearance; Switch.AddChildren(SwitchChildShape); end; type { View to contain whole UI and to handle events, like key press. } TMyView = class(TCastleView) function Press(const Event: TInputPressRelease): Boolean; override; end; function TMyView.Press(const Event: TInputPressRelease): Boolean; begin Result := inherited; if Result then Exit; if Event.IsKey(keyS) then begin // Switch.WhichChoice cycles from -1 to 2. Switch.WhichChoice := Switch.WhichChoice + 1; if Switch.WhichChoice > 2 then Switch.WhichChoice := -1; Exit(true); end; end; var Window: TCastleWindow; Viewport: TCastleViewport; Scene: TCastleScene; MyView: TMyView; begin Window := TCastleWindow.Create(Application); MyView := TMyView.Create(Application); Window.Container.View := MyView; Viewport := TCastleViewport.Create(Application); Viewport.FullSize := true; Viewport.Camera.Translation := Vector3(2, -1, 8); MyView.InsertFront(Viewport); Scene := TCastleScene.Create(Application); Scene.Load(BuildRootNode, true); Scene.PreciseCollisions := true; Viewport.Items.Add(Scene); Viewport.InsertFront(TCastleExamineNavigation.Create(Application)); // add a simple headlight, i.e. directional light attached to the camera Viewport.Camera.Add(TCastleDirectionalLight.Create(Application)); Window.Open; Application.Run; end.