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

By: Chikako Yonezawa

Abstract: この記事では、Delphi 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つの点を設定するためのコードは次の通りです。

P[0].x := u;
P[0].z := v;

P[1].x := u+d;
P[1].z := v;

P[2].x := u+d;
P[2].z := v+d;

P[3].x := u;
P[3].z := v+d;

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

P[0].y := f(P[0].x,P[0].z);
P[1].y := f(P[1].x,P[1].z);
P[2].y := f(P[2].x,P[2].z);
P[3].y := f(P[3].x,P[3].z);

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

with VertexBuffer do begin
  Vertices[0] := P[0];
  Vertices[1] := P[1];
  Vertices[2] := P[2];
  Vertices[3] := P[3];
end;

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

// First triangle is (P1,P2,P3)
IndexBuffer[0] := 1;
IndexBuffer[1] := 2;
IndexBuffer[2] := 3;

// Second triangle is (P3,P0,P1)
IndexBuffer[3] := 3;
IndexBuffer[4] := 0;
IndexBuffer[5] := 1;

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

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

HSLmap

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

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

BMP := TBitmap.Create(1,360); // This is actually just a line
for k := 0 to 359 do
  BMP.Pixels[0,k] := HSLtoRGB(k/360,0.75,0.5);

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

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

with VertexBuffer do begin
  TexCoord0[0] := PointF(0,(P[0].y+35)/45);
  TexCoord0[1] := PointF(0,(P[1].y+35)/45);
  TexCoord0[2] := PointF(0,(P[2].y+35)/45);
  TexCoord0[3] := PointF(0,(P[3].y+35)/45);
end;

    すべてをあわせます

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

function f(x,z : Double) : Double;
var
  temp : Double;
begin
  temp := x*x+z*z;
  if temp < Epsilon then
    temp := Epsilon;

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

procedure TForm1.GenerateMesh;
const
  MaxX = 30;
  MaxZ = 30;
var
  u, v : Double;
  P : array [0..3] of TPoint3D;
  d : Double;
  NP, NI : Integer;
  BMP : TBitmap;
  k : Integer;
begin
  Mesh1.Data.Clear;

  d := 0.5;

  NP := 0;
  NI := 0;

  Mesh1.Data.VertexBuffer.Length := Round(2*MaxX*2*MaxZ/d/d)*4;
  Mesh1.Data.IndexBuffer.Length := Round(2*MaxX*2*MaxZ/d/d)*6;

  BMP := TBitmap.Create(1,360);
  for k := 0 to 359 do
    BMP.Pixels[0,k] := CorrectColor(HSLtoRGB(k/360,0.75,0.5));

  u := -MaxX;
  while u < MaxX do begin
    v := -MaxZ;
    while v < MaxZ do begin
      // Set up the points in the XZ plane
      P[0].x := u;
      P[0].z := v;
      P[1].x := u+d;
      P[1].z := v;
      P[2].x := u+d;
      P[2].z := v+d;
      P[3].x := u;
      P[3].z := v+d;

      // Calculate the corresponding function values for Y = f(X,Z)
      P[0].y := f(Func,P[0].x,P[0].z);
      P[1].y := f(Func,P[1].x,P[1].z);
      P[2].y := f(Func,P[2].x,P[2].z);
      P[3].y := f(Func,P[3].x,P[3].z);

      with Mesh1.Data do begin
        // Set the points
        with VertexBuffer do begin
          Vertices[NP+0] := P[0];
          Vertices[NP+1] := P[1];
          Vertices[NP+2] := P[2];
          Vertices[NP+3] := P[3];
        end;

        // Map the colors
        with VertexBuffer do begin
          TexCoord0[NP+0] := PointF(0,(P[0].y+35)/45);
          TexCoord0[NP+1] := PointF(0,(P[1].y+35)/45);
          TexCoord0[NP+2] := PointF(0,(P[2].y+35)/45);
          TexCoord0[NP+3] := PointF(0,(P[3].y+35)/45);
        end;

        // Map the triangles
        IndexBuffer[NI+0] := NP+1;
        IndexBuffer[NI+1] := NP+2;
        IndexBuffer[NI+2] := NP+3;
        IndexBuffer[NI+3] := NP+3;
        IndexBuffer[NI+4] := NP+0;
        IndexBuffer[NI+5] := NP+1;
      end;

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

      v := v+d;
    end;
    u := u+d;
  end;

  Mesh1.Material.Texture := BMP;
end;

    デモアプリケーション

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: ETNASC03