C++ Builder makes it easy to build engineering applications with custom data visualization methods. These kinds of applications are necessary to understand data results easily. You can use OpenGL or Direct3D libraries or similar 3rd party engines to create great-looking 3D visualizations which help users to make sense of complex datasets. With C++ Builder you can directly create your own 3D objects in your own C++ app by using TViewport3D. You can also animate them at runtime. In this post we will explain how to visualize 3D nodal data with colored cubes.
Table of Contents
Why are 3D data visualizations used?
3D data visualization is important an important tool to help understand complex physical and statistical systems such as thermal distributions, fluid properties, pressure changes, and stress analysis of materials.
How to use Viewport3D for 3D data visualizations?
Viewport3D (TViewportd3D) component in C++ Builder FireMonkey projects is a great way 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 examples on creating these 3D objects. You can also easily load your 3D objects into Viewport3D by using Model3D (TModel3D).
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 which 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 the MaterialSource property. If no material is specified, then the shape is filled with a red color.
How to use TCube for 3D data visualizations?
The TCube is used in the 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
How to code 3D data visualizations for a 3D grid object with cubes in C++?
Let’s assume we have 3D data grid, perhaps from some calculations or the obtained values of sensor about a 3D environment or about a 3D object. We can use cubes to visualize these kinds of 3D data. the idea here is to display magnitudes in every node. To do that we will use a color gradient (i.e gradient from yellow to red and to blue). We will display these colors without 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 parts is we can easily slice this 3D form in X Y or Z directions so we can visualize every layer. The disadvantage are bigger grids may take a lot of memory and slower display time and, consequentially, slower animations.
Let’s start to create our 3D grid object with cubes.
1. Create a new C++ Builder Console FMX application, save all project and unit files to a folder. Modify the code lines as shown below.
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.
3. 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.
4. The small cube will be used to see center of this Dummy1 object in design. We will make it invisible at run time.
5. Set both Light Types to Point and, put Lights to upper left and right corners, one light could be gray.
6. Set Camera in a good position for example z=-20, position where the cube (origin of Dummy1) is centered.
7. Select Viewport3D, set Use Design Camera property to false, arrange your camera position.
Let’s start with some global definitions about our cubic grid, and let’s create TCube grid array as below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#define GRIDSX 10 #define GRIDSY 10 #define GRIDSZ 10 #define DX 0.5 #define DY 0.5 #define DZ 0.5 TCube *gridcube[GRIDSX][GRIDSY][GRIDSZ]; float data[GRIDSX][GRIDSY][GRIDSZ]; float dataMAX,dataMIN; int LX,LY; bool rotation=false; |
Now, simple we can create a 3D cube as below:
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 } |
We can set features of this cube 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 generate 3D data as shown below, this procedure also prints results to the Memo1 component.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void generate_data() { dataMAX = -1000000; dataMIN = 1000000; for(int k=0; k<GRIDSZ; k++) for(int j=0; j<GRIDSY; j++) for(int i=0; i<GRIDSX; i++) { data[i][j][k] = 20 - 18*(pow(i-GRIDSX/2,2)+ pow(j-GRIDSY/2,2)+pow(k-GRIDSZ/2,2)); dataMAX = Max(dataMAX, data[i][j][k]); dataMIN = Min(dataMIN, data[i][j][k]); Form1->Memo1->Lines->Add(IntToStr(i)+","+ IntToStr(j)+","+IntToStr(k)+" = "+FloatToStr(data[i][j][k]) ); } } |
If we combine these two examples we can create a function which creates DX,DY and DZ sized cube in a given x, y, z with origin displacement
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 |
void create_cube(int x, int y, int z, TAlphaColor cla) { TCube *cube=new TCube(Form1->Viewport3D1); if(cube!=NULL) { cube->BeginUpdate(); cube->Parent = Form1->Dummy1; cube->Width = DX; cube->Height = DZ; cube->Depth = DY; cube->Opacity = 1.0; cube->Position->X = -0.5*DX*GRIDSX + x*DX + 0.5*DX; cube->Position->Y = -0.5*DX*GRIDSY + y*DY + 0.5*DY; cube->Position->Z = -0.5*DZ*GRIDSZ + z*DZ + 0.5*DZ; 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(); gridcube[x][y][z]=cube; } } |
We can rotate all 3D cubes by rotating their parent Dummy1. Select ViewPort3D1 and double click its OnMouseDown(), OnMouseMove(), OnMouseUp() events 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 |
void __fastcall TForm1::Viewport3D1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y) { LX=X; LY=Y; rotation=true; } //--------------------------------------------------------------------------- void __fastcall TForm1::Viewport3D1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y) { rotation=false; } //--------------------------------------------------------------------------- void __fastcall TForm1::Viewport3D1MouseMove(TObject *Sender, TShiftState Shift, float X, float Y) { if(rotation) { Dummy1->RotationAngle->X=(Y-LY)*0.4; Dummy1->RotationAngle->Y=(LX-X)*0.4; } } |
When we close the form we should free all cube material sources and cubes as given example below,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { for(int k=0; k<GRIDSZ; k++) for(int j=0; j<GRIDSY; j++) for(int i=0; i<GRIDSX; i++) { if(gridcube[i][j][k]!=NULL) { gridcube[i][j][k]->MaterialSource->Free(); gridcube[i][j][k]->Free(); } } } |
Now lets change our Button1 click, we can create data cubes with Button1 click as below,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void __fastcall TForm1::Button1Click(TObject *Sender) { Cube1->Visible=false; generate_data(); TAlphaColorRec acr = 0x00000000; for(int k=0; k<GRIDSZ; k++) for(int j=0; j<GRIDSY/2; j++) for(int i=0; i<GRIDSX; i++) { acr.B=0xFF*abs((data[i][j][k]-dataMIN)/(dataMAX-dataMIN)); acr.R=acr.B; create_cube(i,j,k, acr.Color); } } |
What does the C++ 3D visualization example look like?
When you run you application, if you click the button, it will generate data and display as below,
These kinds of graphics are good to help users understand thermal distributions, fluid properties, pressure changes, stress analysis of materials or any similar 3D data. They can be displayed as above to understand and check the whole data on that system.
Is there a full Example of 3D data visualizations of a 3D Grid object with cubes in C++?
Here is the full C++ Builder – FireMonkey example of creating 3D data visualizations in C++:
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
#include <fmx.h> #pragma hdrstop #include "Data_Visualization3D_with_Cubes_Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.fmx" TForm1 *Form1; #define GRIDSX 10 #define GRIDSY 10 #define GRIDSZ 10 #define DX 0.5 #define DY 0.5 #define DZ 0.5 TCube *gridcube[GRIDSX][GRIDSY][GRIDSZ]; float data[GRIDSX][GRIDSY][GRIDSZ]; float dataMAX,dataMIN; int LX,LY; bool rotation=false; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void create_cube(int x, int y, int z, TAlphaColor cla) { TCube *cube=new TCube(Form1->Viewport3D1); if(cube!=NULL) { cube->BeginUpdate(); cube->Parent = Form1->Dummy1; cube->Width = DX; cube->Height = DZ; cube->Depth = DY; cube->Opacity = 1.0; cube->Position->X = -0.5*DX*GRIDSX + x*DX + 0.5*DX; cube->Position->Y = -0.5*DX*GRIDSY + y*DY + 0.5*DY; cube->Position->Z = -0.5*DZ*GRIDSZ + z*DZ + 0.5*DZ; 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(); gridcube[x][y][z]=cube; } } //--------------------------------------------------------------------------- void generate_data() { dataMAX = -1000000; dataMIN = 1000000; for(int k=0; k<GRIDSZ; k++) for(int j=0; j<GRIDSY; j++) for(int i=0; i<GRIDSX; i++) { data[i][j][k] = 20 - 18*(pow(i-GRIDSX/2,2)+ pow(j-GRIDSY/2,2)+pow(k-GRIDSZ/2,2)); dataMAX = Max(dataMAX, data[i][j][k]); dataMIN = Min(dataMIN, data[i][j][k]); Form1->Memo1->Lines->Add(IntToStr(i)+","+ IntToStr(j)+","+IntToStr(k)+" = "+FloatToStr(data[i][j][k]) ); } } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Cube1->Visible=false; generate_data(); TAlphaColorRec acr = 0x00000000; for(int k=0; k<GRIDSZ; k++) for(int j=0; j<GRIDSY/2; j++) for(int i=0; i<GRIDSX; i++) { acr.B=0xFF*abs((data[i][j][k]-dataMIN)/(dataMAX-dataMIN)); acr.R=acr.B; create_cube(i,j,k, acr.Color); /* Sleep(100); Application->ProcessMessages(); Form1->Viewport3D1->Repaint(); */ } } //--------------------------------------------------------------------------- void __fastcall TForm1::Viewport3D1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y) { LX=X; LY=Y; rotation=true; } //--------------------------------------------------------------------------- void __fastcall TForm1::Viewport3D1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y) { rotation=false; } //--------------------------------------------------------------------------- void __fastcall TForm1::Viewport3D1MouseMove(TObject *Sender, TShiftState Shift, float X, float Y) { if(rotation) { Dummy1->RotationAngle->X=(Y-LY)*0.4; Dummy1->RotationAngle->Y=(LX-X)*0.4; } } //--------------------------------------------------------------------------- void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { for(int k=0; k<GRIDSZ; k++) for(int j=0; j<GRIDSY; j++) for(int i=0; i<GRIDSX; i++) { if(gridcube[i][j][k]!=NULL) { if(gridcube[i][j][k]->MaterialSource!=NULL) gridcube[i][j][k]->MaterialSource->Free(); gridcube[i][j][k]->Free(); } } } |
Where can I find other C++ examples of 3D data visualizations?
In our previous post we explained how we can solve heat transfer equations or other 3D equations on those kind of 3D problems. These kinds of methods can be combined together for more professional applications and analyses.
Do you want to learn how to visualize topographic 3D data? How can we view a 3D function? Can we display specific functions in 3D like activation functions in AI? How can we create a 3D map in C++? Topographic Data are data sets about the elevation of a surface, generally the surface of the Earth. C++ Builder makes it very easy to build these kinds of simple visualizations. Here is the post about it:
C++ Builder is the easiest and fastest C and C++ IDE for building simple or professional applications on the Windows, MacOS, and iOS operating systems. It is also easy for beginners to learn with its wide range of samples, tutorials, help files, and LSP support for code. RAD Studio’s C++ Builder version comes with the award-winning VCL framework for high-performance native Windows apps and the powerful FireMonkey (FMX) framework for cross-platform UIs.
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition