In this post, you’ll discover how we can display two-dimensional data? Can we display data in X and Y? In C++, how can we make a 3D rectangular prism? How do we generate random two-dimensional data? How can we display 2D data with rectangular bars? How can we rotate a 3D visualization? How do we zoom in and out of a 3D visualization?
By learning how to do 3D visualization in c++, it will help you to build C++ applications that have data visualizations with the use of low code C++ Software.
Table of Contents
C++ Builder helps you to use the minimum code possible
It’s true, sometimes C++ can be a little..verbose. It’s a great language, but the power of things like classes can sometimes be wrapped up in quite a lot of ‘scaffold code’. The component-based idiom of C++ Builder means that a lot of that additional wordiness is avoided for you since it is encapsulated in components. This is one of the things developers love about Delphi, C++ Builder’s sibling. The fashion trend of the moment has a buzzword that the rest of the computer programming community is latching on to low code.
C++ Builder and RAD Studio Delphi are the original low code kings
Those of us in the know find it not at all surprising that low code is a great advantage, After all, we’ve been doing it for decades now with C++ Builder and Delphi!
Data Visualization with C++ Builder is easy – and it’s super fast!
C++ Builder is a fast programming language that you can use to develop fully native games which operate at the full native speed of the device on which they are running. You can use OpenGL or Direct3D libraries or some other 3rd party 3D Engines. In C++ Builder you can directly create your own 3D objects, you can animate them on runtime. Viewport3D (TViewportd3D) component in C++ Builder FireMonkey projects is good to display many basic 3D Objects like Plane, Cube, Sphere, Cone, Plane, Ellipse3D etc.
Please see this post about Working With 3D In Modern Windows C++ Development for creating these 3D objects. You can also easily load your 3D objects into Viewport3D by using Model3D (TModel3D).
The TMesh class
To create a 3D object to be used in Viewport3D we need to use TMesh classes. TMesh is a custom 3D shape that can be customized by drawing 3D shapes. It is a class publishes a set of properties from its ancestor, TCustomMesh, in order to let you design new 3D shapes at design time from within the IDE, through the Object Inspector. Use the Data property to specify the points, normals and textures for each point, and the order in which the resulting triangles are drawn. The designed shape is filled with the material specified through MaterialSource property. If no material is specified, then the shape is filled with red color.
Using TCube in C++ Builder avoids lots of extra work for you
The TCube is used in Viewport3D component of C++ Builder, it is a class that implements a 3D cube shape, built on a 3D wireframe, that can be placed with Viewport3D component or with a 3D FireMonkey form. We can use these cubes to visualize the magnitude of our data in XYZ Cartesian coordinates with colors.
TCube is a 3D visual object that can be added from the Tool Palette in C++ Builder. To change the color or add texture to the cube, use the MaterialSource property. Set SubdivisionsDepth, SubdivisionsHeight and SubdivisionsWidth to specify how smooth the cube’s surfaces are.
In this post we will use these cubes to display our 3D data (i.e. Temperatures in every nodes of a 3D Object). We can also use other 3D objects in C++ Builder. For example you can use TPlane (TPlane) forms in 3D to display surface distributions in a 3D view. For more details about 3D objects in C++ Builder, please read more about Learn To Quickly Create Specific 3D Objects In Modern C++ Applications For Windows
Setting TCube properties
We can set features of a dragged TCube from the Component Palette, as given example below,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Cube1->BeginUpdate(); Cube1->Width=10; Cube1->Height=10; Cube1->Depth=10; Cube1->SubDivisionsHeight=10; Cube1->SubDivisionsWidth=10; Cube1->Position->X=0; Cube1->Position->Y=0; Cube1->Position->Z=0; Cube1->Rotation->X=0; Cube1->Rotation->Y=0; Cube1->Rotation->Z=0; Cube1->MaterialSource = LightMaterialSource1; //MaterialSources should be defined or dragged on to form Cube1->EndUpdate(); |
We can create a 3D cube
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
TCube *cube; void __fastcall TForm1::Button1Click(TObject *Sender) { if(cube==NULL) { cube=new TCube(Viewport3D1); cube->Parent=Viewport3D1; } } void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { cube->Free(); // must be removed from the memory at the end } |
Now let’s create a new project, generate a random 2D data and let’s visualize this data with rectangular bars in our Viewport3D.
1. Creating a New Project and Setting some Global Variables
Let’s create a new project, generate random 2D data and let’s visualize this data with rectangular bars in our Viewport3D. First,
1. Create a new C++ Builder Console FMX application, save all project and unit files to a folder.
2. Add some defines and some global variables for bars into our example as given below, add or modify code lines as below,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include <fmx.h> #pragma hdrstop #include "Data_Visualization3D_with_Rectangulars_Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.fmx" TForm1 *Form1; #define GRIDSX 10 #define GRIDSY 10 #define DX 0.8 #define DY 0.8 #define BARWIDTH 0.5 #define BARDEPTH 0.5 #define SCALEY 0.05 TCube *bars[GRIDSX][GRIDSY]; float data[GRIDSX][GRIDSY]; float dataMAX,dataMIN; float LX,LY; // Last X and Y position of mouse when pressed float RX,RY,RZ; // Rotation Values of Dummy object in X Y Z axes bool rotation_by_mouse=false; bool visualization_generated=false; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } |
2. Generating Random 2D Data
Let’s assume that we have data in 2D form, that means we have values in each X and Y coordinates. So, as an example, we can generate a random 2D data as below,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void generate_2D_data() { dataMAX = -1000000; dataMIN = 1000000; //let's generate random data randomize(); for(int j=0; j<GRIDSY; j++) for(int i=0; i<GRIDSX; i++) { data[i][j] = rand()%50; dataMAX = Max(dataMAX, data[i][j]); dataMIN = Min(dataMIN, data[i][j]); } } |
Simply we can print all of these generated values of this 2D data to TMemo component as given example below,
1 2 3 4 5 6 7 8 9 10 11 12 |
void print_2D_data() { Form1->Memo1->Lines->Clear(); for(int j=0; j<GRIDSY; j++) for(int i=0; i<GRIDSX; i++) { Form1->Memo1->Lines->Add( IntToStr(i)+"," + IntToStr(j)+" = " + FloatToStr(data[i][j]) ); } } |
3. Visualization of 2D Data with 3D Rectangular Prisms
We have 3D data grid from the calculations or from the obtained values of sensor about a 3D environment or about a 3D object. We can use cubes to visualize these kind of 3D data. Idea here, to display magnitudes in every node we will use color gradient (i.e gradient from yellow to red and to blue ) and we will display these colors with out 3D cubes. Using cubes has some advantages: easy to visualize in nodal form, easy to color, easy to rotate and zoom all, one of the most important part is we can easily slice this 3D form in X Y or Z directions so we can visualize every layer. Disadvantages are bigger grids may take a lot of memory and slower display time and slower animations.
Let’s start to create our 3D grid object with cubes,
2. Drag a Viewport3D from Tool Palette on to Form. We will use this to display our 3D map space. Add a Dummy Object, Small Cube and Camera, 2 Lights to our ViewPort3D by dragging from the Tools Palette. Let’s modify these from design and
– Dummy1 will be our rigid object which will be composed with a lot of cubes, thus we can easily move or rotate this Dummy object and all other 3D objects attached to this.
– Small cube will be used to see center of this Dummy1 object in design, we will make it invisible on run time.
– Set both Light Types to Point and, put Lights to upper left and right corners, one light could be gray.
– Set Camera in a good position for example z=-20, position where the cube (origin of Dummy1) is centered.
– Select Viewport3D, set Use Design Camera property to false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
void create_cube(int x, int y, int z, int value, TAlphaColor cla) { TCube *cube=new TCube(Form1->Viewport3D1); if(cube!=NULL) { cube->BeginUpdate(); cube->Parent = Form1->Dummy1; cube->Width = BARWIDTH; cube->Depth = BARDEPTH; cube->Height = value*SCALEY; cube->Opacity = 1.0; cube->Position->X = -0.5*DX*GRIDSX + x*DX + 0.5*DX; cube->Position->Y = -0.5*cube->Height; // Normaly origin is the center, lets set origin to base cube->Position->Z = -0.5*DX*GRIDSY + y*DY + 0.5*DY; TLightMaterialSource *mat=new TLightMaterialSource(Form1->Dummy1); mat->Shininess=00; mat->Ambient = cla; mat->Emissive=0x0; mat->Specular=0x0; cube->MaterialSource = mat; cube->HitTest = false; cube->EndUpdate(); cube->Repaint(); bars[x][y]=cube; } } //-- |
Rotating a 3D prism in only 10 lines of code!
We can rotate all 3D prisms by rotating their parent Dummy1. Select ViewPort3D1 and double click its OnMouseDown(), OnMouseMove(), OnMouseUp() events in the Object Inspector Panel and modify them as below,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void __fastcall TForm1::Viewport3D1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y) { LX=X; LY=Y; RX = Dummy1->RotationAngle->X; RY = Dummy1->RotationAngle->Y; RZ = Dummy1->RotationAngle->Z; rotation_by_mouse = true; } //--------------------------------------------------------------------------- void __fastcall TForm1::Viewport3D1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y) { rotation_by_mouse = false; } //--------------------------------------------------------------------------- void __fastcall TForm1::Viewport3D1MouseMove(TObject *Sender, TShiftState Shift, float X, float Y) { if(rotation_by_mouse) { Dummy1->RotationAngle->X = RX+ (Y-LY)*0.4; Dummy1->RotationAngle->Y = RY+ (LX-X)*0.4; Dummy1->RotationAngle->Z = RZ- 0.4*((LX-X)-(Y-LY)); } } |
You can also do zoom in and zoom out by changing camera z position. To do this, select Viewport3D, double click to OnMouseWheel() event and change camera position to as below,
1 2 3 4 5 6 |
void __fastcall TForm1::Viewport3D1MouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, bool &Handled) { Camera1->Position->Z += 0.01*WheelDelta; } |
When done we must free all the bars which were allocated in the memory with new command. We can use this procedure when Form is closed or when 3D display is done, as given below,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void free_2D_data() { for(int j=0; j<GRIDSY; j++) for(int i=0; i<GRIDSX; i++) { if(bars[i][j]!=NULL) { if(bars[i][j]->MaterialSource!=NULL) bars[i][j]->MaterialSource->Free(); bars[i][j]->Free(); } } } |
When we close the form we should free all cube material sources and cubes as given example below,
1 2 3 4 5 6 |
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { free_2D_data(); } |
Finally we should add all these above to our Button OnClick() events (Button1 , Button2)
1 2 3 4 5 6 7 8 9 |
void __fastcall TForm1::Button1Click(TObject *Sender) { Cube1->Visible=false; generate_2D_data(); print_2D_data(); if(!visualization_generated )generate_3D_visualization(); } |
We can update data and update the view by changing height of all bars as below,
1 2 3 4 5 6 7 8 |
void __fastcall TForm1::RandomizeUpdateClick(TObject *Sender) { generate_2D_data(); // print_3D_data(); // enable this if you want to see generated new data update_3D_visualization(); } |
Here is the finished 3D data visualization in C++ Builder
Final Words
From now, you can advance this example, by adding a plane object and a texture that shows directions and grids, or this texture can be a natural texture. You can light when a cube is selected or when the mouse is on it, you can also show its value with coordinates and percentage. Firemonkey has Opacity features in graphics so you can make cubes semitransparent to see their behind or to make them like glass. You can also apply different textures and colors to each of them.
It is really easy and simple to do these kinds of graphics.
In this method, we chose to use Viewport3D and 3D cubes. You can do these kinds of graphics with OpenGL or other 3D engines and components. You can also directly draw to the Canvas or to the Canvas of a Bitmap by calculating projections of objects with 3D Rotation Matrix as given example here.
RAD Studio, C++ Builder is a great programming language to calculate these kinds of problems in C++ and is also powerful enough for all your development needs. If you are new to C++ Builder, why not download and try C++ builder today?