यदि आपके पास एक 2D वेक्टर है जिसे x और y के रूप में व्यक्त किया गया है, तो इसे निकटतम कम्पास दिशा में बदलने का एक अच्छा तरीका क्या है?
जैसे
x:+1, y:+1 => NE
x:0, y:+3 => N
x:+10, y:-2 => E // closest compass direction
यदि आपके पास एक 2D वेक्टर है जिसे x और y के रूप में व्यक्त किया गया है, तो इसे निकटतम कम्पास दिशा में बदलने का एक अच्छा तरीका क्या है?
जैसे
x:+1, y:+1 => NE
x:0, y:+3 => N
x:+10, y:-2 => E // closest compass direction
जवाबों:
सबसे सरल तरीका संभवतः वेक्टर के कोण का उपयोग करना है atan2()
, जैसा कि टेट्रड टिप्पणियों में बताता है, और फिर पैमाने और इसे गोल करना, जैसे (स्यूडोकोड):
// 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
लाइन कुछ स्पष्टीकरण की आवश्यकता हो सकती। काफी सभी भाषाओं कि मुझे लगता है कि के बारे में पता में यह राशि समारोह रेडियन में कोण देता है। 2 से विभाजित πatan2()
यह धर्मान्तरित एक पूर्ण चक्र के अंशों को रेडियंस से, और 8 से गुणा फिर किसी मंडली के आठवें भाग, में बदल देता है जो हम तो निकटतम पूर्णांक तक दौर। अंत में, हम रैप-अराउंड की देखभाल के लिए इसे मोडुलो 8 को कम कर देते हैं, ताकि 0 और 8 दोनों पूर्व में सही ढंग से मैप किए जा सकें।
इसका कारण + 8
, जिसके बारे में मैंने ऊपर बताया है, वह यह है कि कुछ भाषाओं atan2()
में नकारात्मक परिणाम (यानी - π से + π के बजाय 0 से 2 which ) %
तक लौट सकते हैं और modulo ऑपरेटर ( ) को नकारात्मक मानों को लौटाने के लिए परिभाषित किया जा सकता है। नकारात्मक तर्क (या नकारात्मक तर्क के लिए इसका व्यवहार अपरिभाषित हो सकता है)। 8
कटौती से पहले इनपुट में (यानी एक पूर्ण मोड़) जोड़ना सुनिश्चित करता है कि तर्क हमेशा सकारात्मक होते हैं, परिणाम को किसी अन्य तरीके से प्रभावित किए बिना।
यदि आपकी भाषा एक सुविधाजनक दौर-से-निकटतम फ़ंक्शन प्रदान करने के लिए नहीं होती है, तो आप इसके बजाय एक पूर्णांक पूर्णांक रूपांतरण का उपयोग कर सकते हैं और इस तरह केवल तर्क में 0.5 जोड़ सकते हैं:
int octant = int( 8 * angle / (2*PI) + 8.5 ) % 8; // int() rounds down
ध्यान दें, कुछ भाषाओं में, डिफ़ॉल्ट फ्लोट-टू-पूर्णांक रूपांतरण राउंड नकारात्मक इनपुट को नीचे की बजाय शून्य की ओर बढ़ाता है, जो यह सुनिश्चित करने का एक और कारण है कि इनपुट हमेशा सकारात्मक होता है।
बेशक, आप 8
उस लाइन के सभी घटनाओं को किसी अन्य संख्या (जैसे 4 या 16, या यहां तक कि 6 या 12 यदि आप एक हेक्स मानचित्र पर हैं) से सर्कल को उस कई दिशाओं में विभाजित करने के लिए बदल सकते हैं। बस तदनुसार एनुम / सरणी समायोजित करें।
atan2(y,x)
, नहीं atan2(x,y)
।
atan2(x,y)
यह भी काम करेगा, अगर कोई बस उत्तर की ओर से शुरू होने वाले दक्षिणावर्त क्रम में कम्पास हेडिंग सूचीबद्ध करता है।
octant = round(8 * angle / 360 + 8) % 8
quadtant = round(4 * angle / (2*PI) + 4) % 4
और एनम का उपयोग करके { E, N, W, S }
:।
आपके पास 8 विकल्प हैं (या 16 या अधिक यदि आप भी महीन परिशुद्धता चाहते हैं)।
atan2(y,x)
अपने वेक्टर के लिए कोण प्राप्त करने के लिए उपयोग करें ।
atan2()
निम्नलिखित तरीके से काम करता है:
तो x = 1, y = 0 का परिणाम 0 होगा, और यह x = -1, y = 0 पर बंद होगा, जिसमें π और-y दोनों होंगे।
अब हमें बस atan2()
उस कंपास के आउटपुट को मैप करना होगा जो हमारे ऊपर है।
लागू करने के लिए सरलतम कोणों का एक वृद्धिशील चेक है। यहाँ कुछ छद्म कोड हैं जिन्हें आसानी से संशोधित परिशुद्धता के लिए संशोधित किया जा सकता है:
//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]
अब और अधिक सटीक जोड़ने के लिए, केवल दिशा एनम के लिए मान जोड़ें।
एल्गोरिथ्म कम्पास के आस-पास बढ़ते मूल्यों की जांच करके काम करता है यह देखने के लिए कि क्या हमारा कोण कहीं और है जहां हमने अंतिम बार जांच की थी और नई स्थिति। इसलिए हम -PI + वृद्धि / 2 से शुरू करते हैं। हम प्रत्येक दिशा में समान स्थान शामिल करने के लिए अपने चेक को ऑफ़सेट करना चाहते हैं। कुछ इस तरह:
पश्चिम में दो का टूटना है क्योंकि पश्चिम में वापसी के मूल्य atan2()
बंद हैं।
atan2
, हालांकि ध्यान रखें कि 0 डिग्री संभवतः पूर्व में होगी और उत्तर में नहीं।
angle >=
ऊपर दिए गए कोड में चेक की आवश्यकता नहीं है; उदाहरण के लिए यदि कोण 45 से कम है तो उत्तर को पहले ही वापस कर दिया जाएगा इसलिए आपको पूर्व चेक के लिए कोण> = 45 की जाँच करने की आवश्यकता नहीं है। इसी तरह आपको पश्चिम लौटने से पहले किसी भी चेक की आवश्यकता नहीं है - यह एकमात्र संभावना है जो शेष है।
if
यदि आप 16 दिशाओं या अधिक के लिए जाना चाहते हैं, तो एक टन के बयान की बात नहीं करें ।
जब भी आप वैक्टर के साथ काम कर रहे हों, तो किसी विशेष फ्रेम में कोणों में परिवर्तित करने के बजाय मौलिक वेक्टर संचालन पर विचार करें।
क्वेरी वेक्टर v
और यूनिट वैक्टर के एक सेट को देखते हुए s
, सबसे अधिक संरेखित वेक्टर वह वेक्टर है s_i
जो अधिकतम होता है dot(v,s_i)
। इसका कारण यह है कि मापदंडों के लिए निर्धारित लंबाई दिए गए डॉट उत्पाद में एक ही दिशा वाले वैक्टर के लिए अधिकतम है और विपरीत दिशाओं के साथ वैक्टर के लिए न्यूनतम है, सुचारू रूप से इनबेटीइन बदलते हुए।
यह सामान्य रूप से दो से अधिक आयामों में सामान्यीकरण करता है, मनमाने दिशा-निर्देशों के साथ विस्तार योग्य है और यह फ्रेम-विशिष्ट समस्याओं जैसे अनंत ग्रेडिएंट्स से ग्रस्त नहीं है।
कार्यान्वयन-वार, यह प्रत्येक कार्डिनल दिशा में एक वेक्टर से एक पहचानकर्ता (एनम, स्ट्रिंग, जो भी आपको ज़रूरत है) को उस दिशा का प्रतिनिधित्व करने के लिए उकसाएगा। फिर आप अपने दिशा-निर्देशों के सेट पर लूप करेंगे, जिसमें सबसे अधिक डॉट उत्पाद होगा।
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
साथ float2
कुंजी के रूप में? यह बहुत गंभीर नहीं लगता है।
एक तरीका जिसका उल्लेख यहाँ नहीं किया गया है वह वैक्टरों को जटिल संख्याओं के रूप में मान रहा है। उन्हें त्रिकोणमिति की आवश्यकता नहीं है और घूर्णन को जोड़ने, गुणा करने या गोल करने के लिए बहुत सहज हो सकते हैं, खासकर जब से आप पहले से ही अपने शीर्षकों को संख्याओं के जोड़े के रूप में प्रतिनिधित्व करते हैं।
यदि आप उनसे परिचित नहीं हैं, तो निर्देश वास्तविक घटक होने के साथ + b (i) के रूप में व्यक्त किए जाते हैं और b (i) काल्पनिक है। यदि आप कार्टेसियन प्लेन की कल्पना करते हैं कि X वास्तविक है और Y काल्पनिक है, तो 1 पूर्व (दाएं) होगा, मैं उत्तर में होगा।
यहाँ मुख्य भाग है: 8 कार्डिनल दिशाओं को उनके वास्तविक और काल्पनिक घटकों के लिए विशेष रूप से संख्या 1, -1 या 0 के साथ दर्शाया गया है। तो आपको बस इतना करना है कि अपने एक्स को कम करना है, वाई एक अनुपात के रूप में समन्वय करता है और दिशा पाने के लिए निकटतम पूरी संख्या में दोनों को गोल करता है।
NW (-1 + i) N (i) NE (1 + i)
W (-1) Origin E (1)
SW (-1 - i) S (-i) SE (1 - i)
शीर्ष-से-निकटतम विकर्ण रूपांतरण के लिए, X और Y दोनों को आनुपातिक रूप से कम करें ताकि बड़ा मान 1 या -1 हो। सेट
// 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));
}
मूल रूप से (10, -2) के दोनों घटकों को गोल करना आपको 1 + 0 (i) या 1. देता है, इसलिए निकटतम दिशा पूर्व है।
उपरोक्त को वास्तव में एक जटिल संख्या संरचना के उपयोग की आवश्यकता नहीं है, लेकिन उनके बारे में इस तरह की सोच 8 कार्डिनल दिशाओं को खोजने के लिए तेज बनाती है। यदि आप दो या दो से अधिक वैक्टर की नेट हेडिंग प्राप्त करना चाहते हैं तो आप वेक्टर गणित को सामान्य तरीके से कर सकते हैं। (जटिल संख्या के रूप में, आप नहीं जोड़ते, लेकिन परिणाम के लिए गुणा करें)
Max(x, y)
Max(Abs(x, y))
नकारात्मक चतुर्थांश के लिए काम करना चाहिए । मैंने इसकी कोशिश की और izb के समान परिणाम मिला - यह गलत दिशाओं पर कम्पास दिशाओं को स्विच करता है। मुझे लगता है कि जब हेडिंग होगी तो यह स्विच करेगा। हेडिंग / हेडिंग। एक्स 0.5 को पार करता है (इसलिए 0 से 1 तक गोल वैल्यू स्विच होता है), जो आर्कटिक (0.5) = 26.565 ° है।
यह काम करने लगता है:
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"};
}
ई = 0, पूर्वोत्तर = 1, एन = 2, NW = 3, डब्ल्यू = 4, दप = 5, एस = 6, एसई = 7
f (x, y) = mod ((4-2 * (1 + साइन (x)) * (1-साइन (y ^ 2)) - (2 + साइन (x)) * साइन (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)
जब आप एक स्ट्रिंग चाहते हैं:
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)
यह आपको बिटफ़िल्ड का उपयोग करके स्थिरांक देता है:
// 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
थोड़ा-सा प्रदर्शन सुधार <
-चेक को संबंधित >
-चेक की शाखा में डाल दिया जाएगा , लेकिन मैंने ऐसा करने से परहेज किया क्योंकि यह पठनीयता को नुकसान पहुँचाता है।
if (x > 0.9) dir |= DIR_E
और बाकी सभी के आधार पर लिख सकते हैं । यह फिलिप के मूल कोड से बेहतर होना चाहिए और L2 मानदंड और atan2 का उपयोग करने से थोड़ा सस्ता होना चाहिए। शायद या शायद नही।