netlib.narod.ru< Назад | Оглавление | Далее >

13.5. «Ходьба» по ландшафту

После того, как мы сконструировали ландшафт, хорошо добавить возможность перемещать камеру таким образом, чтобы она имитировала ходьбу по ландшафту. То есть нам надо менять высоту камеры (координату Y) в зависимости от того, в каком месте ландшафта мы находимся. Для этого мы сначала должны определить по координатам X и Z камеры квадрат ландшафта в котором мы находимся. Все это делает функция Terrain::getHeight; в своих параметрах она получает координаты X и Z камеры и возвращает высоту, на которой должна быть расположена камера, чтобы она оказалась над ландшафтом. Давайте рассмотрим реализацию функции.

float Terrain::getHeight(float x, float z)
{
     // Выполняем преобразование перемещения для плоскости XZ,
     // чтобы точка START ландшафта совпала с началом координат.
     x = ((float)_width / 2.0f) + x;
     z = ((float)_depth / 2.0f) - z;

     // Масштабируем сетку таким образом, чтобы размер
     // каждой ее ячейки стал равен 1. Для этого используем
     // коэффициент 1 / cellspacing поскольку
     // cellspacing * 1 / cellspacing = 1.
     x /= (float)_cellSpacing;
     z /= (float)_cellSpacing;

Сперва мы выполняем перемещение в результате которого начальная точка ландшафта будет совпадать с началом координат. Затем мы выполняем операцию масштабирования с коэффициентом равным единице деленной на размер клетки; в результате размер клетки ландшафта будет равен 1. Затем мы переходим к новой системе координат, где положительное направление оси Z направлено «вниз». Конечно, вы не найдете кода меняющего систему координат, просто помните, что ось Z направлена вниз. Все эти этапы показаны на рис. 13.9.


Рис. 13.9. Cетка ландшафта до и после преобразования

Рис. 13.9. Исходная сетка ландшафта и сетка после переноса начальной точки ландшафта в начало координат, масштабирования, делающего размер квадрата равным 1 и смены направления оси Z


Мы видим, что измененная координатная система соответствует порядку элементов матрицы. То есть верхний левый угол — это начало координат, номера столбцов увеличиваются вправо, а номера строк растут вниз. Следовательно, согласно рис 13.9, и помня о том, что размер ячейки равен 1, мы сразу видим что номер строки и столбца для той клетки на которой мы находимся вычисляется так:

float col = ::floorf(x);
float row = ::floorf(z);

Другими словами, номер столбца равен целой части координаты X, а номер строки — целой части координаты Z. Также вспомните, что результатом функции floor(t) является наибольшее целое число, которое меньше или равно t.

Теперь, когда мы знаем в какой ячейке находимся, можно получить высоты ее четырех вершин:

// A   B
// *---*
// | / |
// *---*
// C   D
float A = getHeightmapEntry(row,   col);
float B = getHeightmapEntry(row,   col+1);
float C = getHeightmapEntry(row+1, col);
float D = getHeightmapEntry(row+1, col+1);

Теперь мы знаем в какой ячейке находимся и высоты всех четырех ее вершин. Нам надо найти высоту (координату Y) точки ячейки с указанными координатами X и Z, где находится камера. Это нетривиальная задача, поскольку ячейка может быть наклонена, как показано на рис. 13.10.


Рис. 13.10. Высота ячейки (координата Y) для заданных координат местоположения камеры X и Z

Рис. 13.10. Высота ячейки (координата Y) для заданных координат местоположения камеры X и Z


Чтобы определить высоту мы должны узнать в каком треугольнике ячейки мы находимся. Вспомните, что каждая ячейка визуализируется как два треугольника. Чтобы определить в каком треугольнике мы находимся, мы берем тот квадрат сетки в котором находимся и перемещаем его таким образом, чтобы верхняя левая вершина совпадала с началом координат.

Поскольку переменные row и col определяют местоположение левой верхней вершины той ячейки где мы находимся, необходимо выполнить перемещение на –col по оси X и –row по оси Z. Преобразование координат X и Z выполняется так:

float dx = x - col;
float dz = z - row;

Ячейка после выполнения преобразования показана на рис. 13.11.


Рис. 13.11. Ячейка до и после преобразования, переносящего ее верхнюю левую вершину в начало координат

Рис. 13.11. Ячейка до и после преобразования, переносящего ее верхнюю левую вершину в начало координат


Теперь, если dz < 1.0 – dx мы находимся в «верхнем» треугольнике Δv0v1v2. В ином случае мы находимся в «нижнем» треугольнике Δv0v2v3 (рис. 13.10).

Теперь мы посмотрим как определить высоту, если мы находимся в «верхнем» треугольнике. Для «нижнего» треугольника процесс аналогичен и ниже будет приведен код для обоих вариантов. Чтобы найти высоту когда мы находимся в «верхнем» треугольнике, надо сформировать два вектора u = (cellSpacingB – A, 0) и v = (0, C – A, –cellSpacing), совпадающих со сторонами треугольника и начинающихся в точке, заданной вектором q = (qxAqz), как показано на рис. 13.12(а) Затем мы выполняем линейную интерполяцию вдоль вектора u на dx и вдоль вектора v на dz. Эти интерполяции показаны на рис. 13.12(б). Координата Y вектора (q + dxu + dzv) дает нам высоту для заданных координат X и Z; чтобы увидеть, как это происходит, вспомните геометрическую интерпретацию сложения векторов.


Рис. 13.12. Вычисление высоты в верхнем треугольнике

Рис. 13.12. (а) Вычисляем два вектора, совпадающих со сторонами треугольника. (б) Высота вычисляется путем линейной интерполяции u на dx и v на dz


Обратите внимание, что поскольку нас интересует только значение высоты, мы можем выполнять интерполяцию только для компоненты y и игнорировать остальные компоненты. В этом случае высота определяется по формуле A + dxuy + dzvy.

Итак, вот заключительная часть кода метода Terrian::getHeight:

if(dz < 1.0f - dx) // верхний треугольник ABC
{
     float uy = B - A; // A->B
     float vy = C - A; // A->C

     height = A + d3d::Lerp(0.0f, uy, dx) +
                  d3d::Lerp(0.0f, vy, dz);
}
else // нижний треугольник DCB
{
     float uy = C - D; // D->C
     float vy = B - D; // D->B

     height = D + d3d::Lerp(0.0f, uy, 1.0f - dx) +
                  d3d::Lerp(0.0f, vy, 1.0f - dz);
}
return height;
}

Функция Lerp выполняет линейную интерполяцию вдоль одномерной линии и ее реализация выглядит так:

float d3d::Lerp(float a, float b, float t)
{
     return a - (a*t) + (b*t);
}

netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz