Create a 3D Rotating Pumpkin with FireMonkey

Posted by on in Blogs

With FireMonkey (FMX), you can create fully cross-platform GPU-powered graphical user interfaces using different 3D APIs on different platforms.

This post shows FireMonkey 3D programming and building of interactive cross-platform 3D GUIs using Embarcadero’s RAD Studio (C++ Builder or Delphi).

Cross-platform 3D rendering

The foundation of the FireMonkey graphics architecture is abstracting away the underlying 3D API from the developer. The FireMonkey framework comes with pre-built, reusable 3D components that make it easy to write complex 3D applications.

In FireMonkey it is very easy to create sophisticated, GPU-powered 3D user interfaces using reusable visual components that let you focus on your business application logic instead of spending time on writing low-level 3D API code.

There are different 3D APIs available on different operating systems supported by the FireMonkey library. Standard APIs for rendering 3D graphics on mobile targets is a subset version of the OpenGL library called OpenGL ES.

On desktop targets, FireMonkey supports DirectX on Windows and full OpenGL on Mac. All these APIs have different interfaces and abstractions, but the FireMonkey library provides one common denominator for writing cross-platform 3D apps using just one codebase. This figure shows the FMX 3D Graphics Architecture:

FMX_Architecture

The key abstraction in FireMonkey 3D graphics is the abstract TContext3D class that declares virtual drawing methods that are implemented differently on different targets.

This is a similar architecture to FireMonkey 2D rendering, where the abstract TCanvas class has different implementations on all supported operating systems.

Writing 3D apps is considered a domain for professional developers because it involves understanding low-level 3D mathematics and interacting with the Graphical Processor Unit (GPU). The component-based development of Delphi and C++ Builder hides the underlying complexity of 3D coding and you can create highly interactive, visually rich 3D apps with very little to no coding at all!

FireMonkey provides a 3D material system that is based on GPU shader programs. You do not have to be an expert in writing DirectX HLSL or OpenGL shader code to create high-performance 3D graphics applications. In the FireMonkey framework, applying a different shading model is a matter of using a dedicated material component that can just be attached to a 3D object control that represents a specific geometry.

Using Context3D

Similarly to FireMonkey 2D architecture, there are two possible approaches to 3D rendering. We can render in code or use reusable components. The first path is what is used by many other programming languages and development environments. The more complex and sophisticated our 3D visualization, the more complex our 3D rendering code becomes. Using Rapid Application Development (RAD) Delphi or C++ Builder with components very quickly pays off as we typically do not need to write too much code to build a great user experience with interactive 3D worlds.

As I mentioned above, the main interface for calling into 3D APIs in a cross-platform way in FireMonkey is the TContext3D class.

Let's build a project that is going to use the TContext3D class directly in code.

1. Using Delphi or C++ Builder 10.2.3, select Create a new Multi-Device Application (Delphi):

DevelopFMXDelphi

2. Select 3D Application

App3D

The 3D Application Template is a jumping off point for creating a 3D Multi-Device Application based on a 3D Form.

3. Select OK.

4. This creates a blank template form Delphi project that looks like this:

Delphi3DProject

 5. File | Save All.

Create a new folder to save the project called FMX3D, such as C:UsersamannDocumentsEmbarcaderoStudioProjectsFMX3D

6. Save the main form Unit1.pas as uMain.pas

7. Save the project, Project1.dproj, as Cube3D.dproj

8. Change the Caption property of the form to frmCube3D

Right-Click on main form | Quick Edit | change Caption = frmCube3D.

frmCube3D

9. Click check-mark to save changes.

10. Save all.

When we look into the source of the form, uMain.pas, you will see that this time, the main form class is derived from TForm3D and not TForm:

classTForm3D

One of the best things about Delphi and C++ Builder is that you can quickly inspect the source code of classes and types that come with it. 

Right-click on the TForm3D identifier and select Find Declaration. You will jump to the FMX.Forms3D unit where this class is defined.

As we see, the TForm class inherits from TCustomForm, which, in turn, derives from TCommonCustomForm and implements the IScene interface. The key method of this interface is GetCanvas, which returns the TCanvas instance.

This interface, among other members, has a GetContext: TContext3D function that we can use to access the underlying 3D context of the form in code:

function TCustomForm3D.GetContext: TContext3D;
begin
  Result := FContext;
end;

FireMonkey is based on vector graphics, so a RAD Studio, Delphi or C++ Builder programmer can easily scale, rotate, and transform user interface controls without losing graphics accuracy! At the lowest level, we can just use TCanvas methods for writing cross-platform drawing code.

As an alternative and a more straightforward way to access the 3D context is to use the OnRender event of the FMX.Forms3D unit that passes the context as one of its arguments:

OnRender

OnRender Event

Let’s take a quick look at how to use the OnRender event of the form.

Double-click on the OnRender event of the form. 

OnRenderEvent

Typically, we enclose all rendering code with calls to BeginScene and EndScene. Enter the following code into the event handler:

procedure TForm3.Form3DRender(Sender: TObject; Context: TContext3D);
begin
  Context.BeginScene;
  try
    // access context 3D methods and properties here
  finally
    Context.EndScene;
  end;

end;


Right-click on the TContext3D parameter and select the option to Find Declaration. This time, we will jump to FMX.Types3D unit where we can find the TContext3D class. It is defined as abstract because it serves as the interface to the functionality that is implemented by different platform-specific classes inherited from it that override its abstract virtual methods. 

TContext3D = class abstract(TInterfacedPersistent, IFreeNotification)

We see many low-level functions here for drawing different geometries, dealing with states, shaders, materials, and more. This is because 3D APIs, such as OpenGL and DirectX, are relatively complex. 

The Good News with using RAD Studio, Delphi and/or C++ Builder is we do NOT need to write code on such a low level. If we want to do something beyond what FireMonkey 3D classes provide, we can do it here, but most of the time, we will be working with higher level abstractions for doing 3D and that gives us Rapid Application Development!

In the unit FMX.Types3D, just after the TContext3D class declaration in the source code, we see a helper class for it that offers some convenience methods for common drawing operations, like FillCube, DrawLine, DrawCube, DrawRect and FillPolygon.

  TContextClass = class of TContext3D;

  TContextHelper = class helper for TContext3D
  public
    { helper }
    procedure FillCube(const Center, Size: TPoint3D; const Opacity: Single; const Color: TAlphaColor);
    procedure DrawLine(const StartPoint, EndPoint: TPoint3D; const Opacity: Single; const Color: TAlphaColor);
    procedure DrawRect(const TopLeft, BottomRight: TPoint3D; const Opacity: Single; const Color: TAlphaColor);
    procedure DrawCube(const Center, Size: TPoint3D; const Opacity: Single; const Color: TAlphaColor);
    procedure FillPolygon(const Center, Size: TPoint3D; const Rect: TRectF; const Points: TPolygon;
      const Material: TMaterial; const Opacity: Single; Front: Boolean = True; Back: Boolean = True;
      Left: Boolean = True);
  end;

We can use these helper methods to quickly render typical geometries such as lines, rectangles, polygons, or cubes in 3D.

FireMonkey (FMX) Helper Methods

As an example, let’s see how to draw a cube on our form. 

1. Enter this code between the BeginScene and EndScene calls in the OnRender event of the form:

uses
  System.Math.Vectors;

{$R *.fmx}

procedure TForm3.Form3DRender(Sender: TObject; Context: TContext3D);
begin
  Context.BeginScene;
  try
    // access context 3D methods and properties here
    Context.DrawCube(TPoint3D.Create(0, 0, 0), TPoint3D.Create(10, 10, 10), 1,
      TAlphaColorRec.Black);
  finally
    Context.EndScene;
  end;


2. Run the application.

The application should just show an empty form.  This is correct because the OnRender event has not been called. We need a way to fire the OnRender event of our form? 

3. One way to do is to drop a TTimer component on the form and in its OnTimer event, call the Invalidate method of the form to force the form to re-rendering. 

4. Enter the following code for the TTimer event:

procedure TForm3.Timer1Timer(Sender: TObject);
begin
  self.Invalidate;
end;

5. Run the application again, and now you should see our black cube, from using Context.DrawCube, displayed on the form, like this:

 DrawCube

 Very Nice!  This 3D cube was created using code.  But we could also use RAD Studio’s pre-built components to create the same looking 3D Cube without needing to write any code, by following these next steps:

1. Remove all code from the OnRender and OnTimer events and click on Save.

2. Add a TStrokeCube component on the form.

StrokeCube3D

