Zwięzły: naznaczony zwięzłością wypowiedzi lub stwierdzeń: wolny od wszelkich opracowań i zbędnych szczegółów. Po prostu to
wyrzucam
Odpowiedzi:
25
Najprostszym sposobem jest prawdopodobnie uzyskanie kąta wektora za pomocą atan2(), jak sugeruje Tetrad w komentarzach, a następnie skalowanie i zaokrąglanie go, np. (Pseudokod):
// enumerated counterclockwise, starting from east = 0:enum compassDir {
E =0, NE =1,
N =2, NW =3,
W =4, SW =5,
S =6, SE =7};// for string conversion, if you can't just do e.g. dir.toString():conststring[8] headings ={"E","NE","N","NW","W","SW","S","SE"};// actual conversion code:float angle = atan2( vector.y, vector.x );int octant = round(8* angle /(2*PI)+8)%8;
compassDir dir =(compassDir) octant;// typecast to enum: 0 -> E etc.string dirStr = headings[octant];
octant = round( 8 * angle / (2*PI) + 8 ) % 8Linia może wymagać pewnego wyjaśnienia. W całkiem dużo wszystkich języków, które znam, które mają to, funkcja zwraca kąt w radianach. Dzielenie go przez 2 π przekształca go z radianów na ułamki pełnego koła, a pomnożenie przez 8 następnie przekształca go w ósemki koła, które następnie zaokrąglamy do najbliższej liczby całkowitej. Na koniec zmniejszamy go modulo 8, aby zająć się zawijaniem, tak aby zarówno 0, jak i 8 były poprawnie odwzorowane na wschód.atan2()
Powodem tego + 8, który pominąłem powyżej, jest to, że w niektórych językach atan2()mogą zwracać wyniki ujemne (tj. Od - π do + π zamiast od 0 do 2 π ), a operator modulo ( %) może być zdefiniowany tak, aby zwracał wartości ujemne dla argumenty negatywne (lub jego zachowanie w przypadku argumentów negatywnych może być niezdefiniowane). Dodanie 8(tj. Jeden pełny obrót) do danych wejściowych przed redukcją zapewnia, że argumenty są zawsze dodatnie, bez wpływu na wynik w jakikolwiek inny sposób.
Jeśli twój język nie zapewnia wygodnej funkcji zaokrąglania do najbliższego, możesz zamiast tego użyć skracającej konwersji liczb całkowitych i po prostu dodać 0,5 do argumentu, tak jak to:
int octant =int(8* angle /(2*PI)+8.5)%8;// int() rounds down
Zauważ, że w niektórych językach domyślna konwersja liczb zmiennoprzecinkowych zaokrągla ujemne wartości wejściowe w górę do zera zamiast w dół, co jest kolejnym powodem, dla którego należy upewnić się, że dane wejściowe są zawsze dodatnie.
Oczywiście możesz zastąpić wszystkie wystąpienia 8tej linii inną liczbą (np. 4 lub 16, a nawet 6 lub 12, jeśli jesteś na mapie heksadecymalnej), aby podzielić okrąg na tyle kierunków. Po prostu dostosuj odpowiednio wyliczanie / tablicę.
Zauważ, że zwykle tak atan2(y,x)nie jest atan2(x,y).
sam hocevar
@Sam: Ups, poprawione. Oczywiście atan2(x,y)zadziałałoby również, gdyby po prostu wymienić nagłówki kompasu w kolejności zgodnej z ruchem wskazówek zegara, zaczynając od północy.
Ilmari Karonen
2
Nawiasem mówiąc, +1, naprawdę uważam, że jest to najprostsza i najbardziej rygorystyczna odpowiedź.
sam hocevar
1
@TheLima:octant = round(8 * angle / 360 + 8) % 8
Ilmari Karonen,
1
Należy pamiętać, że cel ten można łatwo przekształcić w 4-way Kompas: quadtant = round(4 * angle / (2*PI) + 4) % 4i przy użyciu ENUM: { E, N, W, S }.
Spoike
10
Masz 8 opcji (lub 16 lub więcej, jeśli chcesz jeszcze dokładniejszej).
Użyj, atan2(y,x)aby uzyskać kąt dla wektora.
atan2() działa w następujący sposób:
Zatem x = 1, y = 0 spowoduje 0, i jest nieciągły przy x = -1, y = 0, zawierający zarówno π, jak i π.
Teraz musimy tylko zmapować wyjście tak, atan2()aby pasowało do kompasu, który mamy powyżej.
Prawdopodobnie najłatwiejszym do wdrożenia jest stopniowe sprawdzanie kątów. Oto pseudo kod, który można łatwo modyfikować w celu zwiększenia precyzji:
//start direction from the lowest value, in this case it's west with -πenum direction {
west,
south,
east,
north
}
increment =(2PI)/direction.count
angle = atan2(y,x);
testangle =-PI + increment/2
index =0while angle > testangle
index++if(index > direction.count -1)return direction[0]//roll over
testangle += increment
return direction[index]
Teraz, aby dodać większą precyzję, po prostu dodaj wartości do wyliczenia kierunku.
Algorytm działa poprzez sprawdzanie rosnących wartości wokół kompasu, aby sprawdzić, czy nasz kąt leży gdzieś pomiędzy miejscem, w którym ostatnio sprawdzaliśmy, a nową pozycją. Dlatego zaczynamy od -PI + przyrost / 2. Chcemy przesunąć nasze kontrole, aby objąć równą przestrzeń wokół każdego kierunku. Coś takiego:
Zachód jest podzielony na dwie części, ponieważ wartości zwracane atan2()na Zachodzie są nieciągłe.
Łatwym sposobem na „przekonwertowanie ich na kąt” jest użycie atan2, choć należy pamiętać, że 0 stopni to prawdopodobnie wschód, a nie północ.
Tetrad
1
Nie potrzebujesz angle >=czeków w powyższym kodzie; na przykład, jeśli kąt jest mniejszy niż 45, wówczas północ zostanie już zwrócona, więc nie trzeba sprawdzać, czy kąt> = 45 dla kontroli wschodniej. Podobnie nie potrzebujesz w ogóle czeku przed powrotem na zachód - to jedyna pozostała możliwość.
MrKWatkins
4
Nie nazwałbym tego zwięzłym sposobem uzyskania wskazówek. Wydaje się dość niezgrabny i będzie wymagał wielu zmian, aby dostosować to do różnych „rozdzielczości”. Nie mówiąc już o tonie ifwypowiedzi, jeśli chcesz iść w 16 lub więcej kierunkach.
bummzack
2
Nie ma potrzeby normalizowania wektora: kąt pozostaje taki sam w przypadku zmian wielkości.
Kylotan
Dzięki @bummzack, zredagowałem post, aby uczynić go bardziej zwięzłym i łatwym do zwiększenia precyzji poprzez dodanie większej liczby wartości wyliczeniowych.
MichaelHouse
8
Ilekroć masz do czynienia z wektorami, rozważ podstawowe operacje wektorowe zamiast konwersji na kąty w określonej ramce.
Biorąc pod uwagę wektor zapytania vi zestaw wektorów jednostkowych s, najbardziej wyrównanym wektorem jest wektor, s_iktóry się maksymalizuje dot(v,s_i). Wynika to z tego, że iloczyn skalarny o ustalonych długościach dla parametrów ma maksimum dla wektorów o tym samym kierunku i minimum dla wektorów o przeciwnych kierunkach, płynnie zmieniających się między nimi.
Uogólnia to ogólnie na więcej niż dwa wymiary, jest rozszerzalne z dowolnymi kierunkami i nie ma problemów specyficznych dla ramki, takich jak nieskończone gradienty.
Pod względem implementacji sprowadzałoby się to do skojarzenia z wektora w każdym kardynalnym kierunku z identyfikatorem (wyliczenie, ciąg, cokolwiek potrzebujesz) reprezentującego ten kierunek. Następnie zapętlisz swój zestaw wskazówek, znajdując ten z najwyższym iloczynem kropkowym.
map<float2,Direction> candidates;
candidates[float2(1,0)]= E; candidates[float2(0,1)]= N;// etc.for each (float2 dir in candidates){float goodness = dot(dir, v);if(goodness > bestResult){
bestResult = goodness;
bestDir = candidates[dir];}}
Ta implementacja może być również napisana bez rozgałęzień i wektoryzowana bez większych problemów.
Promit
1
A mapz float2kluczem? To nie wygląda bardzo poważnie.
sam hocevar
Jest to „pseudo-kod” w sposób dydaktyczny. Jeśli chcesz implementacji zoptymalizowanych pod kątem paniki, GDSE prawdopodobnie nie jest dobrym miejscem na twój makaron-makaron. Jeśli chodzi o użycie float2 jako klucza, float może dokładnie reprezentować liczby całkowite, których tu używamy, i możesz zrobić dla nich doskonale dobry komparator. Klucze zmiennoprzecinkowe są nieodpowiednie tylko wtedy, gdy zawierają specjalne wartości lub próbujesz wyszukać obliczone wyniki. Iteracja po sekwencji asocjacyjnej jest w porządku. Jasne, mógłbym użyć liniowego wyszukiwania w tablicy, ale byłby to bezcelowy bałagan.
Lars Viklund
3
Jednym ze sposobów, o którym nie wspomniano tutaj, jest traktowanie wektorów jako liczb zespolonych. Nie wymagają trygonometrii i mogą być dość intuicyjne w dodawaniu, mnożeniu lub zaokrąglaniu rotacji, zwłaszcza, że nagłówki są już reprezentowane jako pary liczb.
W przypadku, gdy nie jesteś ich zaznajomiony, kierunki są wyrażone w postaci a + b (i), a istota jest składnikiem rzeczywistym, a b (i) jest urojoną. Jeśli wyobrażasz sobie płaszczyznę kartezjańską z X będącym rzeczywistym, a Y będącym urojonym, 1 byłby na wschodzie (po prawej), byłbym na północy.
Oto kluczowa część: 8 głównych kierunków jest reprezentowanych wyłącznie przez liczby 1, -1 lub 0 dla ich rzeczywistych i urojonych składników. Wszystko, co musisz zrobić, to zmniejszyć współrzędne X, Y jako stosunek i zaokrąglić obie do najbliższej liczby całkowitej, aby uzyskać kierunek.
NW (-1+ i) N (i) NE (1+ i)
W (-1)Origin E (1)
SW (-1- i) S (-i) SE (1- i)
W celu zamiany kierunku na najbliższą przekątną zmniejsz proporcjonalnie zarówno X, jak i Y, aby większa wartość wynosiła dokładnie 1 lub -1. Zestaw
Zaokrąglenie obu składników pierwotnie (10, -2) daje 1 + 0 (i) lub 1. Więc najbliższy kierunek to wschód.
Powyższe w rzeczywistości nie wymaga użycia złożonej struktury liczbowej, ale myślenie o nich jako takich przyspiesza znalezienie 8 głównych kierunków. Możesz zrobić matematykę wektorową w zwykły sposób, jeśli chcesz uzyskać nagłówek netto dwóch lub więcej wektorów. (Jako liczby zespolone nie dodajesz, ale mnożysz dla wyniku)
To niesamowite, ale popełnia błąd podobny do tego, który popełniłem przy własnej próbie. Odpowiedzi są bliskie, ale nieprawidłowe. Kąt graniczny między E i NE wynosi 22,5 stopnia, ale odcina się przy 26,6 stopni.
izb
Max(x, y)powinien Max(Abs(x, y))pracować dla ujemnych kwadrantów. Próbowałem i uzyskałem ten sam wynik co izb - to zmienia kierunek kompasu pod niewłaściwym kątem. Sądzę, że zmieniłby się, gdy kurs. Y / kurs. X przekroczy 0,5 (więc zaokrąglona wartość zmienia się z 0 na 1), co oznacza arctan (0,5) = 26,565 °.
amitp
Innym sposobem użycia liczb zespolonych w tym przypadku jest zaobserwowanie, że mnożenie liczb zespolonych wymaga rotacji. Jeśli konstruujesz liczbę zespoloną reprezentującą 1/8 obrotu wokół koła, to za każdym razem, gdy ją pomnożysz, przesuwasz jedną oktanę. Więc możesz zapytać: czy możemy policzyć, ile mnożników zajęło przejście ze Wschodu do obecnego kursu? Odpowiedź na „ile razy musimy przez to pomnożyć” to: logarytm . Jeśli szukasz logarytmów dla liczb zespolonych… używa atan2. Ostatecznie jest to równoważne z odpowiedzią Ilmari.
Najprawdopodobniej dlatego, że nie ma wyjaśnienia twojego kodu. Dlaczego to jest rozwiązanie i jak działa?
Vaillancourt
prowadziłeś to?
Ray Tayek
Nie, a biorąc pod uwagę nazwę klasy, zakładałem, że to zrobiłeś i zadziałało. I to świetnie. Ale zapytałeś, dlaczego ludzie głosowali, a ja odpowiedziałem; Nigdy nie sugerowałem, że to nie zadziałało :)
Vaillancourt
-2
E = 0, NE = 1, N = 2, NW = 3, W = 4, SW = 5, S = 6, SE = 7
f (x, y) = mod ((4-2 * (1 + znak (x)) * (1-znak (y ^ 2)) - (2 + znak (x)) * znak (y)
// main direction constants
DIR_E =0x1
DIR_W =0x2
DIR_S =0x4
DIR_N =0x8// mixed direction constants
DIR_NW = DIR_N | DIR_W
DIR_SW = DIR_S | DIR_W
DIR_NE = DIR_N | DIR_E
DIR_SE = DIR_S | DIR_E
// calculating the direction
dir =0x0if(x >0) dir |= DIR_E
if(x <0) dir |= DIR_W
if(y >0) dir |= DIR_S
if(y <0) dir |= DIR_N
return dir
Nieznaczną poprawą wydajności byłoby umieszczenie <-checks w gałęzi else odpowiednich >-checks, ale powstrzymałem się od robienia tego, ponieważ szkodzi to czytelności.
Przepraszam, ale to nie da dokładnie odpowiedzi, której szukam. Dzięki temu kodowi będzie dawać „N”, jeśli wektor jest dokładnie na północ, a NE lub NW, jeśli x jest dowolną inną wartością. Potrzebuję najbliższego kierunku kompasu, np. Jeśli wektor jest bliżej N niż NW, wówczas zwróci N.
izb
Czy to rzeczywiście dałoby najbliższy kierunek? Wygląda na to, że wektor (0,00001,100) dałby ci północny wschód. edytuj: pobiłeś mnie do tego izb.
CiscoIPPhone
nie powiedziałeś, że chcesz najbliższego kierunku.
Philipp
1
Przepraszam, ukryłem to w tytule. Powinno być jaśniej w
tekście
1
A co z używaniem nieskończonej normy? Dzielenie przez max (abs (vector.components)) daje znormalizowany wektor w odniesieniu do tej normy. Teraz możesz napisać małą tabelę kontrolną na podstawie if (x > 0.9) dir |= DIR_Ecałej reszty. Powinien być lepszy niż oryginalny kod Phillippa i nieco tańszy niż stosowanie norm L2 i atan2. Może, a może nie.
Odpowiedzi:
Najprostszym sposobem jest prawdopodobnie uzyskanie kąta wektora za pomocą
atan2()
, jak sugeruje Tetrad w komentarzach, a następnie skalowanie i zaokrąglanie go, np. (Pseudokod):octant = round( 8 * angle / (2*PI) + 8 ) % 8
Linia może wymagać pewnego wyjaśnienia. W całkiem dużo wszystkich języków, które znam, które mają to, funkcja zwraca kąt w radianach. Dzielenie go przez 2 π przekształca go z radianów na ułamki pełnego koła, a pomnożenie przez 8 następnie przekształca go w ósemki koła, które następnie zaokrąglamy do najbliższej liczby całkowitej. Na koniec zmniejszamy go modulo 8, aby zająć się zawijaniem, tak aby zarówno 0, jak i 8 były poprawnie odwzorowane na wschód.atan2()
Powodem tego
+ 8
, który pominąłem powyżej, jest to, że w niektórych językachatan2()
mogą zwracać wyniki ujemne (tj. Od - π do + π zamiast od 0 do 2 π ), a operator modulo (%
) może być zdefiniowany tak, aby zwracał wartości ujemne dla argumenty negatywne (lub jego zachowanie w przypadku argumentów negatywnych może być niezdefiniowane). Dodanie8
(tj. Jeden pełny obrót) do danych wejściowych przed redukcją zapewnia, że argumenty są zawsze dodatnie, bez wpływu na wynik w jakikolwiek inny sposób.Jeśli twój język nie zapewnia wygodnej funkcji zaokrąglania do najbliższego, możesz zamiast tego użyć skracającej konwersji liczb całkowitych i po prostu dodać 0,5 do argumentu, tak jak to:
Zauważ, że w niektórych językach domyślna konwersja liczb zmiennoprzecinkowych zaokrągla ujemne wartości wejściowe w górę do zera zamiast w dół, co jest kolejnym powodem, dla którego należy upewnić się, że dane wejściowe są zawsze dodatnie.
Oczywiście możesz zastąpić wszystkie wystąpienia
8
tej linii inną liczbą (np. 4 lub 16, a nawet 6 lub 12, jeśli jesteś na mapie heksadecymalnej), aby podzielić okrąg na tyle kierunków. Po prostu dostosuj odpowiednio wyliczanie / tablicę.źródło
atan2(y,x)
nie jestatan2(x,y)
.atan2(x,y)
zadziałałoby również, gdyby po prostu wymienić nagłówki kompasu w kolejności zgodnej z ruchem wskazówek zegara, zaczynając od północy.octant = round(8 * angle / 360 + 8) % 8
quadtant = round(4 * angle / (2*PI) + 4) % 4
i przy użyciu ENUM:{ E, N, W, S }
.Masz 8 opcji (lub 16 lub więcej, jeśli chcesz jeszcze dokładniejszej).
Użyj,
atan2(y,x)
aby uzyskać kąt dla wektora.atan2()
działa w następujący sposób:Zatem x = 1, y = 0 spowoduje 0, i jest nieciągły przy x = -1, y = 0, zawierający zarówno π, jak i π.
Teraz musimy tylko zmapować wyjście tak,
atan2()
aby pasowało do kompasu, który mamy powyżej.Prawdopodobnie najłatwiejszym do wdrożenia jest stopniowe sprawdzanie kątów. Oto pseudo kod, który można łatwo modyfikować w celu zwiększenia precyzji:
Teraz, aby dodać większą precyzję, po prostu dodaj wartości do wyliczenia kierunku.
Algorytm działa poprzez sprawdzanie rosnących wartości wokół kompasu, aby sprawdzić, czy nasz kąt leży gdzieś pomiędzy miejscem, w którym ostatnio sprawdzaliśmy, a nową pozycją. Dlatego zaczynamy od -PI + przyrost / 2. Chcemy przesunąć nasze kontrole, aby objąć równą przestrzeń wokół każdego kierunku. Coś takiego:
Zachód jest podzielony na dwie części, ponieważ wartości zwracane
atan2()
na Zachodzie są nieciągłe.źródło
atan2
, choć należy pamiętać, że 0 stopni to prawdopodobnie wschód, a nie północ.angle >=
czeków w powyższym kodzie; na przykład, jeśli kąt jest mniejszy niż 45, wówczas północ zostanie już zwrócona, więc nie trzeba sprawdzać, czy kąt> = 45 dla kontroli wschodniej. Podobnie nie potrzebujesz w ogóle czeku przed powrotem na zachód - to jedyna pozostała możliwość.if
wypowiedzi, jeśli chcesz iść w 16 lub więcej kierunkach.Ilekroć masz do czynienia z wektorami, rozważ podstawowe operacje wektorowe zamiast konwersji na kąty w określonej ramce.
Biorąc pod uwagę wektor zapytania
v
i zestaw wektorów jednostkowychs
, najbardziej wyrównanym wektorem jest wektor,s_i
który się maksymalizujedot(v,s_i)
. Wynika to z tego, że iloczyn skalarny o ustalonych długościach dla parametrów ma maksimum dla wektorów o tym samym kierunku i minimum dla wektorów o przeciwnych kierunkach, płynnie zmieniających się między nimi.Uogólnia to ogólnie na więcej niż dwa wymiary, jest rozszerzalne z dowolnymi kierunkami i nie ma problemów specyficznych dla ramki, takich jak nieskończone gradienty.
Pod względem implementacji sprowadzałoby się to do skojarzenia z wektora w każdym kardynalnym kierunku z identyfikatorem (wyliczenie, ciąg, cokolwiek potrzebujesz) reprezentującego ten kierunek. Następnie zapętlisz swój zestaw wskazówek, znajdując ten z najwyższym iloczynem kropkowym.
źródło
map
zfloat2
kluczem? To nie wygląda bardzo poważnie.Jednym ze sposobów, o którym nie wspomniano tutaj, jest traktowanie wektorów jako liczb zespolonych. Nie wymagają trygonometrii i mogą być dość intuicyjne w dodawaniu, mnożeniu lub zaokrąglaniu rotacji, zwłaszcza, że nagłówki są już reprezentowane jako pary liczb.
W przypadku, gdy nie jesteś ich zaznajomiony, kierunki są wyrażone w postaci a + b (i), a istota jest składnikiem rzeczywistym, a b (i) jest urojoną. Jeśli wyobrażasz sobie płaszczyznę kartezjańską z X będącym rzeczywistym, a Y będącym urojonym, 1 byłby na wschodzie (po prawej), byłbym na północy.
Oto kluczowa część: 8 głównych kierunków jest reprezentowanych wyłącznie przez liczby 1, -1 lub 0 dla ich rzeczywistych i urojonych składników. Wszystko, co musisz zrobić, to zmniejszyć współrzędne X, Y jako stosunek i zaokrąglić obie do najbliższej liczby całkowitej, aby uzyskać kierunek.
W celu zamiany kierunku na najbliższą przekątną zmniejsz proporcjonalnie zarówno X, jak i Y, aby większa wartość wynosiła dokładnie 1 lub -1. Zestaw
Zaokrąglenie obu składników pierwotnie (10, -2) daje 1 + 0 (i) lub 1. Więc najbliższy kierunek to wschód.
Powyższe w rzeczywistości nie wymaga użycia złożonej struktury liczbowej, ale myślenie o nich jako takich przyspiesza znalezienie 8 głównych kierunków. Możesz zrobić matematykę wektorową w zwykły sposób, jeśli chcesz uzyskać nagłówek netto dwóch lub więcej wektorów. (Jako liczby zespolone nie dodajesz, ale mnożysz dla wyniku)
źródło
Max(x, y)
powinienMax(Abs(x, y))
pracować dla ujemnych kwadrantów. Próbowałem i uzyskałem ten sam wynik co izb - to zmienia kierunek kompasu pod niewłaściwym kątem. Sądzę, że zmieniłby się, gdy kurs. Y / kurs. X przekroczy 0,5 (więc zaokrąglona wartość zmienia się z 0 na 1), co oznacza arctan (0,5) = 26,565 °.to wydaje się działać:
źródło
E = 0, NE = 1, N = 2, NW = 3, W = 4, SW = 5, S = 6, SE = 7
f (x, y) = mod ((4-2 * (1 + znak (x)) * (1-znak (y ^ 2)) - (2 + znak (x)) * znak (y)
źródło
Kiedy chcesz ciąg:
Daje to stałe poprzez wykorzystanie pól bitowych:
Nieznaczną poprawą wydajności byłoby umieszczenie
<
-checks w gałęzi else odpowiednich>
-checks, ale powstrzymałem się od robienia tego, ponieważ szkodzi to czytelności.źródło
if (x > 0.9) dir |= DIR_E
całej reszty. Powinien być lepszy niż oryginalny kod Phillippa i nieco tańszy niż stosowanie norm L2 i atan2. Może, a może nie.