FireMonkey と C++ でカスタムメッシュを生成することにより、数学関数を視覚化する

By: Chikako Yonezawa

Abstract: この記事では、C++Builder XE2 および FireMonkey を使用して数学関数を視覚化するための3次元メッシュをどのように生成するかを解説します

    前提条件!

この記事では、メッシュ (mesh) 、および、テクスチャなどの 3D グラフィックスの基礎知識があることを前提としています。

    ゴール!

ゴールは、以下の画像のような、鮮やかな色を使用して3次元で、sin(x*x+z*z)/(x*x+z*z) のような関数グラフを作成することです。

Hide image
Click to see full-sized image

    メッシュを生成する

メッシュを生成するための最も速い方法は、TMesh オブジェクトの Data->Point および Data->TriangleIndices を使用することです。しかしながら、それらの2つのプロパティは文字列であり、実行時 (設計時に設定される場合は設計時) にメッシュを生成するために解析されます。この解析は、特に今回のケースでは内部バッファを使用するよりも約65 倍も遅く、実際にはかなり時間がかかります。

そこで、published ではないプロパティ Data->VertexBuffer および Data->IndexBuffer を代わりに使用します。

この例の中では、X 軸を -30 から +30 で走査し、Z 軸に対しても同様に行います。グラフ化する働きとして、それぞれのポイントの Y の値を与えます。

    ステップ1: ワイヤーフレームを生成する

以下の画像では、f = exp(sin x + cos z) の表面を表す低密度のワイヤーフレームを表示しています。

赤く表示しているのは、正方形の 1つです。それぞれの正方形は、メッシュを生成するために 2つの三角形に分割されます。メッシュは単に、XZ 面を走査して得たすべての三角形から単純に構築されています。

Hide image
Click to see full-sized image

正方形の角を P0, P1, P2, P3 と名付けます。

Hide image
Triangles

(P1, P2, P3) と (P3, P0, P1) の 2つの三角形になります。

X 軸上のどこかに u があり、Z 軸上のどこかに v があり、デルタステップとして dを与えるとすると、XZ 面内のこれらの 4つの点を設定するためのコードは次の通りです。

P0.X = u;
P0.Z = v;

P1.X = u+d;
P1.Z = v;

P2.X = u+d;
P2.Z = v+d;

P3.X = u;
P3.Z = v+d;

次に、それぞれの点の Y の構成要素に対応する関数値を計算します。f は関数f(x,z) です。

P0.Y = f(P0.X,P0.Z);
P1.Y = f(P1.X,P1.Z);
P2.Y = f(P2.X,P2.Z);
P3.Y = f(P3.X,P3.Z);

点は、完全にすべて 3次元で定義されています。次にメッシュにそれらを差し込みます。

Mesh1->Data->VertexBuffer->Vertices[0] = P0;
Mesh1->Data->VertexBuffer->Vertices[1] = P1;
Mesh1->Data->VertexBuffer->Vertices[2] = P2;
Mesh1->Data->VertexBuffer->Vertices[3] = P3;

この部分は簡単でした。今度は、点がどの三角形を構成するかをメッシュに指示する必要があります。次のように行います。

// First triangle is (P1,P2,P3)
Mesh1->Data->IndexBuffer->Indices[0] = 1;
Mesh1->Data->IndexBuffer->Indices[1] = 2;
Mesh1->Data->IndexBuffer->Indices[2] = 3;

// Second triangle is (P3,P0,P1)
Mesh1->Data->IndexBuffer->Indices[3] = 3;
Mesh1->Data->IndexBuffer->Indices[4] = 0;
Mesh1->Data->IndexBuffer->Indices[5] = 1;

    ステップ2: テクスチャを生成する

メッシュにいくつかの色を与えるために、このようなテクスチャのビットマップを生成します。

HSLmap

これは、色相が、0 から 359 度の単純な HSL カラーマップです。彩度と明度は固定されています。

このテクスチャを生成するためのコードが以下となります。

BMP = new TBitmap(1,360);
for (k = 0; k < 360; k++)
  BMP->Pixels[0][k] = CorrectColor(HSLtoRGB(k/360.0,0.75,0.5));

    ステップ3: ワイヤーフレームにテクスチャをマッピングする

最後に、メッシュにテクスチャをマッピングする必要があります。これは、TexCoord0 配列を使用して行います。TexCoord0 配列のそれぞれの要素は、四角形 (0,0)-(1,1) 座標システムの中の点です。単なる線であるテクスチャへマッピングしているので、X 座標は常に 0 です。Y 座標は、(0,1) にマップされ、コードは次のようになります。

