Движение 4-х колёсного БТРа по 3-х мерной карте

Стратегии старого образца, такие как Starcraft и другие подобные игры, используют в себе псевдотрёхмерность. На самом деле, в этих играх применяется двухмерная графика. Расчёт движения объекта в таком случае простейший:

  X = X0 + V * cos(A);
  Y = Y0 + V * sin(A);

где X0, Y0 - точка положения объекта в текущий момент времени,
    X, Y - расчётное положение, куда будет перемещён объект,
    V - скорость перемещения объекта,
    A - угол, в котором перемещается объект.

В нашем случае, мы используем полностью трёхмерную модель: карта, юниты и камера - всё трёхмерное!

Немного остановимся на карте - это не просто набор треугольников, как в обычном трёхмерном объекте. Проекция карты на плоскоть XY представляет собой "тетрадный листок" (см. рис. 1).


Рис. 1

Проще говоря, на проекции треугольников накладываются следующие условия:

При этом важно учесть, что каждая точка треугольника имеет свою высоту. Вот как выглядит такая карта в трёхмерной проекции (см. рис. 2):


Рис. 2

Наложение данных условий на карту упрошает, а тем самым на много ускоряет расчёты определения высоты расположения объектов на карте. Такую карту можно представить в виде двухмерного массива, содержащего в своих ячейках высоты поверхности карты. Для получения высот по такому массиву опишем функцию CMap3D::GetHeight:

double CMap3D::GetHeight(int x, int y)
{
  ...
}

Как внутри будет реализована функция CMap3D::GetHeight - это дело каждого программиста, у кого-то это будет массив, а у кого-то - динамически выделенная память.

Но даже перемещаясь по такой карте, в игре необходимо, естественно, задавать не индексы массива, а реальные координаты объекта. Напишем для этого функцию определения высоты по координатам CMap3D::GetHeightByXY, предварительно проработав математику. Рассмотрим треугольник, в котором будет определяться высота (см. рис. 3).


Рис. 3

Проведём из точки (X1, Y1) через точку (X0, Y0) луч. Пересечение луча со стороной треугольника (X2, Y2) - (X3, Y3) будет в точке (X4, Y4). Понятно, что X4 будет равно X2 или X3. А вот чтобы рассчитать Y4 составим уравнение нашего луча:

  (X0 - X1) / (X4 - X1) = (Y0 - Y1) / (Y4 - Y1);

получаем:

  Y4 - Y1 = (X4 - X1) * (Y0 - Y1) / (X0 - X1);
  Y4 = (X4 - X1) * (Y0 - Y1) / (X0 - X1) - Y1;

Перейдём в координаты Y-Z и рассмотрим сторону треугольника (Y2, Z2) - (Y3, Z3) (см. рис. 4).


Рис. 4

Точка (Y4, Z4) лежит на стороне треугольника (Y2, Z2) - (Y3, Z3). Сделаем расчёт координаты Z4, также через уравнение прямой:

  (Y4 - Y2) / (Y3 - Y2) = (Z4 - Z2) / (Z3 - Z2);
  Z4 - Z2 = (Y4 - Y2) * (Z3 - Z2) / (Y3 - Y2);
  Z4 = (Y4 - Y2) * (Z3 - Z2) / (Y3 - Y2) + Z2;

Далее, перейдя в координаты X-Z, cпроецируем на них отрезок (X1, Z1) - (X4, Z4) (см. рис. 5).


Рис. 5

Вычисляем Z0:

  (X0 - X1) / (X4 - X1) = (Z0 - Z1) / (Z4 - Z1);
  Z0 - Z1 = (X0 - X1) * (Z4 - Z1) / (X4 - X1);
  Z0 = (X0 - X1) * (Z4 - Z1) / (X4 - X1) + Z1;

Переведём математику в функцию CMap3D::GetHeightByXY:

double CMap3D::GetHeightByXY(double x, double y)
{
  double X0 = (x - MinX) / StepX;
  double Y0 = (y - MinY) / StepY;
  double dX = X0 - floor(X0);
  double dY = Y0 - floor(Y0);
  double x1, y1, x2, y2;

  x1 = floor(X0);
  y1 = floor(Y0);
  x2 = ceil(X0);
  y2 = ceil(Y0);
  if (x2 - x1 < 0.001) x2 = x1 + 1;
  if (y2 - y1 < 0.001) y2 = y1 + 1;

  double X1, Y1, X2, Y2, X3, Y3;
  if (dX >= dY)
  {
    X1 = x1;
    Y1 = Y2 = y1;
    X2 = X3 = x2;
    Y3 = y2;
  }
  else
  {
    X1 = x2;
    Y1 = Y2 = y2;
    X2 = X3 = x1;
    Y3 = y1;
  }
  double Z1 = GetHeight(X1, Y1);
  double Z2 = GetHeight(X2, Y2);
  double Z3 = GetHeight(X3, Y3);
  double X4 = X2;
  if (fabs(X0 - X1) < 0.001) return Z1;
  double Y4 = (X4 - X1) * (Y0 - Y1) / (X0 - X1) + Y1;
  double Z4 = (Y4 - Y2) * (Z3 - Z2) / (Y3 - Y2) + Z2;
  double Z0 = (X0 - X1) * (Z4 - Z1) / (X4 - X1) + Z1;

  return Z0;
}