3. Using the Object Inspector, change the TStrokeCube component Color property to Black and its Width, Height, and Depth properties to 10

StrokeCube   StrokeCubeProps

 4. Run the app now, and you should see the same 3D cube, but this time with no code, but with a component that encapsulates the call to the DrawCube method of the 3D context, that looks like this:

CudeNoCode

As we can see, using RAD Studio’s pre-built components for 3D rendering makes you a more productive programmer!

Now that we have a brief overview of using FireMonkey 3D capabilities, lets next look at how to create a FMX 3D application for a spinning 3D Halloween Pumpkin!

Firemonkey 3D Example:

These steps show how to use FireMonkey 3D capabilities to create a spinning 3D Pumpkin application.

 Steps:

1. Create a new Multi-Device Application (C++ or Delphi)

We have these 7 different templates to start with like, Blank, 3D, Header/Footer, Tabbed, etc.

2. Let’s create a new 3D application!  Select the 3D Application template. 

The project gets created and we start with a Blank Form.

With the FireMonkey (FMX) multi-device capabilities, you can create fully cross-platform GPU-powered graphical user interfaces using different 3D APIs on different platforms.

For this Halloween Season, let’s create a UI that shows a spinning 3D Halloween Pumpkin!

3. File | Save Project.  Save and name the project Pumpkin3D.

With FireMonkey it is very easy to create sophisticated, GPU-powered 3D user interfaces using reusable visual components that let you focus on your business application logic instead of spending time on writing low-level 3D API code.

There are different 3D APIs available on different operating systems supported by the FireMonkey library. Standard APIs for rendering 3D graphics on mobile targets is a subset version of the OpenGL library called OpenGL ES.

On desktop targets, FireMonkey supports DirectX on Windows and full OpenGL on Mac. All these APIs have different interfaces and abstractions, but the FireMonkey library provides one common denominator for writing cross-platform 3D apps using just one codebase!

FireMonkey provides a 3D material system that is based on GPU shader programs. You do not have to be an expert in writing DirectX HLSL or OpenGL shader code to create high-performance 3D graphics applications. In the FireMonkey framework, applying a different shading model is a matter of using a dedicated material component that can just be attached to a 3D object control that represents a specific geometry.

Using Rapid Application Development (RAD) with components very quickly pays off as we typically do not need to write too much code to build a great user experience with interactive 3D worlds.

For example, RAD Studio, Delphi and/or C++ BUilder includes all these 3D Shapes we can use as pre-built components:  

4. The next step is to add a TDummy component to the form.

5. Change the TDummy component Name property to DummyScene.

The TDummy component acts as a container for other 3D components and is very useful for applying common transformations to a group of 3D objects. 

The TDummy component does not have any visual representation at runtime. In the FireMonkey 2D world, the TLayout component plays a similar role. In the world of FireMonkey 3D, the concept of parenting is also very important. Most FireMonkey components can own other components, and owned components inherit different properties from their owners including geometrical transformation, such as moving, scaling, and rotations. It is a common pattern to use one TDummy component as a parent for all other visual and non-visual 3D objects. In this way, we have one central point for manipulating the 3D scene as a whole, changing its position, rotation, and so on.

6. Add a TSphere component to the from.  Drop the TSphere component on the form and make sure that it belongs to the root dummy control:

DummySphere

7. Rename the sphere to SpherePumpkin. Let's make the Pumpkin bigger. Select the root dummy component in the Object Inspector.

8. For the DummyScene, expand its Scale property and change all three X, Y, and Z values to 10. Notice that the contained sphere changed its size. In this way, we control the scale, rotation, and position of all 3D objects that make up the scene.

SphereScale10        OIScale

9. Drop a TTextureMaterialSource component on the form and connect it with the sphere using the SphereEarth.MaterialSource property.

OITexture

10. On the TextureMaterialSource component, click on the ellipsis button next to the Texture property and click on Edit.

11. This will show a dialog where we can load a bitmap to be used as texture.

You can find a free Halloween Pumpkin image here:  https://pixabay.com/en/pumpkin-halloween-deco-decoration-786668/

12. Load the Pumpkin image into the IDE BitMap Editor, like this:

PumpKinBitMap

13. Click OK.

Your Pumpkin texture material on your sphere, should look something like this:

PumpkinTexture

