この記事は、Yılmaz Yörü氏のブログの抄訳です
「データビジュアライゼーション(データの視覚化)」と聞くと難しそうですが、C++Builderでは簡単に作成することができます。 C++は非常に強力なプログラミング言語であり、リッチなグラフィックスを備えた完全にネイティブで超高速なアプリケーション、ゲーム、ユーティリティを開発することができます。OpenGLやDirect3Dのライブラリや、サードパーティ製の3Dエンジンを利用できます。C++Builderでは、独自の3Dオブジェクトを直接作成することができ、実行時にアニメーションさせることも可能です。
C++BuilderのFireMonkeyプロジェクトでは、Viewport3D (TViewportd3D)コンポーネントを利用することで、Plane, Cube, Sphere, Cone, Plane, Ellipse3Dなどの基本的な3Dオブジェクトを表示するのに適しています。これらの3Dオブジェクトの作成については、別の記事「モダンなWindows C++開発で3Dオブジェクトを扱う方法を学ぶ」をご覧ください。また、Model3D(TModel3D)を使用すれば、3Dオブジェクトを簡単にViewport3Dに読み込むことができます。
Table of Contents
データの視覚化とは、何を意味するのでしょうか?
以前 別のブログで、「視覚化とは、便利なものを美しくする技術」と説明しました。私たち人間は、多数の事実や数字は数値ではなく、円グラフ、棒グラフ、線グラフ、散布図などのグラフィックに変換することで、より簡単に理解することができます。 これは、例えば、スプレッドシートの行と列の数字だけの情報よりも、そのデータを視覚的に加工した情報のほうが理解できるのです。
C++で3Dオブジェクトを作成する方法は?
Viewport3Dで使用する3Dオブジェクトを作成するには、TMeshクラスを使用する必要があります。TMeshは、継承元であるTCustomMeshからプロパティセットを全て引き継いで公開しているクラスなので、IDEのオブジェクトインスペクタを使用して、設計画面で新しい3D図形の設計やTMeshが持つ各プロパティを変更してカスタマイズが可能です。例えば、Dataプロパティを使用すると、点、各点の法線およびテクスチャ、生成される三角形の描画順序を指定できます。そして設計された図形は、MaterialSourceプロパティで指定されたマテリアルで塗りつぶされ、マテリアルが指定されていない場合は、赤い色で塗りつぶされます。
TCylinderを使用して3Dデータの視覚化を作成する方法
TCylinderは、3D FireMonkeyフォーム上に配置可能な3D 円柱の形を実装するクラスで、ツール パレットから追加することができるビジュアル オブジェクトです。色を変更したり、円柱にテクスチャを追加するには、MaterialSource プロパティを使用します。 円柱の表面のなめらかさを指定する場合には、SubdivisionsAxes、SubdivisionsCap、SubdivisionsHeight を設定します。
このブログでは、このTCylinderを(円柱状の棒として)を使用し、2Dデータを3D空間に表示します。C++Builderでは、他の3Dオブジェクトも使用できます。例えば、TCubeやTConeを使用して、2Dデータを3Dビューに表示したり、2DノードのX方向とY方向の値を表示することもできます。C++Builderでの3Dオブジェクトの詳細については、別の記事「モダンなWindows C++開発で3Dオブジェクトを扱う方法を学ぶ」をご覧ください。
以下のコード例のように、3Dの円柱を作成することができます。
1 |
TCylinder *cylinder = new TCylinder(Form1->Viewport3D1); |
例えば、以下のコードように、グローバル変数やTForm1のpublic変数として定義することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
TCylinder *cylinder; //---------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { if(cylinder==NULL) = new TCylinder(Form1->Viewport3D1); if(cylinder!=NULL) { cylinder->BeginUpdate(); // update its properties cylinder->EndUpdate(); } } //---------------------------------------------------------------------- void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { if(cylinder!=NULL) cylinder->Free(); // must be removed from the memory at the end } |
また、以下の例のように、このキューブの TCylinderの機能をドラッグして設定することもできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Cylinder1->BeginUpdate(); Cylinder1->Width=10; Cylinder1->Height=10; Cylinder1->Depth=10; Cylinder1->SubDivisionsWidth=10; Cylinder1->Position->X=0; Cylinder1->Position->Y=0; Cylinder1->Position->Z=0; Cylinder1->Rotation->X=0; Cylinder1->Rotation->Y=0; Cylinder1->Rotation->Z=0; Cylinder1->MaterialSource = LightMaterialSource1; //MaterialSources should be defined or dragged on to form Cylinder1->EndUpdate(); |
ランダムな2次元テストデータの生成方法
ここでは、2D形式のデータ、つまりX座標とY座標のそれぞれに値があると仮定します。例として、以下のようなランダムな2Dデータを生成してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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]); } } |
あとは、以下のコード例のように、この2DデータをTMemoコンポーネントに出力するだけです。
1 2 3 4 5 6 7 8 9 10 |
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]) ); } } |
3Dの円筒棒で2Dデータの視覚化を作成するためには?
3D環境や3Dオブジェクトに関するセンサーの計算または取得された値からの3Dデータグリッドがあります。円柱を円筒棒として使用して、この種の3Dデータを視覚化できます。このブログで試すアイデアは、すべてのノードの大きさを表示するために、色のグラデーション(黄色から赤、青へのグラデーション)を使用し、これらの色を3D円柱で表示します。
TViewport3Dを使用して円柱を表示することには、次のようなメリットがあります。ノード形式での視覚化が容易であること、色付けが容易であること、回転やズームが容易であること、そして最も重要な点は、この3DフォームをX YまたはZ方向に簡単にスライスできるので、すべての層を視覚化できることです。逆にデメリットとしては、グリッドが大きくなるとメモリを多く消費し、表示時間やアニメーションが遅くなる可能性があります。
まずは、円柱を使用して2Dデータを作成してみましょう。
- C++Builder FMXマルチデバイスアプリケーションを新規に作成し、プロジェクトファイルとユニットファイルをすべてフォルダに保存します。
- ツールパレットのViewport3Dをフォーム上にドラッグします。これを使って3Dマップの空間を表示します。ツールパレットからダミーオブジェクト(Dummy)、CubeとCamera、Lightを2個、ドラッグしてViewPort3Dに追加します。
それでは、配置したこれらのコンポーネントを設計画面で変更していきましょう。
- Dummy1は、多数の円柱で構成されるリジッドオブジェクトになるため、このDummyオブジェクトとそれに接続されている他のすべての3Dオブジェクトを簡単に移動または回転させることができます。
- Cubeは、設計時ではこのDummy1オブジェクトの中心を見るために使用されますが、これをテスト実行で使用し、実行時に非表示にします。
- 2つのLightコンポーネントのLightTypeプロパティを「Point」に設定し、左上隅と右上隅に配置すると1つのLightがグレーになります。
- Camera1を適切な位置に設定します。(例えば、Positionを z = -20、Cube(Dummy1の原点)が中央に配置される位置など)
- Viewport3Dを選択し、UseDesignCameraプロパティをfalseに設定します。
この2つの例を組み合わせると、半径X、半径Yの円柱と、DX、DY、DZサイズの立方体を、原点移動しながら、指定されたx、y、zに作成する関数を作ることができます。
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_cylinder(int x, int y, int z, int value, TAlphaColor cla) { TCylinder *cylinder=new TCylinder(Form1->Viewport3D1); if(cylinder!=NULL) { cylinder->BeginUpdate(); cylinder->Parent = Form1->Dummy1; cylinder->Width = radiusX; cylinder->Depth = radiusZ; cylinder->Height = value*SCALEY; cylinder->SubdivisionsAxes =18; // higher numbers more smooth but slower display cylinder->Opacity = 1.0; cylinder->Position->X = -0.5*DX*GRIDSX + x*DX + 0.5*DX; cylinder->Position->Y = -0.5*cylinder->Height; // Normaly origin is the center, lets set origin to base cylinder->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; cylinder->MaterialSource = mat; cylinder->HitTest = false; cylinder->EndUpdate(); cylinder->Repaint(); bars[x][y]=cylinder; } } |
データの視覚化にプロフェッショナルな外観を与えるために回転を加える
親のDummy1 を回転させることで、すべての 3Dキューブを回転させることができます。ViewPort3D1を選択し、そのOnMouseDown()、OnMouseMove()、OnMouseUp()イベントをダブルクリックして、以下のコードのように変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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; } } |
そしてフォームを閉じる際には、以下のコード例のように、すべてのキューブマテリアルソースとキューブを解放する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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(); } } } |
フォームのクローズ処理が完了したら、新しいコマンドでメモリに割り当てられたすべての円筒棒(bars)を解放する必要があります。 例えば、以下のコードで行うことができます。
1 2 3 4 5 6 7 8 9 10 11 12 |
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(); } } } |
データの使用後は、必ず解放することが必要
フォームを閉じる際には、以下のコード例のように、すべてのキューブマテリアルソースとキューブを解放する必要があります。
1 2 3 4 |
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { free_2D_data(); } |
最後にButton OnClick()イベントハンドラ(Button1 , Button2)に上記で紹介したコードを全て追加します。
1 2 3 4 5 6 7 |
void __fastcall TForm1::Button1Click(TObject *Sender) { Cube1->Visible=false; generate_2D_data(); print_2D_data(); if(!visualization_generated )generate_3D_visualization(); } |
新しいデータで3Dビューを更新する方法
以下のコードのようにすべての円筒棒の高さを変更することで、データを更新し、ビューを更新することができます。
1 2 3 4 5 6 |
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(); } |
美しくプロフェッショナルな3Dデータの視覚化の例
アプリケーションを実行すると、下図のような結果になりますが、いかがでしょうか?
なかなか壮観ではありませんか?
最後に
このブログで紹介した例を発展させて、平面オブジェクトと、方向とグリッドを示すテクスチャを追加することもできますし、このテクスチャを自然なテクスチャにすることもできます。円筒棒が選択されたときや、マウスが円筒棒の上にあるときに点灯させたり、座標やパーセンテージで値を表示することができます。FireMonkeyにはグラフィックの不透明度(Opacity)を調整する機能があるので、円筒棒を半透明にして透過させたり、ガラスのように少し曇った見せ方をするなど、それぞれの円筒棒に対して異なるテクスチャや色を適用することもできます。
このブログの説明とメソッドが少し長いため難しく見えますが、C++Builderでこれらを試してみると、本当に簡単でシンプルにこの種のグラフィックを作成できることがわかります。 以前 別のブログ記事では、「熱伝導方程式」や「数百万もの未知数の方程式」を、このような3D問題で解く方法を説明しました。
今回のプログの例とメソッドがお役に立てば幸いです。今回のメソッドでは、FireMonkeyのViewport3Dと3D円柱(TCylinder)を使用していますが、もちろんOpenGLや他の3Dエンジン、コンポーネント等を利用して同じように3D図形を表示することができます。
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition