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. Example in Pascal

Demo of grouping X3D nodes

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,
  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;
 
procedure WindowPress(Container: TUIContainer; const Event: TInputPressRelease);
begin
  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;
  end;
end;
 
var
  Window: TCastleWindow;
  Viewport: TCastleViewport;
  Scene: TCastleScene;
begin
  Window := TCastleWindow.Create(Application);
  Window.Open;
  Window.OnPress := @WindowPress;
 
  Viewport := TCastleViewport.Create(Application);
  Viewport.FullSize := true;
  Viewport.Camera.Translation := Vector3(2, -1, 8);
  Window.Controls.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));
 
  Application.Run;
end.