Визуализация математических функций путем генерации сетки с использованием FireMonkey

By: Anders Ohlsson

Abstract: В статье обсуждается способ генерации трехмерной сетки для визуализации математических функций с использованием Delphi XE2 и FireMonkey

    Предварительные знания

Предполагается, что читатель знаком с основами 3D-графики, включая понятия «сетка» (mesh) и «текстура» (textures).

    Задача

Рассмотрим задачу построения трехмерного графика функции как, например, sin(x*x+z*z)/(x*x+z*z) с использованием цветовой палитры:

Hide image
Click to see full-sized image

    Генерация сетки

Самым простом способом генерации сетки является использование Data.Points и Data.TriangleIndices объекта класса TMesh. Эти два свойства имеют строковой тип, поэтому они подвергаются синтаксическому анализу и разбору («парсингу») для генерации сетки в design-time (или в design-time, если они заданы именно на этом этапе). Этот синтаксический анализ требует определенного времени, что в данном случае приблизительно в 65 медленнее, чем использование внутренних буферов. Поэтому вместо него мы будем использовать не-published свойства Data.VertexBuffer и Data.IndexBuffer.

В нашем примере мы будем выполнять итерации от -30 до +30 по оси Х и в том же интервале по оси Z. Мы будем визуализировать функцию, которая рассчитываем значение Y для каждой точки.

    Шаг 1: Генерация «проволочной рамки»

Картинка ниже демонстрирует достаточно разреженную «проволочную рамку», описывающую поверхность f = exp(sin x + cos z). Красным цветом здесь выделена один из четырехугольников. Каждый четырехугольник разделен на два треугольника для того, что образует необходимую нам сетку. Сетка состоит из всех треугольников, которые мы получаем при итерации по плоскости XZ.

Hide image
Click to see full-sized image

Обозначим углы этого четырёхугольника P0, P1, P2 и P3:

Hide image

Теперь два полученных треугольника можно обозначить как (P1, P2, P3) и (P3, P0, P1).

Выберем произвольное значение u на оси X в выбранном диапазоне, а v – произвольное значение на оси Z. Обозначим d – приращение значений координат на каждом шаге. Следующий код вычисляет четыре искомых точки на плоскости XZ:

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);

Теперь точки полностью определены по трём координатам. Теперь загружаем эти значения в сетку.

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: Генерация текстуры

Для того, чтобы задать сетке определенный цвет, мы создадим текстурный растр, который выглядит следующим образом:

Это – простая модель «Hue, Saturation, Lightness» (HSL), где значение «тона» изменяется от 0 до 359 градусов. «Насыщенность» и «светлота» остаются постоянными.

Для генерации текстуры используется следующий код:

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;

    Демонстрационное приложение

Вы можете скачать моё demo application that graphs 5 different mathematical functions, расположенное на CodeCentral. Здесь представлены некоторые снимки экрана этого приложения:

Hide image
Click to see full-sized image

Hide image
Click to see full-sized image

Hide image
Click to see full-sized image

Hide image
Click to see full-sized image

Hide image
Click to see full-sized image

    Контакты

Если у вас есть вопросы, присылайте их на адрес aohlsson «собачка» embarcadero точка com.

Server Response from: ETNASC04