Добро пожаловать в удивительный мир неголономного планирования движений. Я рекомендую делать это, используя планировщик пути с решетчатой сеткой . Другие альтернативы включают кинодинамический RRT и оптимизацию траектории . К неголономным системам относятся автомобили, лодки, велосипеды или что-то еще, где транспортное средство не может двигаться в любом направлении, в котором оно хочет. Планирование этих систем намного сложнее, чем голономных, и до 2000 года находилось на грани научных исследований. В настоящее время существует множество алгоритмов на выбор, из которых работают достойно.
Вот как это работает.
государственный
Конфигурация вашего автомобиля q на самом деле является трехмерным состоянием, содержащим положение x, y автомобиля и его ориентацию t . Узлы в вашем алгоритме A * на самом деле являются трехмерными векторами.
class Node
{
// The position and orientation of the car.
float x, y, theta;
}
действия
Так что с краями?
Это немного сложнее, потому что ваш автомобиль может выбрать бесконечное количество способов поворота колеса. Таким образом, мы можем сделать это доступное для решетки сетки планировщика, ограничивая количество действий , автомобиль может принять для дискретного множества, A . Для простоты предположим, что автомобиль не ускоряется, а может мгновенно менять скорость. В нашем случае A может быть следующим:
class Action
{
// The direction of the steering wheel.
float wheelDirection;
// The speed to go at in m/s.
float speed;
// The time that it takes to complete an action in seconds.
float dt;
}
Теперь мы можем создать дискретный набор действий, которые автомобиль может предпринять в любое время. Например, жесткое правое нажатие при полном газе в течение 0,5 секунды будет выглядеть так:
Action turnRight;
turnRight.speed = 1;
turnRight.wheelDirection = 1;
turnRight.dt = 0.5;
Установка автомобиля на задний ход и задний ход будет выглядеть так:
Action reverse;
reverse.speed = -1;
reverse.wheelDirection = 0;
reverse.dt = 0.5;
И ваш список действий будет выглядеть так:
List<Action> actions = { turnRight, turnLeft, goStraight, reverse ...}
Вам также нужен способ определения того, как действие, выполненное на узле, приводит к созданию нового узла. Это называется прямой динамикой системы.
// These forward dynamics are for a dubin's car that can change its
// course instantaneously.
Node forwardIntegrate(Node start, Action action)
{
// the speed of the car in theta, x and y.
float thetaDot = action.wheelDirection * TURNING_RADIUS;
// the discrete timestep in seconds that we integrate at.
float timestep = 0.001;
float x = start.x;
float y = start.y;
float theta = start.theta;
// Discrete Euler integration over the length of the action.
for (float t = 0; t < action.dt; t += timestep)
{
theta += timestep * thetaDot;
float xDot = action.speed * cos(theta);
float yDot = action.speed * sin(theta);
x += timestep * xDot;
y += timestep * yDot;
}
return Node(x, y, theta);
}
Дискретные ячейки сетки
Теперь, чтобы построить решетчатую сетку, все, что нам нужно сделать, это хешировать состояния автомобиля в отдельные ячейки сетки. Это превращает их в отдельные узлы, за которыми может следовать A *. Это очень важно, потому что в противном случае у A * не было бы никакой возможности узнать, являются ли два состояния автомобиля фактически одинаковыми, чтобы сравнить их. Хэширование целочисленных значений ячеек сетки делает это тривиальным.
GridCell hashNode(Node node)
{
GridCell cell;
cell.x = round(node.x / X_RESOLUTION);
cell.y = round(node.y / Y_RESOLUTION);
cell.theta = round(node.theta / THETA_RESOLUTION);
return cell;
}
Теперь мы можем создать план A *, где GridCells - это узлы, Actions - это ребра между узлами, а Start и Goal выражены в терминах GridCells. Эвристика между двумя GridCell - это расстояние в x и y плюс угловое расстояние в тэте.
По пути
Теперь, когда у нас есть путь с точки зрения GridCells и действий между ними, мы можем написать последователь пути для автомобиля. Поскольку ячейки сетки являются дискретными, автомобиль будет прыгать между ячейками. Поэтому нам придется сгладить движение автомобиля по дорожке. Если в вашей игре используется физический движок, это можно сделать, написав контроллер рулевого управления, который пытается удерживать автомобиль как можно ближе к траектории. В противном случае вы можете анимировать путь, используя кривые Безье или просто усредняя ближайшие точки в пути.