您想学习如何可视化地形数据吗?我们如何查看 3D 功能?我们可以像 AI 中的激活函数一样在 3D 中显示特定功能吗?我们如何在 C++ 中创建 3D 地图?
地形数据是有关表面(通常是地球表面)高程的数据集。通常使用两种这样的数据类型,第一种是数字地形数据,表示通常在地形四边形地图上找到的信息,第二种是数字地形模型 (DEM)由数据网格组成。第一种数据模型,通常在四边形地形图上找到,例如等高线、道路、河流、铁路、城镇等。为简单起见,第一类数据将被称为数字地形图数据。第二种是数字高程模型或 DEM,由数据网格组成,网格中的每个单元格代表地球上某个点的高程。
C++ Builder 很容易构建这些简单的可视化。C++ 是一种快速编程语言,您可以开发完全本机的应用程序,这些应用程序以机器的全速运行。您可以使用 OpenGL 或 Direct3D 库或其他一些 3rd 方 3D 引擎。在 C++ Builder 中, 您可以直接创建自己的 3D 对象,您可以在运行时为它们设置动画。 C++ Builder FireMonkey 项目中的Viewport3D ( TViewportd3D ) 组件可以很好地显示许多基本的 3D 对象,如平面、立方体、球体、圆锥、平面、Ellipse3D 等。请参阅这篇关于 在现代 Windows C++ 开发中使用 3D 的文章以 创建这些 3D 对象. 您还可以使用Model3D轻松地将 3D 对象加载到 Viewport3D ( TModel3D )。
要创建要在Viewport3D 中使用的 3D 对象, 我们需要使用 TMesh 类。 TMesh是一种自定义的 3D 形状,可以通过绘制 3D 形状进行自定义。它是一个从其祖先TCustomMesh发布一组属性的类 ,以便让您在设计时从 IDE 中通过Object Inspector设计新的 3D 形状 。使用 Data 属性指定每个点的点、法线和纹理,以及绘制生成的三角形的顺序。设计的形状填充有通过MaterialSource 属性指定的 材料。如果未指定材质,则形状填充为红色。
视口3D
Viewport3D ( TViewport3D ) 是一个 3D 组件,用于在其空间中显示 3D 对象。 TViewport3D 实现了 IViewport3D 方法来描述如何看到 3D 对象。开发一些小型 3D 游戏或在您的应用程序中添加一些 3D 功能真的很简单也很好。Viewport3D 是一个组件,用于显示相机的视口或默认视图。您可以在表单的任何位置安排它的位置、宽度和高度,也可以在客户端视图中使用它来进行全屏显示。我们可以在没有 3D 相机的情况下使用 viewport3D,它使用自己设计的相机视图。如果您想使用相机并希望从该相机的视图中查看视图,则必须从其属性或如下所示将其UseDesignCamera属性设置为 false;
Viewport3D1->UseDesignCamera=false;
您可以使用BeginUpdate()、EndUpdate()方法更新 Viewport3D 中的任何对象,如下所示;
Viewport3D1->BeginUpdate();
// Move or set Objects, Cameras, Lights here
Viewport3D1->EndUpdate();
这将一次性更新 Viewport3D 的完整 3D 矩阵。您还可以像这样更改此 Viewport3D 组件的背景颜色;
Viewport3D1->Color=claBlack;
TPlane 是做什么的?
该 TPlane 在的Viewport3D组件中使用,它是一种类实现的2D平面,可以放置一个3D FireMonkey形式,它表示可以在3D形式使用2D平面。平面支持 3D 旋转和对齐。TPlane 是一个可视对象,可以从Tool Palette添加 。
Plane1->BeginUpdate();
Plane1->Width=10;
Plane1->Height=10;
Plane1->SubDivisionsHeight=10;
Plane1->SubDivisionsWidth=10;
Plane1->Position->X=0;
Plane1->Position->Y=0;
Plane1->Position->Z=0;
Plane1->Rotation->X=0;
Plane1->Rotation->Y=0;
Plane1->Rotation->Z=0;
Plane1->MaterialSource =TextureMaterialSource1; //MaterialSources should be defined or dragged
Plane1->EndUpdate();
我们可以使用TPlane来显示人脸的各个部分。但是使用 TMesh 设置 4 个角节点要简单得多。
TMesh 如何帮助我们进行可视化?
使用 TMesh 显示 3D 地形数据。使用 TPlane 及其 4 个坐标可以显示 3D 面、3D 函数、3D 地面形状和任何其他数据。
TMesh 表示可以自定义的 3D 形状。该TMesh类出版了一套从它的祖先,性能 TCustomMesh,为了让您在IDE中设计在设计时新的3D图形,通过 对象观察。
使用 Data 属性指定每个点的点、法线和纹理,以及绘制生成的三角形的顺序。设计的形状填充有通过MaterialSource 属性指定的 材料。如果未指定材质,则形状填充为红色。
TMesh *mesh=new TMesh(Form1->Dummy1);
mesh->Parent=Form1->Dummy1;
// ....
// when done free from the memory
mesh->Free();
如何在 C++ 中生成地形数据
在我们的示例中,我们将使用 TMesh 来显示表面,我们将使用数字高程模型 (DEM)。我们将使用 x 和 y 坐标及其 z 高程创建我们自己的数据。让我们先定义我们的 100×100 网格数据和一个 TMesh 对象。
#define GRIDSX 100
#define GRIDSY 100
double data[GRIDSX][GRIDSY];
TMesh *mesh;
现在让我们生成一个数据。我们可以生成如下所示的随机数据,或者我们可以使用如下所示的 3D 函数。
void generate_topographic_data()
{
srand(time(0));
float ev= 1.0;
for(int j=0; j<GRIDSY; j++)
for(int i=0; i<GRIDSX; i++)
{
data[i][j] = 0.005*(pow(i-GRIDSX*0.5,2)+pow(j-GRIDSY*0.5,2));
}
/* for(int j=0; j<GRIDSY; j++)
for(int i=0; i<GRIDSX; i++)
{
if(rand()%5==1) data[i][j]+= (50-rand()%100)/100.0;
}
*/
}
从数据生成地形 3D 网格
我们可以从网格数据中生成 4 个点的网格的每个平面,每个点都有 x、y、z,我们可以缩放它以获得更好的可视化。下面看看它是如何工作的,
void generate_topographic_mesh()
{
TPoint3D P0,P1,P2,P3;
int NP=0, NI=0;
float scale =2, scaleZ=2.0;
float OX = GRIDSX*scale/2;
float OY = GRIDSY*scale/2;
if(mesh!=NULL) mesh->Free();
mesh=new TMesh(Form1->Dummy1);
mesh->Parent=Form1->Dummy1;
mesh->Width=1;
mesh->Height=1;
mesh->Depth=1;
mesh->Position->X=0;
mesh->Position->Y=0;
mesh->Position->Z=0;
mesh->Scale->X=1;
mesh->Scale->Y=1;
mesh->Scale->Z=1;
mesh->RotationAngle->X=0;
mesh->RotationAngle->Y=0;
mesh->RotationAngle->Z=0;
mesh->RotationCenter->X=0;
mesh->RotationCenter->Y=0;
mesh->RotationCenter->Z=0;
mesh->WrapMode=TMeshWrapMode::Original;
mesh->TwoSide=true;
mesh->Visible=true;
mesh->Opacity=1.0;
mesh->Data->Clear();
mesh->Data->VertexBuffer->Length=4*GRIDSX*GRIDSY;
mesh->Data->IndexBuffer->Length= 6*GRIDSY*GRIDSY;
for(int j=0; j<GRIDSY-1; j++)
for(int i=0; i<GRIDSX-1; i++)
{
P0.X = i*scale-OX;
P0.Y = data[i][j]*scaleZ;
P0.Z = j*scale-OY;
P1.X = (i+1)*scale-OX;
P1.Y = data[i+1][j]*scaleZ;
P1.Z = j*scale-OY;
P2.X = (i+1)*scale-OX;
P2.Y = data[i+1][j+1]*scaleZ;
P2.Z = (j+1)*scale-OY;
P3.X = i*scale-OX;
P3.Y = data[i][j+1]*scaleZ;
P3.Z = (j+1)*scale-OY;
mesh->Data->VertexBuffer->Vertices[NP+0] = P0;
mesh->Data->VertexBuffer->Vertices[NP+1] = P1;
mesh->Data->VertexBuffer->Vertices[NP+2] = P2;
mesh->Data->VertexBuffer->Vertices[NP+3] = P3;
mesh->Data->VertexBuffer->TexCoord0[NP+0] =PointF(0, (P0.Y+35)/45);
mesh->Data->VertexBuffer->TexCoord0[NP+1] =PointF(0, (P0.Y+35)/45);
mesh->Data->VertexBuffer->TexCoord0[NP+2] =PointF(0, (P0.Y+35)/45);
mesh->Data->VertexBuffer->TexCoord0[NP+3] =PointF(0, (P0.Y+35)/45);
//mesh->Data->Normals=".....";
mesh->Data->IndexBuffer->Indices[NI+0] =NP+0;
mesh->Data->IndexBuffer->Indices[NI+1] =NP+2;
mesh->Data->IndexBuffer->Indices[NI+2] =NP+1;
mesh->Data->IndexBuffer->Indices[NI+3] =NP+0;
mesh->Data->IndexBuffer->Indices[NI+4] =NP+3;
mesh->Data->IndexBuffer->Indices[NI+5] =NP+2;
NP+=4;
NI+=6;
}
mesh->MaterialSource=Form1->LightMaterialSource1;
mesh->Data->CalcFaceNormals(true);
mesh->Repaint();
}
地形 3D 网格的完整示例
这是完全最大化的 C++ Builder FMX 示例的屏幕截图。这里我们有 100×100 的网格和一个功能高度。
如果我们使用上面的generate_topographic_data()的remmed行随机生成数据,我们可以看到这一点。
下面列出了这个例子的全部代码,
//---------------------------------------------------------------------------
#include <fmx.h>
#include <time.h>
#pragma hdrstop
#include "DataVisualization3D_TopographicData_Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm1 *Form1;
#define GRIDSX 100
#define GRIDSY 100
double data[100][100];
TMesh *mesh;
float LX,LY;
bool rotation=false;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void generate_topographic_data()
{
srand(time(0));
float ev= 1.0;
for(int j=0; j<GRIDSY; j++)
for(int i=0; i<GRIDSX; i++)
{
data[i][j] = 0.005*(pow(i-GRIDSX*0.5,2)+pow(j-GRIDSY*0.5,2));
}
//for(int ev=0; ev<5; ev++)
/* for(int j=0; j<GRIDSY; j++)
for(int i=0; i<GRIDSX; i++)
{
if(rand()%5==1) data[i][j]+= (50-rand()%100)/100.0;
}
*/
}
//---------------------------------------------------------------------------
void generate_topographic_mesh()
{
TPoint3D P0,P1,P2,P3;
int NP=0, NI=0;
float scale =2, scaleZ=2.0;
float OX = GRIDSX*scale/2;
float OY = GRIDSY*scale/2;
if(mesh!=NULL) mesh->Free();
mesh=new TMesh(Form1->Dummy1);
mesh->Parent=Form1->Dummy1;
mesh->Width=1;
mesh->Height=1;
mesh->Depth=1;
mesh->Position->X=0;
mesh->Position->Y=0;
mesh->Position->Z=0;
mesh->Scale->X=1;
mesh->Scale->Y=1;
mesh->Scale->Z=1;
mesh->RotationAngle->X=0;
mesh->RotationAngle->Y=0;
mesh->RotationAngle->Z=0;
mesh->RotationCenter->X=0;
mesh->RotationCenter->Y=0;
mesh->RotationCenter->Z=0;
mesh->WrapMode=TMeshWrapMode::Original;
mesh->TwoSide=true;
mesh->Visible=true;
mesh->Opacity=1.0;
mesh->Data->Clear();
mesh->Data->VertexBuffer->Length=4*GRIDSX*GRIDSY;
mesh->Data->IndexBuffer->Length= 6*GRIDSY*GRIDSY;
for(int j=0; j<GRIDSY-1; j++)
for(int i=0; i<GRIDSX-1; i++)
{
P0.X = i*scale-OX;
P0.Y = data[i][j]*scaleZ;
P0.Z = j*scale-OY;
P1.X = (i+1)*scale-OX;
P1.Y = data[i+1][j]*scaleZ;
P1.Z = j*scale-OY;
P2.X = (i+1)*scale-OX;
P2.Y = data[i+1][j+1]*scaleZ;
P2.Z = (j+1)*scale-OY;
P3.X = i*scale-OX;
P3.Y = data[i][j+1]*scaleZ;
P3.Z = (j+1)*scale-OY;
mesh->Data->VertexBuffer->Vertices[NP+0] = P0;
mesh->Data->VertexBuffer->Vertices[NP+1] = P1;
mesh->Data->VertexBuffer->Vertices[NP+2] = P2;
mesh->Data->VertexBuffer->Vertices[NP+3] = P3;
mesh->Data->VertexBuffer->TexCoord0[NP+0] =PointF(0, (P0.Y+35)/45);
mesh->Data->VertexBuffer->TexCoord0[NP+1] =PointF(0, (P0.Y+35)/45);
mesh->Data->VertexBuffer->TexCoord0[NP+2] =PointF(0, (P0.Y+35)/45);
mesh->Data->VertexBuffer->TexCoord0[NP+3] =PointF(0, (P0.Y+35)/45);
//mesh->Data->Normals=".....";
mesh->Data->IndexBuffer->Indices[NI+0] =NP+0;
mesh->Data->IndexBuffer->Indices[NI+1] =NP+2;
mesh->Data->IndexBuffer->Indices[NI+2] =NP+1;
mesh->Data->IndexBuffer->Indices[NI+3] =NP+0;
mesh->Data->IndexBuffer->Indices[NI+4] =NP+3;
mesh->Data->IndexBuffer->Indices[NI+5] =NP+2;
NP+=4;
NI+=6;
}
mesh->MaterialSource=Form1->LightMaterialSource1;
mesh->Data->CalcFaceNormals(true);
mesh->Repaint();
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
generate_topographic_data();
generate_topographic_mesh();
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;
}
}
//---------------------------------------------------------------------------
正如这里给出的,您可以生成自己的 3D 地形,您可以在此网格上添加纹理,颈椎枕还可以为河流和山脉等创建不同的纹理网格。您可以创建颜色纹理,您可以用颜色显示高程。您可以使用此方法来显示您的函数,即人工智能中的激活函数。您可以开发具有随机或精心设计的地形图的 3D 游戏,如上例所示。请注意,互联网上有很多地形数据。例如,您可以从https://opentopography.org/获取此类数据
C++ Builder真的很棒,您的梦想没有限制!只是梦想和编码!
Clang:下载 C++Builder 并用更少的代码以 10 倍的速度构建 Windows C++ 应用程序
GCC:安装 Embarcadero Dev-C++,这是一个低内存 Windows Native C++ IDE