Transform, animate, duplicate, build a scene

Car 3D model
Car on a road
More cars!
Cars, surrounded by a wall build in code

In the last chapter, we created a TCastleScene instance. This is a very powerful class in our engine, it represents any non-trivial 3D or 2D object. You often load it's contents from the file using the Load method. To actually make it visible (and animated, and sometimes even interactive), you need to also add it to the Viewport.Items.

In this chapter, we will extend a little the code from the previous chapter, to add more functionality around the scene.

A complete program using the code shown here is in the engine examples, in the examples/3d_rendering_and_processing/cars_demo.lpr. If you get lost or are unsure where to place some code snippet, just look there:)

1. Transform

You can group and transform (move, rotate, scale) scenes using the TCastleTransform class. Actually, the TCastleScene is itself a descendant of TCastleTransform, so it can be transformed and can have children. Let's change the program from previous chapter to make the car (one 3D object) move along a road (another 3D object).

  1. At the place where you declared Scene: TCastleScene; in the previous chapter, change it to CarScene: TCastleScene;, and add a second scene RoadScene: TCastleScene;.

  2. Create both scenes, placing both CarScene and RoadScene; as children of Viewport.Items.

    The complete code doing this, using TCastleWindowBase, looks like this:

    uses SysUtils, CastleVectors, CastleViewport,
      CastleFilesUtils, CastleWindow, CastleSceneCore, CastleScene;
      Window: TCastleWindowBase;
      Viewport: TCastleViewport;
      CarScene, RoadScene: TCastleScene;
      Window := TCastleWindowBase.Create(Application);
      Viewport := TCastleViewport.Create(Application);
      Viewport.FullSize := true;
      Viewport.AutoNavigation := true;
      CarScene := TCastleScene.Create(Application);
      CarScene.Spatial := [ssRendering, ssDynamicCollisions];
      CarScene.ProcessEvents := true;
      RoadScene := TCastleScene.Create(Application);
      RoadScene.Spatial := [ssRendering, ssDynamicCollisions];
      RoadScene.ProcessEvents := true;
      Viewport.Items.MainScene := RoadScene;
      // nice camera to see the road
        Vector3(-43.30, 27.23, -80.74),
        Vector3(  0.60, -0.36,   0.70),
        Vector3(  0.18,  0.92,   0.32)

    If, instead of TCastleWindowBase, you use TCastleControlBase, you should be able to adjust this code easily. Move the scenes setup to TForm1.FormCreate, and declare variables as private fields of TForm1. Consult the previous chapter as needed.

    Note that we set Viewport.Items.MainScene as RoadScene. It doesn't really matter in this demo (and we could also leave MainScene unassigned). The MainScene determines some central things for the world (default camera, navigation mode, background / sky, fog settings). So you set MainScene to whichever 3D model determines these things for your world.

  3. To make the car actually moving, we should now update the TCastleTransform.Translation property. For example, we can update it in the Window.OnUpdate callback (if you use Lazarus, there's an analogous event TCastleControlBase.OnUpdate).

    Before opening the window, assign:

    Window.OnUpdate := @WindowUpdate;

    At the beginning of your program (but after the definition of the CarScene global variable), define the WindowUpdate procedure:

    procedure WindowUpdate(Container: TUIContainer);
      T: TVector3;
      T := CarScene.Translation;
      { Thanks to multiplying by SecondsPassed, it is a time-based operation,
        and will always move 40 units / per second along the -Z axis. }
      T := T + Vector3(0, 0, -40) * Container.Fps.SecondsPassed;
      { Wrap the Z position, to move in a loop }
      if T.Z < -70.0 then
        T.Z := 50.0;
      CarScene.Translation := T;

That's it, you have a moving object in your world, and the movement in 100% controlled by your code!

You can create any complex tree this way, using the TCastleTransform to build any transformation hierarchy that is comfortable.

2. Play animation

Once you load a scene, you can play a "named animation" within it, using the PlayAnimation method. Open the model with view3dscene and look at the Animation -> Named Animations submenu to see what animations are available. We read animations from a variety of 2D and 3D formats (X3D, VRML, castle-anim-frames, Spine, MD3).

Car animation in view3dscene Dragon animations in view3dscene

To play the animation called wheels_turning on our sample car model, do this sometime after loading the CarScene:

CarScene.PlayAnimation('wheels_turning', true);

You should see now that the wheels of the car are turning. Tip: If you can't easily see the wheels, remember that you can move around in the scene using mouse dragging and mouse wheel. See view3dscene documentation of the "Examine" camera mode. Of course you can configure or disable the default camera navigation in your games (see previous manual chapter for camera description).

There are many other useful methods related to "named animations". See the HasAnimation, ForceAnimationPose and AnimationDuration methods. And if these are not enough, note that the animation is just an X3D TimeSensor node. You can access the underlying node using the AnimationTimeSensor method, and use or even edit this animation as you wish.

See the play_animation example in engine sources for a demo of PlayAnimation capabilities.

3. Control the existence of an object

Any object descending from TCastleTransform, including TCastleScene and TCastleTransform, has properties Exists, Visible, Collides and Pickable. Setting Exists to false makes the object behave like it would not be present in the Viewport.Items tree at all — it's not visible, it's not collidable.

For example, you can toggle the visibility of the car when user presses the 'c' key, like this:

  1. Add unit CastleKeysMouse to your uses clause.

  2. Before opening the window, assign:

    Window.OnPress := @WindowPress;
  3. At the beginning of your program (but after the definition of the CarScene global variable), define the WindowPress procedure:

    procedure WindowPress(Container: TUIContainer; const Event: TInputPressRelease);
      if Event.IsKey('c') then
        CarScene.Exists := not CarScene.Exists;

Advanced hints:

  • In some cases, instead of changing the Exists property, it may be easier to override the GetExists function. This is actually used by the engine to determine whether object "exists". By default it simply returns the Exists property value, but you can change it to decide existence using any algorithm you need. E.g. maybe the object doesn't exist when it's too far from the player, maybe the object "blinks" for half a second in and out.... By changing the return value of GetExists, you can make the object change it's state every frame, at absolutely zero cost.

  • Note that simply changing the Viewport.Items contents has also almost-zero cost. So you can dynamically add and remove objects there during the game, it will be lighting fast.

  • The one thing you should not do during the game, if you hope to have a good performance: do not load from 3D model files during the game (e.g. do not call TCastleSceneCore.Load(URL, ...) method).

    If you want to add scenes dynamically during the game, it's better to load a pool of scenes at the initialization (and prepare them for rendering using the TCastleScene.PrepareResources method). Then add/remove such already-prepared scenes during the game. You can efficiently initialize many scenes from the same 3D model using the TCastleScene.Clone method, or load an X3D graph once using the LoadNode function and then use repeatedly the TCastleSceneCore.Load(TX3DRootNode, ...) overloaded version.

    See the manual chapter about optimization for more hints.

4. Many instances of the same 3D object

Many car instances

It's allowed to add the same instance of the TCastleScene many times to your viewport items hierarchy. This allows to reuse it's data completely, which is great for both performance and the memory usage.

For example, let's make 20 cars moving along the road! You will need 20 instances of TCastleTransform, but only a single instance of the TCastleScene. The modifications to the code are straightforward, just add CarTransforms that is an array of TCastleTransform:

  1. Declare it like CarTransforms: array [1..20] of TCastleTransform;.

  2. Initialize it like this:

    for I := Low(CarTransforms) to High(CarTransforms) do
      CarTransforms[I] := TCastleTransform.Create(Application);
      CarTransforms[I].Translation := Vector3(
        -6 + Random(4) * 6, 0, RandomFloatRange(-70, 50));

    We added a randomization of the initial car position. The RandomFloatRange function is in the CastleUtils unit. There's really nothing magic about the randomization parameters, I just adjusted them experimentally to look right:)

  3. In every WindowUpdate move all the cars, like this:

    procedure WindowUpdate(Container: TUIContainer);
      procedure UpdateCarTransform(const CarTransform: TCastleTransform);
        T: TVector3;
        T := CarTransform.Translation;
        { Thanks to multiplying by SecondsPassed, it is a time-based operation,
          and will always move 40 units / per second along the -Z axis. }
        T := T + Vector3(0, 0, -40) * Container.Fps.SecondsPassed;
        { Wrap the Z position, to move in a loop }
        if T.Z < -70.0 then
          T.Z := 50.0;
        CarTransform.Translation := T;
      I: Integer;
      for I := Low(CarTransforms) to High(CarTransforms) do

    As you can see, this code is very similar to what we had before. We just do it in a loop, for each CarTransforms[I], instead of transforming the CarScene.

Note that all 20 cars are in the same state (they display the same animation). This is the limitation of this technique. If you need the scenes to be in a different state, then you will need different TCastleScene instances. You can efficiently create them e.g. using the TCastleScene.Clone method. In general, it's best to leave this optimization (sharing the same scene multiple times) only for completely static scenes (where you don't turn on ProcessEvents and thus you don't animate them in any way).

5. Building and editing the scene

Cars, surrounded by a wall build in code

Up to now, we have treated TCastleScene as a kind of "black box", that can be loaded from file and then changed using only high-level methods like PlayAnimation. But you have much more flexibility.

The TCastleSceneCore has a property RootNode that holds a scene graph of your scene. In simple cases, you can ignore it, it is automatically created when loading the model from file, and automatically changed by animations (much like the DOM tree of an HTML document changes during the page lifetime). For more advanced uses, you should know that this whole scene graph can be modified at runtime. This means that you can process the 3D models in any way you like, as often as you like, and you can even build a complete renderable 3D object by code — without the need to load it from an external file. You can also build complicated 3D objects from simple ones.

Our scene graph is composed from X3D nodes organized into a tree. X3D is a standard for 3D graphics — basically, the guys designing X3D specification have proposed a set of 3D nodes, with sensible operations, and they documented it in great detail. Our engine implements very large part of the X3D specification, we also add some extensions for cool graphic effects like shadows, shader effects and bump mapping.

Here's an example of building a scene with two simple boxes. It shows nice transparent walls around our road. It uses the RoadScene.BoundingBox value to adjust wall sizes and positions to the road 3D model.

  1. Add units X3DNodes and CastleBoxes to your uses clause.

  2. Define a function that builds a scene from X3D nodes:

    function CreateBoxesScene: TCastleScene;
      WallHeight = 5;
      RoadBox: TBox3D;
      RootNode: TX3DRootNode;
      Appearance: TAppearanceNode;
      Material: TMaterialNode;
      Shape1, Shape2: TShapeNode;
      Box1, Box2: TBoxNode;
      Transform1, Transform2: TTransformNode;
      { The created geometry will automatically adjust to the bounding box
        of the road 3D model. }
      RoadBox := RoadScene.BoundingBox;
      if RoadBox.IsEmpty then
        raise Exception.Create('Invalid road 3D model: empty bounding box');
      Material := TMaterialNode.Create;
      { Yellow (we could have also used YellowRGB constant from CastleColors unit) }
      Material.DiffuseColor := Vector3(1, 1, 0);
      Material.Transparency := 0.75;
      Appearance := TAppearanceNode.Create;
      Appearance.Material := Material;
      Box1 := TBoxNode.Create('box_1_geometry');
      Box1.Size := Vector3(0.5, WallHeight, RoadBox.Size.Z);
      Shape1 := TShapeNode.Create('box_1_shape');
      Shape1.Appearance := Appearance;
      Shape1.Geometry := Box1;
      Transform1 := TTransformNode.Create('box_1_transform');
      Transform1.Translation := Vector3(RoadBox.Min.X, WallHeight / 2, RoadBox.Center.Z);
      Box2 := TBoxNode.Create('box_2_geometry');
      Box2.Size := Vector3(0.5, WallHeight, RoadBox.Size.Z);
      Shape2 := TShapeNode.Create('box_2_shape');
      { Reuse the same Appearance node for another shape.
        This is perfectly allowed (the X3D is actually a graph, not a tree). }
      Shape2.Appearance := Appearance;
      Shape2.Geometry := Box2;
      Transform2 := TTransformNode.Create('box_2_transform');
      Transform2.Translation := Vector3(RoadBox.Max.X, WallHeight / 2, RoadBox.Center.Z);
      RootNode := TX3DRootNode.Create;
      Result := TCastleScene.Create(Application);
      Result.Load(RootNode, true);

    Note that to transform X3D nodes we use the TTransformNode class. We have essentially two transformation trees in our engine:

    1. The "outer" tree is rooted in Viewport.Items, and shows scenes TCastleScene transformed by TCastleTransform and friends.
    2. The "inner" tree is inside every scene. It is rooted in TCastleSceneCore.RootNode, and shows shapes TShapeNode, and is transformed by TTransformNode.

    This is explained in detail in the chapter about the transformation hierarchy.

  3. Add the created scene to the viewport:


Note: in this case, it would probably be simpler to add these 2 walls (boxes) to the road.x3d file in Blender. Thus, you would not need to deal with them in code. But in general, this technique is extremely powerful to generate 3D scenes following any algorithm!

To construct a more flexible mesh than just a box, you can use a universal and powerful IndexedFaceSet node instead of a simple Box. For IndexedFaceSet, you explicitly specify the positions of the vertexes, and how they connect to form faces.

See the examples:

6. Further reading

6.1. Viewports: advanced notes

  • You can use many viewport instances. Just create your own TCastleViewport instance.

    The viewports may show the same, or completely different, scenes.

    The viewports may be used as layers if you set ViewportXxx.Transparent := true and place them on top of each other, using InsertFront in the proper order. This way you can explicitly render some objects on top of other objects, regardless of their positions in a 3D world.

  • You can show the same world from different cameras. For this, use additional TCastleViewport instances and copy the Viewport.Items value.

    For examples of this, see:

    • The chapter about user interface shows how to set additional viewport.
    • Engine example examples/3d_rendering_processing/multiple_viewports.lpr
    • Engine example examples/fps_game/fps_game.lpr

6.2. Creating descendants, implementing AI

You can create descendants to customize all the classes mentioned here. These descendants can override methods e.g. to collide or perform AI (move itself in the world).

Every object (a descendant of TCastleTransform, like TCastleScene or TCastleTransform) "knows" it's World so it knows how to move and collide within the 3D world. This opens various ways how you can implement "artificial intelligence" of a creature, for example:

  1. Derive your creature class from a TCastleTransform or TCastleTransform.
  2. Override it's Update method to move the creature. Use TCastleTransform.Move, TCastleTransform.MoveAllowed, TCastleTransform.Height and TCastleTransform.LineOfSight methods to query the world around you.
  3. As a child of your creature instance, add a TCastleScene that shows an animated creature.

In fact, this is how our creatures with ready AI are implemented:) But you can do it yourself.

6.3. It all applies to 2D too!

Basically, you don't need to learn anything new for 2D games.

  1. You can load 2D models (from X3D, VRML, Spine or any other format) into TCastleScene. Call TCastleScene.Setup2D method to make some scene properties perfect for rendering 2D scenes.

  2. Display these scenes using TCastleViewport. Call TCastleViewport.Setup2D method to set camera properties (projection, position, orientation) to what is usually desired for rendering 2D worlds.

The TCastleTransform can deal with 3D as well as 2D objects, as 2D in our engine is just a special case of 3D. Just use TCastleTransform to transform your 2D scenes, it works perfectly.