The application already looks nice, but let's add some movement and User Touch interaction to this 3D Pumpkin display.

 14. Drop a TFloatAnimation component on the form, and place the FloatAnomation as a child of the SphereEarth component, using the Structure pane, like this:

StructureFloatAni

The TFloatAnimation will be attached to the numerical properties of the SpherePumpkin object to change its values over time.

15. Next, let’s expand the FloatAnimation’s PropertyName property (Dropdown List) and you will see all the float properties that belong to the TSphere class.

16. Select the RotationAngle.Y property, because we want the Pumpkin to rotate around its vertical axis.

RotateY

17. With the FloatAnimation selected in the Object Inspector let’s change some of its properties to animate the Pumpkin.   Set Enabled and Loop to True. Change Duration to 1 second and StopValue to -360. 

 FloatAniEnable_2

18. File | Save All.

19. Run the application. You should see a spinning Pumpkin!  And using Rad Studio's Rapid Application Development, we have not written a single line of code!

SpinningPumpkin

Lastly, let’s add some End User Touch Interaction with the Spinning Pumpkin.  When the end user touches the Pumpkin on the screen, the Pumpkin will either move closer or further to the user in the 3D space.

Using FireMonkey, by default, when we drop 3D objects onto the 3D view, their position is at the beginning of the coordinate system. In FireMonkey, the X-axis increases to the right side of the screen, the Y-axis increases down the screen, and Z goes into the screen. 

20. To add this End User touch functionality, let's change the Position.Z property of the TFloatAnimation in code.  We will change the Position.Z property of the sphere to -1 and notice that the Pumpkin moves closer. If we change the Position.Z property to a positive value, the Pumpkin moves further into the screen.

21. Double-click on the Tsphere's OnClick event. This is a touch event on a mobile target. In order to achieve a better visual effect, we will not be changing the Z position immediately, but with an animation, using this code on the TShpere's OnClick event handler:

void __fastcall TForm3::SpherePumpkinClick(TObject *Sender) {
	if (SpherePumpkin->Position->Z < 0) {
		TAnimator::AnimateFloat(SpherePumpkin, "Position->Z", 1, 0.2);
	}
	else
		TAnimator::AnimateFloat(SpherePumpkin, "Position->Z", -1, 0.2);

}

The above code shows another way of using animations in FireMonkey. We can use either FireMonkey animation components or we can use the different Animate class methods of the TAnimator class and specify the object and its property we want to animate, the target value, and the duration of the animation. This Animate class is declared in the <FMX.Ani.hpp>, but it was already automatically added to the header file, because we added the TFloatAnimation component earlier.

22. Run the application.

If you touch the spinning Pumpkin, it will move either further away or closer to you.

Since this is a Multi-Device application, we can now deploy this FireMonkey 3D Spinning Pumpkin application to an iOS device and an Android device to verify that it works as expected on these targets!

This is the power of cross-platform FireMonkey 3D development! We have quickly created an interactive 3D app with a custom texture with almost no coding that can be natively compiled for all supported mobile and desktop platforms!

For additional information on Creating FireMonkey 3D Applications, see these links:

§  FireMonkey 3D

§  3D Multi-Device Application

§  FireMonkey Dathox Demo (EDN)

§  Importing a 3D Model in a FireMonkey Application

§  FireMonkey Quick Start Guide - Introduction

Samples

§  FireMonkey HD Animation sample

§  FireMonkey 3D Animation sample

§  FireMonkey Gyroscope sample

§  FireMonkey 3D Arrows sample

§  FireMonkey First App3D sample

§  FireMonkey Planets sample



About
Gold User, Rank: 90, Points: 4
Al Mannarino has 25+ years of software development experience, including object-oriented analysis and design (OOAD) and developing and deploying production applications. He is currently a Principal Software Consultant and Evangelist for Embarcadero Technologies. Prior to joining Embarcadero, Al spent three years working with CodeGear, a division of Borland that was acquired by Embarcadero in 2008. He also worked for five years as a lead systems engineer for Borland supporting application lifecycle management, software delivery optimization and developer tools solutions. Prior to Borland, Al served as a systems engineer for companies including Objectivity, Versant, Red Brick Systems, Information Builders, and was an electrical engineer for Grumman Aerospace performing application implementations on complex electrical-mechanical systems. Al has a bachelor's of science degree in electrical engineering from Manhattan College.

Comments

Check out more tips and tricks in this development video: