Dodawanie realistycznych zwrotów
Następnym krokiem jest dodanie realistycznych zakrętów dla naszych jednostek, aby nie wydawały się gwałtownie zmieniać kierunku za każdym razem, gdy muszą się obracać. Proste rozwiązanie polega na użyciu splajnu, aby wygładzić ostre rogi w zakręty. Chociaż rozwiązuje to niektóre problemy estetyczne, nadal powoduje fizycznie bardzo nierealistyczny ruch dla większości jednostek. Na przykład może zmienić nagłe zakręt zbiornika w ciasną zakręt, ale zakręt zakrętu byłby nadal znacznie ciaśniejszy, niż czołg mógłby faktycznie wykonać.
Aby uzyskać lepsze rozwiązanie, pierwszą rzeczą, którą musimy wiedzieć, jest promień skrętu naszego urządzenia. Promień skrętu jest dość prostą koncepcją: jeśli jesteś na dużym parkingu w samochodzie i obróć koło w lewo do oporu i kontynuuj jazdę po okręgu, promień tego koła to twój obrót promień. Promień skrętu Volkswagen Beetle będzie znacznie mniejszy niż w dużym SUV-ie, a promień skrętu osoby będzie znacznie mniejszy niż w przypadku dużego, ociężałego niedźwiedzia.
Powiedzmy, że jesteś w pewnym punkcie (początek) i skierowany w określonym kierunku, i musisz dotrzeć do innego punktu (miejsca docelowego), jak pokazano na rysunku 5. Najkrótszą ścieżkę można znaleźć, skręcając w lewo do samego końca może, idąc w kółko, dopóki nie zostaniesz bezpośrednio wskazany na miejsce docelowe, a następnie przejdź do przodu lub skręcając w prawo i robiąc to samo.
Na rycinie 5 najkrótsza trasa jest wyraźnie zieloną linią na dole. Ścieżka ta okazuje się dość prosta do obliczenia ze względu na pewne zależności geometryczne, zilustrowane na rycinie 6.
Najpierw obliczamy położenie punktu P, który jest środkiem naszego koła zwrotnego i zawsze znajduje się w promieniu r od punktu początkowego. Jeśli skręcamy w prawo z naszego początkowego kierunku, oznacza to, że P znajduje się pod kątem (kierunek_ początkowy - 90) od początku, więc:
angleToP = initial_direction - 90
P.x = Origin.x + r * cos(angleToP)
P.y = Origin.y + r * sin(angleToP)
Teraz, gdy znamy położenie punktu środkowego P, możemy obliczyć odległość od P do miejsca docelowego, pokazaną jako h na schemacie:
dx = Destination.x - P.x
dy = Destination.y - P.y
h = sqrt(dx*dx + dy*dy)
W tym momencie chcemy również sprawdzić, czy cel nie znajduje się w kręgu, ponieważ gdyby tak było, nigdy byśmy go nie osiągnęli:
if (h < r)
return false
Teraz możemy obliczyć długość odcinka d, ponieważ znamy już długości dwóch pozostałych boków prawego trójkąta, mianowicie h i r. Możemy również określić kąt na podstawie relacji prostokąt-trójkąt:
d = sqrt(h*h - r*r)
theta = arccos(r / h)
Na koniec, aby obliczyć punkt Q, w którym należy opuścić okrąg i zacząć od linii prostej, musimy znać całkowity kąt + i można go łatwo określić jako kąt od P do celu:
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)
Powyższe obliczenia przedstawiają ścieżkę skrętu w prawo. Ścieżkę po lewej stronie można obliczyć dokładnie w ten sam sposób, z tym wyjątkiem, że dodajemy 90 do initial_direction do obliczania angleToP, a później używamy - zamiast +. Po obliczeniu obu, po prostu widzimy, która ścieżka jest krótsza i używamy tej.
W naszej implementacji tego algorytmu i kolejnych, wykorzystujemy strukturę danych, która przechowuje do czterech różnych „segmentów linii”, z których każdy jest prosty lub zakrzywiony. Dla opisanych tutaj zakrzywionych ścieżek zastosowano tylko dwa segmenty: łuk, po którym następuje linia prosta. Struktura danych zawiera elementy, które określają, czy segment jest łukiem czy linią prostą, długość segmentu i jego pozycję początkową. Jeśli segment jest linią prostą, struktura danych określa również kąt; dla łuków określa środek okręgu, kąt początkowy na okręgu oraz całkowitą liczbę radianów pokrytych przez łuk.
Po obliczeniu zakrzywionej ścieżki niezbędnej do przejścia między dwoma punktami możemy łatwo obliczyć naszą pozycję i kierunek w dowolnym momencie, jak pokazano na Listingu 2.
WYKAZ 2. Obliczanie pozycji i orientacji w określonym czasie.
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