Como criar caminhos de aparência natural com A * em uma grade?

13

Estive lendo isso: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html

Mas há algumas coisas que eu não entendo, por exemplo, o artigo diz para usar algo parecido com isto para encontrar caminhos com movimento diagonal:

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * max(dx, dy)

Não sei como definir D para obter um caminho de aparência natural, como no artigo, defino D com o menor custo entre quadrados adjacentes, como ele disse, e não sei o que eles querem dizer com as coisas sobre a heurística. ser 4 * D, isso não parece mudar nada.

Esta é a minha função heurística e a função move:

def heuristic(self, node, goal):
    D = 5
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * max(dx, dy)

def move_cost(self, current, node):
   cross = abs(current.x - node.x) == 1 and abs(current.y - node.y) == 1
   return 7 if cross else 5

Resultado:

insira a descrição da imagem aqui

O caminho tranqüilo que queremos que aconteça:

insira a descrição da imagem aqui

O restante do meu código: http://pastebin.com/TL2cEkeX


Atualizar

Esta é a melhor solução que encontrei até agora:

def heuristic(node, start, goal):
    dx1 = node.x - goal.x
    dy1 = node.y - goal.y
    dx2 = start.x - goal.x
    dy2 = start.y - goal.y
    cross = abs(dx1*dy2 - dx2*dy1)

    dx3 = abs(dx1)
    dy3 = abs(dy1)

    return 5 + (cross*0.01) * (dx3+dy3) + (sqrt(2)-2) * min(dx3, dy3)

def move_cost(current, node):
    cross = abs(current.x - node.x) == 1 and abs(current.y - node.y) == 1
    return 7 if cross else 5

Ele produz o caminho desejado a partir da segunda foto, mas não lida com obstáculos muito bem (tende a rastejar nas paredes) e falha em produzir caminhos ideais, às vezes em distâncias maiores.

Quais são alguns ajustes e otimizações que posso aplicar para melhorá-lo?

Entidade anônima
fonte
2
E se você usar a distância cartesiana como sua heurística?
Jimmy
2
Aqui está apenas uma ideia: aumente o custo de passar de um bloco para outro, para cada passo que o agente se move na mesma direção.
precisa saber é o seguinte
@ Jimmy Eu tentei sqrt (pow (goal.x - node.x, 2) + pow (goal.y - node.y, 2)) e, para o meu pequeno exemplo de caminho, na verdade, ele retorna exatamente o mesmo que a imagem na minha pergunta .
Entidade anônima

Respostas:

10

A * fornece o caminho mais curto do gráfico. Ao usar uma grade como gráfico, geralmente existem vários caminhos mais curtos. No seu primeiro diagrama, esse é um dos caminhos mais curtos. Ele coloca todos os movimentos axiais primeiro e todos os movimentos diagonais depois. Mas esse é o mesmo caminho de comprimento, como se você colocasse todas as diagonais em primeiro lugar, ou se misturasse movimentos axiais e diagonais. Todos são equivalentemente curtos, e qual deles A * escolhe depende de como o código é escrito e de como o gráfico é representado.

Eu acho que o que você está querendo é:

  1. Você precisa mover-se na grade, mas deseja misturar as etapas axial e diagonal para que fique melhor. Uma abordagem é escolher um dos outros caminhos igualmente curtos; Continue lendo a página de Heurística para encontrar “desempate”. Outra abordagem é quando você estiver avaliando vizinhos, escolha aleatoriamente qual deles avaliar primeiro, para que nem sempre escolha um antes do outro. Eu não recomendo o uso de distância euclidiana / cartesiano se você deseja mover no grid; é uma incompatibilidade que torna o A * mais lento.
  2. Você não precisa se mover na grade e deseja se mover em linha reta. Uma abordagem é endireitar os caminhos usando “puxar as cordas”. Você está procurando lugares onde o caminho gira e desenhando linhas retas entre esses pontos. Outra abordagem é aplicar isso ao próprio gráfico subjacente. Em vez de encontrar o caminho na grade, encontre os pontos-chave no mapa e depois mova-se ao longo de linhas retas entre esses pontos-chave. Você pode ver um exemplo aqui . Ainda outra abordagem é o algoritmo Theta * .
amitp
fonte
Boa resposta. Atualizei minha pergunta com algumas informações novas. Espero que você possa especificar um pouco sua resposta.
Entidade Anônima
Eu acho que um pouco sobre obstáculos é esperado; há um diagrama na página Heuristics intitulado "menos bonito com obstáculos". As abordagens de desempate não ajudam muito a contornar obstáculos. Uma das outras abordagens (como Theta *) pode ser o que você deseja.
amitp
2

O algoritmo A * permite atribuir diferentes custos às bordas do caminho. Você também pode atribuir custos, dependendo das circunstâncias. Esta é a sua principal ferramenta para moldar os caminhos A * na aparência da maneira que você deseja.

Quando você deseja desencorajar diagonais longas, pode penalizá-las. Adicione um pouquinho de custo para cada vez que o caminho seguir na mesma direção. Quando você fizer isso, o algoritmo tentará distribuir as etapas diagonais automaticamente da maneira mais uniforme possível em todo o caminho. Apenas certifique-se de que esse custo adicional nunca seja mais do que o custo de obter uma vantagem adicional, ou o algoritmo começará a fazer desvios completamente desnecessários apenas para evitar linhas retas.

Uma boa fórmula pode ser:

cost = normal_cost * (1.1 - 0.1 / num_of_steps_in_the_same_direction)

Observe que isso requer que o custo do caminho seja rastreado como valores de ponto flutuante, não como números inteiros.

Philipp
fonte
1

Adaptando A *

Como Philipp disse, você deve adicionar custos quando a direção não mudar por muito tempo. Porém, a função de Philipp pode levar rapidamente a uma soma de custos adicionais, superiores ao custo de atravessar um bloco adicional. Mas sua ideia principal está correta!

Parece fácil adaptar A * para calcular "todos" os caminhos ideais (com o menor comprimento) e, em seguida, selecionar um deles por outra heurística. Mas há um problema. Se você tiver um longo caminho, pode haver muitas soluções com tamanho ideal. Isso faz com que o algoritmo A * demore muito mais para calcular todas essas outras soluções também. Isso ocorre porque a grade. Você não pode andar 80 graus em vez de 90 graus, o que leva a várias soluções abaixo do ideal em vez de uma solução ideal. Para a imaginação, imagine um mapa sem obstáculos. A distância x é 2 e a distância y é 3. Isso significa que todos os caminhos mais curtos têm 2 movimentos diagonais e 1 movimento reto. Existem 3 combinações válidas: SDD, DSD, DDS (onde D = diagonal, S = reta) para esse caminho simples. A verdadeira "diversão" já começa quando você tem caminhos com, por exemplo, 3 movimentos retos e 2 diagonais: SSSDD, SSDSD, SSDDS, SDSSD, SDSDS, SDDSS, DSSSD, DSSDS, DSDSS, DDSSS (10 variações do caminho mais curto, se não perdi nenhum). Eu acho que você deveria ter entendido a idéia ...

Portanto, devemos corrigir isso adaptando a função de custo de uma maneira que menos soluções (ou mesmo apenas uma solução) sejam "ideais".

Adaptando a função de custo

Fazer a adaptação, como Philipp sugere em sua fórmula de exemplo, fornecerá resultados muito melhores, mas ainda há alguns problemas. Ele não distribuirá uniformemente as "partes" mais curtas / mais longas ao longo do caminho, o que significa: as mudanças de direção serão mais frequentes no início do caminho ou vice-versa.

Além disso, um caminho que leva o ator a se virar infinitamente parece subótimo quando observado por um ser humano. Como leva tempo (para mostrar a animação da curva) e, portanto, deve ser mais lenta.

No entanto, em vez de usar flutuadores para os custos, você pode implementar um "custo secundário" ou um critério de classificação secundária. Se os custos primários forem os mesmos, o custo secundário será usado para estimar qual solução deve ser preferida. Isso não fará com que acidentalmente os custos primários (comprimento da rota na medida da grade) aumentem.

SDwarfs
fonte