Jak obliczyć kąt i właściwy kierunek skrętu między dwoma wektorami 2D?

26

Pracuję nad sztuczną inteligencją ruchu, w której nie ma przeszkód, a ruch jest ograniczony do płaszczyzny XY. Obliczam dwa wektory, v , kierunek skierowania statku 1 oraz w , wektor wskazujący od pozycji statku 1 do statku 2.

Następnie obliczam kąt między tymi dwoma wektorami za pomocą wzoru

arccos((v · w) / (|v| · |w|))

Problem, który mam, polega na tym, że arccoszwraca tylko wartości od 0 ° do 180 °. To uniemożliwia ustalenie, czy powinienem skręcić w lewo, czy w prawo, aby stawić czoła drugiemu statkowi.

Czy jest na to lepszy sposób?

Błąd 454
źródło
1
Jeśli używasz Unity, sprawdź Mathf.DeltaAngle().
Russell Borogove

Odpowiedzi:

22

O wiele szybsze jest stosowanie produktów krzyżowych 2d. Nie wymaga kosztownej funkcji wyzwalania.

b2Vec2 target( ... );
b2Vec2 heading( ... );

float cross = b2Cross( target, heading );

if( cross == -0.0f )
   // turn around

if( cross == 0.0f )
  // already traveling the right direction

if( cross < 0.0f)
  // turn left

if( cross > 0.0f)
  // turn right

Jeśli nadal potrzebujesz rzeczywistych kątów, polecam użyć atan2. atan2da ci kąt bezwzględny dowolnego wektora. Aby uzyskać względny kąt między dowolnym wektorem, oblicz ich kąty bezwzględne i użyj prostego odejmowania.

b2Vec2 A(...);
b2Vec2 B(...);

float angle_A = std::atan2(A.y,A.x);
float angle_B = B.GetAngle(); // Box2D already figured this out for you.

float angle_from_A_to_B = angle_B-angle_A;
float angle_from_B_to_A = angle_A-angle_B;
deft_code
źródło
1
Po przeczytaniu odpowiedzi @ Tetrad przypuszczam, że można połączyć produkt krzyżowy i arccos. W ten sposób użyjesz tylko jednej funkcji wyzwalania, ale nadal będziesz mieć aktualny kąt. Jednak odradzam tę optymalizację, dopóki nie upewnisz się, że śledzenie kąta AI ma zauważalny wpływ na wydajność twojej gry.
deft_code
2
Tak, podczas konwersji między wektorami i kątami, atan2 () jest zdecydowanie twoim przyjacielem.
bluescrn
1
Dzięki! Przekonałem się, że tak naprawdę nie potrzebuję kąta, chwytanie produktu krzyżowego 2D jest wystarczająco proste dla moich potrzeb.
Błąd 454
2
Również twój czek if( cross == -0.0f )vs if( cross == 0.0f )wygląda wyjątkowo krucho.
bobobobo
1
@ bobobobo, w przypadku silnika fizyki może nie być opcją jedynie wybranie kierunku i ruch. Magiczne obracanie może spowodować, że silniki fizyczne przestraszą się. Dalsze magiczne obracanie również wygląda okropnie dla animacji. Więc tak, może rozwiązać ten bez pojęcia w lewo lub w prawo, ale polerowane rozwiązanie często potrzebuje tych.
deft_code
10

Użyj arcsin produktu krzyżowego 2D (tj. Komponentu z wektora produktu krzyżowego). To da ci od -90 do 90, co pozwoli ci wiedzieć, czy iść w lewo, czy w prawo.

Bądź ostrożny, ponieważ krzyż A nie jest tym samym krzyżem B

Inną strategią (ale prawdopodobnie nie tak prostą) jest obliczenie „nagłówka” dwóch wektorów za pomocą atan2, a następnie ustalenie, czy A wskazujący na X stopni musi iść w lewo, czy w prawo, aby przejść do B wskazującego na y stopni.

Tetrad
źródło
Dziękuję za odpowiedź. Dla jasności w przyszłych przeglądarkach, przyjęcie odwrotnego sinusa wielkości produktu krzyżowego 2d dałoby wartości od 0 do 90. Przyjmowanie sinusa składnika z produktu krzyżowego 2d daje pożądane wyniki.
Błąd 454
@ Błąd 454, masz absolutną rację, naprawiłem mój post.
Tetrad
1

Użyj wektorów, aby przekierować statek. Tak działają „zachowania kierownicze” - nigdy nie trzeba obliczać kąta, wystarczy użyć wektorów. Jest to obliczeniowo znacznie tańsze.

Wektor w(wektor ze statku 1 na statek 2) to wszystkie potrzebne informacje. Zmodyfikuj wektor prędkości statku 1 lub wektor przyspieszenia statku 1 (lub nawet wektor kursu bezpośrednio), używając wersji ważonej w.

wprowadź opis zdjęcia tutaj

Wielkość , jak daleko statku 1 jest poza oczywiście (jak źle v nie zgadza się z wag) można znaleźć za pomocą ( 1 - dot(v,w))

  • ( dot(v,w)jest zmaksymalizowane, kiedy dokładnie vi wustawia się w kolejce)
  • ( 1 - dot(v,w)) Daje przy 0 vi wcałkowicie wyrównana, pod warunkiem, vi wnormalizuje)
Bobobobo
źródło
0

Istnieje prosty sposób na znalezienie bezwzględnego kąta wektora poprzez normalną geometrię.

na przykład wektor V = 2i - 3j;

wartość bezwzględna współczynnika x = 2;

wartość bezwzględna współczynnika y = 3;

kąt = atan (2/3); [kąt będzie między 0 a 90]

W oparciu o kąt kwadrantu zostanie zmieniony.

jeżeli (x współczynnik <0 i y współczynnik> 0) to kąt = kąt 180;

jeżeli (x współczynnik <0 i y współczynnik <0) to kąt = 180 + kąt;

jeżeli (x współczynnik> 0 i y współczynnik <0) to kąt = kąt 360;

jeśli (x współczynnik> 0 i y współczynnik> 0) to kąt = kąt;

po znalezieniu kąta pierwszego i drugiego wektora po prostu odejmij pierwszy kąt wektorowy od drugiego wektora. Wtedy uzyskasz kąt bezwzględny między dwoma wektorami.

manojyerra
źródło
5
Właśnie to implementuje dla Ciebie funkcja atan2 (). :)
Nathan Reed
@NathanReed, tak, ale czy nie możesz użyć tej metody z iloczynem kropkowym, aby uniknąć narzutu?
jdk1.0
0

Może powinienem napisać nieco inne pytanie i sam na nie odpowiedzieć; to najbliższe pytanie, jakie mogłem znaleźć, do tego, które miałem.

Robię prace 2D na kanwie HTML, gdzie mam kąty obrotu w radianach, a nie w wektorach. Potrzebowałem „kąta skrętu” (ta), aby przejść z „aktualnego kursu” (h) do „docelowego kursu” (t).

Oto moje rozwiązanie:

var ta = t - h,
    ata = Math.abs(ta)
;
if (ta > Math.PI) ta = (ta / ata) * (Math.PI - ata)
return ta;
Shavais
źródło