Grouping component
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).
See also X3D specification of the Grouping component.
Contents:
1. Supported nodes
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.
2. How to calculate bounding box of something
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:
3. TODO
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
4. Example in Pascal
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.