Ласкаво просимо у чудовий світ нехолономічного планування руху. Я рекомендую зробити це за допомогою гратичного планувальника сітки . Інші альтернативи включають кінодинамічну RRT та оптимізацію траєкторії . Неголономічні системи включають автомобілі, човни, одноколісні велосипеди, і справді будь-що там, де транспортний засіб не може подорожувати в будь-якому напрямку, якого він хоче. Планування цих систем набагато складніше, ніж голономічні системи і до ~ 2000 року опинилося на межі академічних досліджень. Сьогодні існує маса алгоритмів, з яких можна вибрати пристойно.
Ось як це працює.
Держава
Конфігурація вашого автомобіля q - це фактично тривимірний стан, що містить положення x, y автомобіля та його орієнтацію t . Вузли в алгоритмі A * - це фактично 3D-вектори.
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 - це вузли, Дії - це краї між вузлами, а Пуск і Мета виражаються в термінах GridCells. Евристичний між двома GridCells - це відстань у x та y плюс кутове відстань у теті.
Слідом за Шляхом
Тепер, коли у нас є шлях з точки зору GridCells та дій між ними, ми можемо написати послідовник шляху для автомобіля. Оскільки осередки сітки дискретні, автомобіль стрибне між клітинами. Тож нам доведеться згладити рух машини по стежці. Якщо у вашій грі використовується фізичний двигун, це можна досягти, написавши рульовий контролер, який намагається тримати машину якомога ближче до шляху. В іншому випадку ви можете анімувати шлях за допомогою кривих Безьє або просто шляхом усереднення кількох найближчих точок шляху.