Bem-vindo ao maravilhoso mundo do planejamento de movimento não-holonômico . Eu recomendo fazer isso usando um planejador de caminho de grade de malha . Outras alternativas incluem o TRS cinodinâmico e a otimização de trajetória . Os sistemas não-holonômicos incluem carros, barcos, monociclos ou realmente qualquer coisa em que o veículo não possa viajar na direção que desejar. O planejamento desses sistemas é muito mais difícil do que os sistemas holonômicos e até 2000 estava à beira da pesquisa acadêmica. Atualmente, existem muitos algoritmos para escolher quais funcionam decentemente.
Aqui está como isso funciona.
Estado
A configuração do seu carro q é realmente um estado 3D contendo a posição x, y do carro e sua orientação t . Os nós no seu algoritmo A * são na verdade vetores 3D.
class Node
{
// The position and orientation of the car.
float x, y, theta;
}
Ações
E as arestas?
Isso é um pouco mais difícil, porque seu carro pode realmente escolher um número infinito de maneiras de girar o volante. Então, nós podemos fazer isso acessível a um planejador grade rede, restringindo o número de acções do carro pode tomar para um conjunto discreto, A . Por uma questão de simplicidade, vamos assumir que o carro não acelera, mas pode mudar sua velocidade instantaneamente. No nosso caso, A pode ser o seguinte:
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;
}
Agora, podemos criar um conjunto discreto de ações que o carro pode executar a qualquer momento. Por exemplo, um disco rígido enquanto pressionava o gás por 0,5 segundos ficaria assim:
Action turnRight;
turnRight.speed = 1;
turnRight.wheelDirection = 1;
turnRight.dt = 0.5;
Colocar o carro em marcha à ré e fazer o backup seria assim:
Action reverse;
reverse.speed = -1;
reverse.wheelDirection = 0;
reverse.dt = 0.5;
E sua lista de ações seria assim:
List<Action> actions = { turnRight, turnLeft, goStraight, reverse ...}
Você também precisa definir como uma ação executada em um nó resulta em um novo nó. Isso é chamado de dinâmica direta do 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);
}
Células de Grade Discreta
Agora, para construir a grade de treliça, tudo o que precisamos fazer é misturar os estados do carro em células de grade discretas. Isso os transforma em nós discretos que podem ser seguidos por A *. Isso é super importante porque, caso contrário, A * não teria como saber se dois estados de carros são realmente os mesmos para compará-los. Ao fazer hash nos valores das células da grade inteira, isso se torna trivial.
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;
}
Agora, podemos fazer um plano A * em que GridCells são os nós, Actions são as arestas entre os nós e Start e Goal são expressos em termos de GridCells. A Heurística entre duas GridCells é a distância em xey mais a distância angular em theta.
Seguindo o caminho
Agora que temos um caminho em termos de GridCells e Actions entre eles, podemos escrever um seguidor de caminho para o carro. Como as células da grade são discretas, o carro pula entre as células. Então teremos que suavizar o movimento do carro ao longo do caminho. Se o seu jogo estiver usando um mecanismo de física, isso pode ser conseguido escrevendo um controlador de direção que tenta manter o carro o mais próximo possível do caminho. Caso contrário, você pode animar o caminho usando curvas de bezier ou simplesmente calculando a média dos poucos pontos mais próximos no caminho.