Теперь можно передвигать по поверхности любую точку. Однако, нам нужно передвигать объект, в данном случае БТР (см. рис. 6), который имеет координаты центра и размеры: длину, ширину и высоту. При этом, необходимо физику движения объекта сделать более-менее реалистичной, а расчёты минимальными.


Рис. 6

Наш БТР имеет 4 колеса, поэтому и рассчёты движения БТРа будут основываться на физическом взаимодействии каждого колеса с поверхностью карты. Для этого, вначале необходимо вычислить точку каждого из четырёх взаимодействий. Опишем для этого функцию CGraphObj3D::CalcPointPos:

CPoint3D CGraphObj3D::CalcPointPos(CPoint3D M)
{
  double A[4] = {M.X, M.Y, M.Z, 1};
  double T[4];
  MUL14x44(T, A, Mat);
  CPoint3D P(T[0], T[1], T[2]);
  return P;
}

Вычисления производяться путём перемножения двух матриц: матрицы точки для локальной системы координат (когда центр объекта находиться в центре системы координат и все три угла поворота объекта вокруг трёх осей равны нулю) и матрицы текущего расположения объекта в пространстве.

Рассмотрим физику одного колеса. БТР находиться в каком-то уравновешенном состоянии: сила толкания пружины колеса равна силе тяжести корпуса БТР, приходящейся на данное колесо. Начинаем движение. При наезде на кочку, колесо перемещается вертикально вверх на расстояние, равное dL (см. рис. 7). При попадании в яму dL будет, естественно, < 0.


Рис. 7

Рассматривая физику движения БТР, для упрощения расчётов предположим, что точки соприкосновения всёх его четырёх колёс находятся на одной плоскости. При наёзде на кочку каждого колеса или попадании в яму, сила реакции пружины начнёт поворачивать корпус, причём каждая в свою сторону. В каждый конкретный момент времени это можно выразить в скоростях поворота вокруг оси OX или OY (см. рис. 8).


Рис. 8

Таким образом, рассчитав высоту расположения БТР (среднее арифметическое точек соприкосновения его колёс) и углы его поворота вокруг осей OX, OY и OZ (поворот вокруг оси OZ - это направление движение БТРа), можно очень даже реалистично отобразить движение нашего БТРа по трёхмерной карте. Для этого напишим функцию CBtrUnit::DoStep:

bool CBtrUnit::DoStep()
{
  CPoint3D P[4];
  P[0] = CalcPointPos(CPoint3D(-RadiusX, RadiusY, -RadiusZ * 1.7));
  P[1] = CalcPointPos(CPoint3D(RadiusX, RadiusY, -RadiusZ * 1.7));
  P[2] = CalcPointPos(CPoint3D(-RadiusX, -RadiusY, -RadiusZ * 1.7));
  P[3] = CalcPointPos(CPoint3D(RadiusX, -RadiusY, -RadiusZ * 1.7));

  double Zbtr = 0;
  for (int i = 0; i < 4; i++)
  {
    double Zgnd = Map3D->GetHeightByXY(P[i].X, P[i].Y);
    double dL = Zgnd - P[i].Z;
    Zbtr += dL;
    double dV = dL * 0.5;
    V[i] = (V[i] + dV) * 0.95;
  }

  double Vx = (V[0] + V[1] - V[2] - V[3]) / 8;
  double Vy = (V[0] + V[2] - V[1] - V[3]) / 8;

  AngleX += Vx;
  AngleY += Vy;

  Pos.Z += Zbtr / 4;
  return true;
}

А что, получилось даже очень не плохо! В этом можно убедиться, скачав текущую версию игры War Strategy на странице Download.

Заканчивая эту тему, хотелось бы заметить, что данный механизм перемещения получился универсальный, позволяет двигать не только БТР, но и любую военную технику, а также легко адаптируется для передвижения людей (углы поворота OX и OY всегда равны 0).

Hosted by uCoz