Benvenuti nel meraviglioso mondo della pianificazione del movimento non olonomo . Consiglio di farlo utilizzando un pianificatore di percorso griglia reticolare . Altre alternative includono la RRT cinodinamica e l' ottimizzazione della traiettoria . I sistemi non olonomici includono automobili, barche, monocicli o qualsiasi cosa in cui il veicolo non possa viaggiare nella direzione desiderata. La pianificazione di questi sistemi è molto più difficile dei sistemi olonomi e fino al 2000 era al limite della ricerca accademica. Al giorno d'oggi ci sono molti algoritmi tra cui scegliere decentemente.
Ecco come funziona.
Stato
La configurazione della tua auto q è in realtà uno stato 3D contenente la posizione x, y dell'auto e il suo orientamento t . I nodi dell'algoritmo A * sono in realtà vettori 3D.
class Node
{
// The position and orientation of the car.
float x, y, theta;
}
Azioni
E i bordi?
È leggermente più difficile, perché la tua auto potrebbe effettivamente scegliere un numero infinito di modi per girare la ruota. Quindi, possiamo renderlo accessibile a un pianificatore di griglia reticolare limitando il numero di azioni che l'auto può compiere su un set discreto, A . Per semplicità, supponiamo che l'auto non acceleri, ma piuttosto possa cambiare istantaneamente la sua velocità. Nel nostro caso, A può essere come segue:
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;
}
Ora, possiamo creare una serie discreta di azioni che l'auto può intraprendere in qualsiasi momento. Ad esempio, un hard right mentre si preme il gas al massimo per 0,5 secondi sarebbe simile a questo:
Action turnRight;
turnRight.speed = 1;
turnRight.wheelDirection = 1;
turnRight.dt = 0.5;
Mettere l'auto in retromarcia e il backup sarebbe simile a questo:
Action reverse;
reverse.speed = -1;
reverse.wheelDirection = 0;
reverse.dt = 0.5;
E il tuo elenco di azioni sarebbe simile al seguente:
List<Action> actions = { turnRight, turnLeft, goStraight, reverse ...}
È inoltre necessario un modo per definire il modo in cui un'azione intrapresa su un nodo risulta in un nuovo nodo. Questo si chiama il dinamica in avanti del sistema.
// 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);
}
Celle a griglia discrete
Ora, per costruire la griglia reticolare, tutto ciò che dobbiamo fare è l' hash degli stati della macchina in celle a griglia discrete. Questo li trasforma in nodi discreti che possono essere seguiti da A *. Questo è estremamente importante perché altrimenti A * non avrebbe modo di sapere se due stati delle auto sono effettivamente gli stessi per confrontarli. Effettuando l'hashing su valori di celle di griglia interi, questo diventa banale.
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;
}
Ora, possiamo fare un piano A * in cui GridCells sono i nodi, Actions sono i bordi tra i nodi e Start e Goal sono espressi in termini di GridCells. L'euristica tra due griglie è la distanza in xey più la distanza angolare in theta.
Seguendo il percorso
Ora che abbiamo un percorso in termini di GridCells e Actions tra di loro, possiamo scrivere un follower di percorso per l'auto. Poiché le celle della griglia sono discrete, l'auto salta tra le celle. Quindi dovremo appianare il movimento della macchina lungo il percorso. Se il tuo gioco utilizza un motore fisico, questo può essere realizzato scrivendo un controller di sterzo che cerca di mantenere l'auto il più vicino possibile al percorso. Altrimenti, puoi animare il percorso usando curve di Bezier o semplicemente facendo la media dei pochi punti più vicini nel percorso.