Добавление реалистичных поворотов
Следующий шаг - добавить реалистичные изогнутые повороты для наших юнитов, чтобы они не меняли направление внезапно при каждом повороте. Простое решение включает использование сплайна для сглаживания крутых углов в поворотах. Хотя это решает некоторые эстетические проблемы, оно все же приводит к физически очень нереалистичным движениям для большинства юнитов. Например, это может превратить резкий поворот танка в крутой поворот, но изогнутый поворот все равно будет намного более жестким, чем танк может фактически выполнить.
Для лучшего решения, первое, что нам нужно знать, это радиус поворота нашего устройства. Радиус поворота - довольно простая концепция: если вы находитесь на большой парковке в своей машине, и поверните колесо влево до упора и продолжайте движение по кругу, радиус этого круга - ваш поворот радиус. Радиус поворота Volkswagen Beetle будет существенно меньше, чем у большого внедорожника, а радиус поворота человека будет существенно меньше, чем у большого неуклюжего медведя.
Допустим, вы находитесь в некоторой точке (начале координат) и указали в определенном направлении, и вам нужно добраться до какой-либо другой точки (пункта назначения), как показано на рисунке 5. Кратчайший путь можно найти, повернув налево так далеко, как вы может, идти по кругу до тех пор, пока вы не будете прямо направлены на пункт назначения, а затем двигаться вперед или повернуть направо и сделать то же самое.
На рисунке 5 кратчайшим маршрутом является зеленая линия внизу. Этот путь оказывается довольно простым для расчета из-за некоторых геометрических соотношений, показанных на рисунке 6.
Сначала мы вычисляем местоположение точки P, которая является центром нашего круга поворота и всегда находится в радиусе r от начальной точки. Если мы поворачиваем направо от нашего начального направления, это означает, что P находится под углом (initial_direction - 90) от начала координат, поэтому:
angleToP = initial_direction - 90
P.x = Origin.x + r * cos(angleToP)
P.y = Origin.y + r * sin(angleToP)
Теперь, когда мы знаем местоположение центральной точки P, мы можем рассчитать расстояние от P до пункта назначения, обозначенного на диаграмме как h:
dx = Destination.x - P.x
dy = Destination.y - P.y
h = sqrt(dx*dx + dy*dy)
На этом этапе мы также хотим проверить, что пункт назначения не находится внутри круга, потому что если бы он был, мы бы никогда не смогли его достичь:
if (h < r)
return false
Теперь мы можем вычислить длину сегмента d, так как мы уже знаем длины двух других сторон прямоугольного треугольника, а именно h и r. Мы также можем определить угол по соотношению прямоугольник:
d = sqrt(h*h - r*r)
theta = arccos(r / h)
Наконец, чтобы выяснить точку Q, в которой нужно выйти из круга и начать по прямой, нам нужно знать общий угол +, и его легко определить как угол от P до пункта назначения:
phi = arctan(dy / dx) [offset to the correct quadrant]
Q.x = P.x + r * cos(phi + theta)
Q.y = P.y + r * sin(phi + theta)
Вышеуказанные расчеты представляют собой поворот направо. Левый путь может быть рассчитан точно таким же образом, за исключением того, что мы добавляем 90 к initial_direction для вычисления angleToP, а позже мы используем - вместо +. Вычислив оба, мы просто видим, какой путь короче, и используем этот.
В нашей реализации этого и последующих алгоритмов мы используем структуру данных, в которой хранится до четырех отдельных «отрезков», каждый из которых является прямым или изогнутым. Для описанных здесь изогнутых траекторий используются только два сегмента: дуга, за которой следует прямая линия. Структура данных содержит элементы, которые определяют, является ли сегмент дугой или прямой линией, длиной сегмента и его начальной позицией. Если сегмент является прямой линией, структура данных также определяет угол; для дуг он указывает центр круга, начальный угол на окружности и общее количество радиан, охватываемых дугой.
Как только мы вычислили искривленный путь, необходимый для перехода между двумя точками, мы можем легко вычислить нашу позицию и направление в любой данный момент времени, как показано в листинге 2.
ЛИСТИНГ 2. Расчет положения и ориентации в конкретный момент времени.
distance = unit_speed * elapsed_time
loop i = 0 to 3:
if (distance < LineSegment[i].length)
// Unit is somewhere on this line segment
if LineSegment[i] is an arc
//determine current angle on arc (theta) by adding or
//subtracting (distance / r) to the starting angle
//depending on whether turning to the left or right
position.x = LineSegment[i].center.x + r*cos(theta)
position.y = LineSegment[i].center.y + r*sin(theta)
//determine current direction (direction) by adding or
//subtracting 90 to theta, depending on left/right
else
position.x = LineSegment[i].start.x
+ distance * cos(LineSegment[i].line_angle)
position.y = LineSegment[i].start.y
+ distance * sin(LineSegment[i].line_angle)
direction = theta
break out of loop
else
distance = distance - LineSegment[i].length