Adicionando voltas realistas
O próximo passo é adicionar curvas realistas às nossas unidades, para que elas não pareçam mudar de direção abruptamente toda vez que precisam girar. Uma solução simples envolve o uso de um spline para suavizar os cantos abruptos em turnos. Enquanto isso resolve algumas das preocupações estéticas, ainda resulta em um movimento fisicamente muito irreal para a maioria das unidades. Por exemplo, isso pode transformar uma curva abrupta de um tanque em uma curva apertada, mas a curva curva ainda seria muito mais apertada do que o tanque poderia realmente executar.
Para uma solução melhor, a primeira coisa que precisamos saber é o raio de viragem da nossa unidade. Raio de viragem é um conceito bastante simples: se você estiver em um grande estacionamento no seu carro e gire a roda para a esquerda o máximo possível e continue dirigindo em círculo, o raio desse círculo será a sua virada. raio. O raio de viragem de um Volkswagen Beetle será substancialmente menor que o de um grande SUV, e o raio de viragem de uma pessoa será substancialmente menor que o de um urso grande e pesado.
Digamos que você esteja em algum ponto (origem) e apontado em uma determinada direção, e precise chegar a outro ponto (destino), conforme ilustrado na Figura 5. O caminho mais curto é encontrado girando à esquerda o máximo possível. pode, andando em círculo até que você esteja diretamente apontado para o destino e, em seguida, prosseguindo, ou girando à direita e fazendo a mesma coisa.
Na Figura 5, a rota mais curta é claramente a linha verde na parte inferior. Esse caminho acaba sendo bastante simples de calcular devido a algumas relações geométricas, ilustradas na Figura 6.
Primeiro calculamos a localização do ponto P, que é o centro do nosso círculo de viragem, e está sempre o raio r longe do ponto inicial. Se estivermos virando à direita em nossa direção inicial, isso significa que P está em um ângulo de (initial_direction - 90) a partir da origem, então:
angleToP = initial_direction - 90
P.x = Origin.x + r * cos(angleToP)
P.y = Origin.y + r * sin(angleToP)
Agora que sabemos a localização do ponto central P, podemos calcular a distância de P ao destino, mostrada como h no diagrama:
dx = Destination.x - P.x
dy = Destination.y - P.y
h = sqrt(dx*dx + dy*dy)
Neste ponto, também queremos verificar se o destino não está dentro do círculo, porque, se estivesse, nunca poderíamos alcançá-lo:
if (h < r)
return false
Agora podemos calcular o comprimento do segmento d, uma vez que já sabemos os comprimentos dos outros dois lados do triângulo retângulo, ou seja, he er. Também podemos determinar o ângulo da relação do triângulo retângulo:
d = sqrt(h*h - r*r)
theta = arccos(r / h)
Finalmente, para descobrir o ponto Q no qual deixar o círculo e começar na linha reta, precisamos conhecer o ângulo total +, e é facilmente determinado como o ângulo de P ao destino:
phi = arctan(dy / dx) [offset to the correct quadrant]
Q.x = P.x + r * cos(phi + theta)
Q.y = P.y + r * sin(phi + theta)
Os cálculos acima representam o caminho de rotação à direita. O caminho da esquerda pode ser calculado exatamente da mesma maneira, exceto que adicionamos 90 à diretiva_inicial para calcular o angleToP e, mais tarde, usamos - em vez de +. Depois de calcular os dois, simplesmente vemos qual caminho é mais curto e o usamos.
Em nossa implementação desse algoritmo e dos seguintes, utilizamos uma estrutura de dados que armazena até quatro "segmentos de linha" distintos, sendo cada um deles reto ou curvado. Para os caminhos curvos descritos aqui, existem apenas dois segmentos usados: um arco seguido por uma linha reta. A estrutura de dados contém membros que especificam se o segmento é um arco ou uma linha reta, o comprimento do segmento e sua posição inicial. Se o segmento for uma linha reta, a estrutura de dados também especificará o ângulo; para arcos, especifica o centro do círculo, o ângulo inicial no círculo e o total de radianos cobertos pelo arco.
Depois de calcular o caminho curvo necessário para obter entre dois pontos, podemos calcular facilmente nossa posição e direção a qualquer instante no tempo, como mostra a Listagem 2.
Listagem 2. Calculando a posição e a orientação em um determinado momento.
distance = unit_speed * elapsed_time
loop i = 0 to 3:
if (distance < LineSegment[i].length)
// Unit is somewhere on this line segment
if LineSegment[i] is an arc
//determine current angle on arc (theta) by adding or
//subtracting (distance / r) to the starting angle
//depending on whether turning to the left or right
position.x = LineSegment[i].center.x + r*cos(theta)
position.y = LineSegment[i].center.y + r*sin(theta)
//determine current direction (direction) by adding or
//subtracting 90 to theta, depending on left/right
else
position.x = LineSegment[i].start.x
+ distance * cos(LineSegment[i].line_angle)
position.y = LineSegment[i].start.y
+ distance * sin(LineSegment[i].line_angle)
direction = theta
break out of loop
else
distance = distance - LineSegment[i].length