Eğer x ve y olarak ifade edilen bir 2B vektörünüz varsa, bunu en yakın pusula yönüne dönüştürmenin iyi bir yolu nedir?
Örneğin
x:+1, y:+1 => NE
x:0, y:+3 => N
x:+10, y:-2 => E // closest compass direction
Eğer x ve y olarak ifade edilen bir 2B vektörünüz varsa, bunu en yakın pusula yönüne dönüştürmenin iyi bir yolu nedir?
Örneğin
x:+1, y:+1 => NE
x:0, y:+3 => N
x:+10, y:-2 => E // closest compass direction
Yanıtlar:
En basit yol, atan2()
Tetrad'ın yorumlarda önerdiği gibi , muhtemelen vektör açısını elde etmek ve ardından ölçeklendirmek ve yuvarlamaktır, örneğin (sözde kod):
// 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():
const string[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 ) % 8
Hat bir açıklama gerekebilir. Bildiğim hemen hemen tüm dillerde , atan2()
fonksiyon radyan cinsinden açıyı döndürür. Bölme 2 π , radyandan tam bir dairenin fraksiyonlarına dönüştürür ve 8 ile çarpmak, daha sonra en yakın tamsayıya yuvarladığımız bir dairenin sekizinci haline dönüştürür. Son olarak, etrafı sarmak için modulo 8'i azaltıyoruz, böylece hem 0 hem de 8 doğru bir şekilde doğu ile eşleştiriliyor.
+ 8
Yukarıda atladığımın nedeni , bazı dillerde atan2()
negatif sonuçlar ( 0'dan 2 π yerine - π ila + π arasında ) döndürebilmesi ve modulo operatörünün ( ) negatif argümanlar (veya negatif argümanlar için davranışı tanımlanmamış olabilir). Ekleme%
8
İndirgeme işleminden önce girdiye (bir tam dönüş) , bağımsız değişkenlerin sonucu başka hiçbir şekilde etkilemeden her zaman pozitif olmasını sağlar.
Diliniz uygun bir en yakın yuvarlama işlevi sağlamazsa, bunun yerine kısaltılmış bir tamsayı dönüşümü kullanabilir ve bağımsız değişkene şu şekilde 0,5 ekleyebilirsiniz:
int octant = int( 8 * angle / (2*PI) + 8.5 ) % 8; // int() rounds down
Bazı dillerde, varsayılan kayan noktalı sayıdan tamsayıya dönüşümün negatif girdileri aşağıdan ziyade sıfıra yuvarladığını unutmayın; bu da girdinin her zaman pozitif olmasını sağlamak için başka bir nedendir.
Tabii ki, 8
daireyi bu kadar yöne bölmek için o satırdaki tüm oluşumları başka bir numarayla (örneğin, 4 veya 16, hatta altıgen bir haritadaysanız 6 veya 12) değiştirebilirsiniz. Numarayı / diziyi buna göre ayarlamanız yeterlidir.
atan2(y,x)
olmadığını unutmayın atan2(x,y)
.
atan2(x,y)
pusula başlıklarını kuzeyden başlayarak saat yönünde sıraladıysa da işe yarar.
octant = round(8 * angle / 360 + 8) % 8
quadtant = round(4 * angle / (2*PI) + 4) % 4
ve enum: 'u kullanabileceğini unutmayın { E, N, W, S }
.
8 seçeneğiniz var (veya daha hassas bir hassasiyet istiyorsanız 16 veya daha fazla).
kullanım atan2(y,x)
Vektörünüz için açı elde etmek için .
atan2()
aşağıdaki şekilde çalışır:
Yani x = 1, y = 0, 0 ile sonuçlanır ve x = -1, y = 0'da hem π hem de -π içeren süreksizdir.
Şimdi sadece atan2()
yukarıdaki pusulanın .
Uygulanması en kolay olanı, açıların artan bir kontrolüdür. Yüksek hassasiyet için kolayca değiştirilebilen bazı sahte kodlar:
//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 = 0
while angle > testangle
index++
if(index > direction.count - 1)
return direction[0] //roll over
testangle += increment
return direction[index]
Şimdi daha fazla hassasiyet eklemek için değerleri yön numarasına eklemeniz yeterlidir.
Algoritma, açımızın en son kontrol ettiğimiz yer ile yeni konum arasında bir yerde olup olmadığını görmek için pusula çevresindeki artan değerleri kontrol ederek çalışır. Bu yüzden -PI + artışıyla başlıyoruz / 2. Çeklerimizi her yönde eşit alan içerecek şekilde dengelemek istiyoruz. Bunun gibi bir şey:
Batı ikiye bölündü çünkü atan2()
Batı'daki dönüş değerleri süreksiz.
atan2
, ancak 0 derecenin muhtemelen kuzey değil, doğu olacağını unutmayın.
angle >=
Yukarıdaki koddaki kontrollere ihtiyacınız yoktur ; örneğin açı 45'ten küçükse, kuzey zaten döndürülmüş olacaktır, bu nedenle doğu kontrolü için açının> = 45 olup olmadığını kontrol etmenize gerek yoktur. Benzer şekilde, batıya dönmeden önce herhangi bir kontrole ihtiyacınız yoktur - kalan tek olasılık budur.
if
16 veya daha fazla yön için gitmek istiyorsanız , bir ton ifadeden bahsetmiyorum .
Vektörlerle uğraşırken, belirli bir çerçevedeki açılara dönüştürmek yerine temel vektör işlemlerini düşünün.
Bir sorgu vektörü v
ve bir dizi birim vektör s
verildiğinde, en hizalanmış vektör, s_i
maksimize edilen vektördür dot(v,s_i)
. Bunun nedeni, parametreler için sabit uzunluklar verilen nokta ürünün, aynı yöne sahip vektörler için maksimum ve karşıt yönlere sahip vektörler için minimum, aralarında sorunsuz bir şekilde değişmesidir.
Bu önemsiz olarak ikiden daha fazla boyuta genelleme yapar, keyfi yönlerle genişletilebilir ve sonsuz degradeler gibi çerçeveye özgü sorunlara maruz kalmaz.
Uygulama açısından, bu, her bir kardinal yönde bir vektörden, o yönü temsil eden bir tanımlayıcıyla (enum, dize, ihtiyacınız olan her şey) ilişkilendirmeye kadar kaynar. Daha sonra, en yüksek nokta ürününe sahip olanı bularak yol tarifleri setinizin üzerinden geçersiniz.
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];
}
}
map
ile float2
anahtar? Bu çok ciddi görünmüyor.
Burada bahsedilmeyen bir yol, vektörlere karmaşık sayılar olarak muamele etmektir. Trigonometri gerektirmezler ve özellikle rotasyonlarınızı eklemek, çoğaltmak veya yuvarlamak için oldukça sezgisel olabilirler, çünkü özellikle başlıklarınızı sayı çiftleri olarak temsil ediyorsunuz.
Onlara aşina değilseniz, yönler gerçek bileşen olmakla birlikte a + b (i) şeklinde ifade edilir ve b (i) hayali olur. X'in gerçek ve Y'nin hayali olduğu kartezyen uçağı hayal ederseniz, 1 doğu olur (sağda), ben kuzey olurum.
Burada kilit kısım: 8 temel yön, gerçek ve hayali bileşenleri için sadece 1, -1 veya 0 rakamlarıyla temsil edilir. Tek yapmanız gereken X, Y koordinatlarınızı bir oran olarak azaltmak ve yönü elde etmek için her ikisini de en yakın tam sayıya yuvarlamaktır.
NW (-1 + i) N (i) NE (1 + i)
W (-1) Origin E (1)
SW (-1 - i) S (-i) SE (1 - i)
Başlık-en yakın çapraz dönüşüm için, hem X hem de Y'yi orantılı olarak azaltın, böylece daha büyük değer tam olarak 1 veya -1 olur. Ayarlamak
// Some pseudocode
enum xDir { West = -1, Center = 0, East = 1 }
enum yDir { South = -1, Center = 0, North = 1 }
xDir GetXdirection(Vector2 heading)
{
return round(heading.x / Max(heading.x, heading.y));
}
yDir GetYdirection(Vector2 heading)
{
return round(heading.y / Max(heading.x, heading.y));
}
Orijinal olanın (10, -2) her iki bileşeninin yuvarlanması size 1 + 0 (i) veya 1 verir. Böylece en yakın yön doğudır.
Yukarıdakiler aslında karmaşık bir sayı yapısının kullanılmasını gerektirmez, ancak bunları böyle düşünmek 8 kardinal yönü bulmayı daha hızlı hale getirir. İki veya daha fazla vektörün net başlığını almak istiyorsanız, vektör matematiğini normal şekilde yapabilirsiniz. (Karmaşık sayılar olarak, eklemezsiniz, ancak sonuç için çarparsınız)
Max(x, y)
Max(Abs(x, y))
negatif çeyrekler için çalışmak olmalı . Denedim ve izb ile aynı sonucu aldım - bu pusula yönlerini yanlış açılarda değiştirir. Ben heading.y / heading.x 0,5 (yani yuvarlatılmış değer 0'dan 1'e geçer), arctan (0,5) = 26,565 ° olduğunda geçiş olacağını tahmin ediyorum.
Bu işe yarıyor gibi görünüyor:
public class So49290 {
int piece(int x,int y) {
double angle=Math.atan2(y,x);
if(angle<0) angle+=2*Math.PI;
int piece=(int)Math.round(n*angle/(2*Math.PI));
if(piece==n)
piece=0;
return piece;
}
void run(int x,int y) {
System.out.println("("+x+","+y+") is "+s[piece(x,y)]);
}
public static void main(String[] args) {
So49290 so=new So49290();
so.run(1,0);
so.run(1,1);
so.run(0,1);
so.run(-1,1);
so.run(-1,0);
so.run(-1,-1);
so.run(0,-1);
so.run(1,-1);
}
int n=8;
static final String[] s=new String[] {"e","ne","n","nw","w","sw","s","se"};
}
E = 0, NS = 1, n = 2, K = 3, B = 4, GB = 5, S = 6, SH = 7
f (x, y) = mod ((4-2 * (1 + işareti (x)) * (1-işareti (y ^ 2)) - (2 + işareti (x)) * işareti (y)
-(1+sign(abs(sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y))))
-pi()/(8+10^-15)))/2*sign((x^2-y^2)*(x*y))),8)
Bir dize istediğinizde:
h_axis = ""
v_axis = ""
if (x > 0) h_axis = "E"
if (x < 0) h_axis = "W"
if (y > 0) v_axis = "S"
if (y < 0) v_axis = "N"
return v_axis.append_string(h_axis)
Bu, bit alanlarını kullanarak size sabitler verir:
// 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 = 0x0
if (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
Hafif bir performans artışı <
-Kontrolleri karşılık gelen >
-kontrollerin diğer-şubesine , ancak bunu yapmaktan kaçındım çünkü okunabilirliğe zarar veriyor.
if (x > 0.9) dir |= DIR_E
. Phillipp'in orijinal kodundan daha iyi olmalı ve L2 normu ve atan2'yi kullanmaktan biraz daha ucuz olmalıdır. Belki .. ya da olmayabilir.