Mesh1->Data->VertexBuffer->TexCoord0[0] = PointF(0,(P0.Y+35)/45);
Mesh1->Data->VertexBuffer->TexCoord0[1] = PointF(0,(P1.Y+35)/45);
Mesh1->Data->VertexBuffer->TexCoord0[2] = PointF(0,(P2.Y+35)/45);
Mesh1->Data->VertexBuffer->TexCoord0[3] = PointF(0,(P3.Y+35)/45);

    すべてをあわせます

全体のメッシュを生成するための完全なコードを以下に記します。

double f(double x, double z)
{
  double Result;
  double temp = x*x+z*z;
  double Pi = 3.1415926535897932384626433832795;
  double Epsilon = 1E-20;

  if (temp < Epsilon)
    temp = Epsilon;

  Result = -2000*Sin(temp/180*Pi)/temp;

  if (Result < MinF)
    MinF = Result;
  if (Result > MaxF)
    MaxF = Result;

  return Result;
}

void __fastcall TForm3D2::GenerateMesh()
{
  int          MaxX = 30;
  int          MaxZ = 30;
  double  u, v;
  TPoint3D P0, P1, P2, P3;
  double  d = 0.5;
  int          NP = 0;
  int          NI = 0;
  TBitmap  *BMP;
  int          k;

  // We have to set these up front. The buffers are cleared every time Length is set.
  Mesh1->Data->VertexBuffer->Length = 2*MaxX*2*MaxZ/d/d*4;
  Mesh1->Data->IndexBuffer->Length = 2*MaxX*2*MaxZ/d/d*6;

  BMP = new TBitmap(1,360);
  for (k = 0; k < 360; k++)
    BMP->Pixels[0][k] = CorrectColor(HSLtoRGB(k/360.0,0.75,0.5));

  u = -MaxX;
  while (u < MaxX) {
    v = -MaxZ;
    while (v < MaxZ) {
      // Set up the points in the XZ plane
      P0.X = u;
      P0.Z = v;
      P1.X = u+d;
      P1.Z = v;
      P2.X = u+d;
      P2.Z = v+d;
      P3.X = u;
      P3.Z = v+d;

      // Calculate the corresponding function values for Y = f(X,Z)
      P0.Y = f(P0.X,P0.Z);
      P1.Y = f(P1.X,P1.Z);
      P2.Y = f(P2.X,P2.Z);
      P3.Y = f(P3.X,P3.Z);

      // Set the points

      Mesh1->Data->VertexBuffer->Vertices[NP+0] = P0;
      Mesh1->Data->VertexBuffer->Vertices[NP+1] = P1;
      Mesh1->Data->VertexBuffer->Vertices[NP+2] = P2;
      Mesh1->Data->VertexBuffer->Vertices[NP+3] = P3;

      // Map the colors
      Mesh1->Data->VertexBuffer->TexCoord0[NP+0] = PointF(0,(P0.Y+35)/45);
      Mesh1->Data->VertexBuffer->TexCoord0[NP+1] = PointF(0,(P1.Y+35)/45);
      Mesh1->Data->VertexBuffer->TexCoord0[NP+2] = PointF(0,(P2.Y+35)/45);
      Mesh1->Data->VertexBuffer->TexCoord0[NP+3] = PointF(0,(P3.Y+35)/45);

      // Map the triangles
      Mesh1->Data->IndexBuffer->Indices[NI+0] = NP+1;
      Mesh1->Data->IndexBuffer->Indices[NI+1] = NP+2;
      Mesh1->Data->IndexBuffer->Indices[NI+2] = NP+3;
      Mesh1->Data->IndexBuffer->Indices[NI+3] = NP+3;
      Mesh1->Data->IndexBuffer->Indices[NI+4] = NP+0;
      Mesh1->Data->IndexBuffer->Indices[NI+5] = NP+1;

      NP = NP+4;
      NI = NI+6;

      v = v+d;
    }
    u = u+d;
  }

  Mesh1->Data->CalcNormals();
  Mesh1->Material->Texture = BMP;
}

    デモアプリケーション

CodeCentral の「demo application that graphs 5 different mathematical functions 」にデモプロジェクトがあります。ここでは、アプリケーションのいくつかのスクリーンショットをお見せします。

Func1Hide image
Click to see full-sized imageHide image
Click to see full-sized imageHide image
Click to see full-sized imageHide image
Click to see full-sized image

    お問い合わせ

Embarcadero.com で aohlson へフィードバック等、メールでお気軽にご相談ください。

Server Response from: ETNASC04