หากคุณมีเวกเตอร์ 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()
ตามที่ Tetrad แนะนำในการคอมเม้นต์จากนั้นทำการสเกลและปัดเศษมันเช่น (pseudocode):
// 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
บรรทัดอาจต้องการคำอธิบายบางอย่าง ในทุกภาษาที่ฉันรู้ว่ามันมีอยู่atan2()
ฟังก์ชั่นจะคืนค่ามุมเป็นเรเดียน การหารด้วย 2 πจะแปลงจากเรเดียนเป็นเศษส่วนของวงกลมเต็มแล้วคูณด้วย 8 จากนั้นแปลงเป็นแปดของวงกลมซึ่งเราจะปัดเศษเป็นจำนวนเต็มที่ใกล้เคียงที่สุด ในที่สุดเราลดมันแบบโมดูโล 8 เพื่อดูแลการล้อมรอบเพื่อให้ทั้ง 0 และ 8 ถูกแมปอย่างถูกต้องไปทางทิศตะวันออก
เหตุผลของการ + 8
ที่ฉันข้ามไปด้านบนคือในบางภาษาatan2()
อาจส่งคืนผลลัพธ์เชิงลบ (เช่นจาก - πถึง + πมากกว่าจาก 0 ถึง 2 π ) และตัวดำเนินการโมดูโล ( %
) อาจถูกกำหนดให้ส่งคืนค่าลบสำหรับ ข้อโต้แย้งเชิงลบ (หรือพฤติกรรมของมันสำหรับข้อโต้แย้งเชิงลบอาจไม่ได้กำหนด) การเพิ่ม8
(เช่นหนึ่งรอบเต็ม) ไปยังอินพุตก่อนการลดจะช่วยให้มั่นใจได้ว่าข้อโต้แย้งนั้นเป็นผลบวกเสมอโดยไม่ส่งผลต่อผลลัพธ์ในทางอื่น
หากภาษาของคุณไม่ได้ให้ฟังก์ชั่นการปัดเศษที่ใกล้เคียงที่สุดคุณสามารถใช้การแปลงจำนวนเต็มที่ถูกตัดทอนแทนและเพียงเพิ่ม 0.5 ลงในอาร์กิวเมนต์เช่นนี้:
int octant = int( 8 * angle / (2*PI) + 8.5 ) % 8; // int() rounds down
โปรดทราบว่าในบางภาษาการแปลงแบบลอยเป็นจำนวนเต็มเริ่มต้นจะลบอินพุตที่เป็นศูนย์แทนที่จะเป็นลงซึ่งเป็นอีกสาเหตุหนึ่งที่ทำให้แน่ใจได้ว่าอินพุตนั้นเป็นค่าบวกเสมอ
แน่นอนคุณสามารถแทนที่เหตุการณ์ทั้งหมด8
ในบรรทัดนั้นด้วยหมายเลขอื่น (เช่น 4 หรือ 16 หรือ 6 หรือ 12 ถ้าคุณอยู่บนแผนที่ฐานสิบหก) เพื่อแบ่งวงกลมออกเป็นหลายทิศทาง เพียงแค่ปรับ enum / array ตาม
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 }
enum:
คุณมี 8 ตัวเลือก (หรือ 16 หรือมากกว่านั้นหากคุณต้องการความแม่นยำที่ละเอียดยิ่งขึ้น)
ใช้atan2(y,x)
เพื่อให้ได้มุมสำหรับเวกเตอร์ของคุณ
atan2()
ทำงานในวิธีต่อไปนี้:
ดังนั้น x = 1, y = 0 จะส่งผลให้เป็น 0, และมันไม่ต่อเนื่องที่ x = -1, y = 0, มีทั้งπและ-π
ตอนนี้เราเพียงต้องการแมปเอาท์พุทของ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]
ตอนนี้เพื่อเพิ่มความแม่นยำมากขึ้นเพียงแค่เพิ่มค่าให้กับทิศทาง enum
อัลกอริทึมทำงานโดยการตรวจสอบค่าที่เพิ่มขึ้นรอบ ๆ เข็มทิศเพื่อดูว่ามุมของเราวางอยู่ระหว่างจุดที่เราตรวจสอบล่าสุดกับตำแหน่งใหม่หรือไม่ นั่นเป็นเหตุผลที่เราเริ่มต้นด้วย -PI + increment / 2 เราต้องการออฟเซ็ตเช็คของเราเพื่อรวมพื้นที่ที่เท่ากันในแต่ละทิศทาง บางสิ่งเช่นนี้
ทิศตะวันตกแตกเป็นสองส่วนเนื่องจากค่าส่งคืนของatan2()
ที่ทิศตะวันตกไม่ต่อเนื่อง
atan2
แม้ว่าโปรดจำไว้ว่า 0 องศาน่าจะเป็นทิศตะวันออกและไม่ใช่ทิศเหนือ
angle >=
ตรวจสอบในรหัสข้างต้น; ตัวอย่างเช่นถ้ามุมน้อยกว่า 45 แล้วทิศเหนือจะถูกส่งคืนแล้วดังนั้นคุณไม่จำเป็นต้องตรวจสอบว่ามุม> = 45 สำหรับมุมตะวันออก ในทำนองเดียวกันคุณไม่จำเป็นต้องมีการตรวจสอบใด ๆ ก่อนที่จะกลับไปทางตะวันตก - มันเป็นความเป็นไปได้เท่านั้นที่เหลืออยู่
if
คำแถลงมากมายหากคุณต้องการไป 16 ทิศทางหรือมากกว่านั้น
เมื่อใดก็ตามที่คุณกำลังจัดการกับเวกเตอร์ให้พิจารณาการดำเนินการเวกเตอร์ขั้นพื้นฐานแทนการแปลงเป็นมุมในเฟรมเฉพาะบางอย่าง
เมื่อระบุเวกเตอร์แบบสอบถามv
และชุดเวกเตอร์หน่วยเวกเตอร์s
ที่จัดเรียงมากที่สุดคือเวกเตอร์s_i
ที่ขยายให้ใหญ่สุดdot(v,s_i)
ที่เพิ่มนี่เป็นเพราะผลิตภัณฑ์จุดที่กำหนดความยาวคงที่สำหรับพารามิเตอร์มีค่าสูงสุดสำหรับเวกเตอร์ที่มีทิศทางเดียวกันและต่ำสุดสำหรับเวกเตอร์ที่มีทิศทางตรงข้ามเปลี่ยนไปอย่างราบรื่นในระหว่าง
สิ่งนี้ทำให้ภาพรวมมีมิติมากกว่าสองเล็กน้อยโดยทั่วไปสามารถขยายได้ตามทิศทางที่กำหนดเองและไม่เกิดปัญหาเฉพาะเฟรมเช่นการไล่ระดับสีไม่สิ้นสุด
การนำไปปฏิบัติอย่างชาญฉลาดสิ่งนี้จะทำให้ความสัมพันธ์จากเวกเตอร์ในแต่ละทิศทางสำคัญกับตัวระบุ (enum, string, อะไรก็ตามที่คุณต้องการ) แสดงถึงทิศทางนั้น จากนั้นคุณจะวนซ้ำชุดทิศทางของคุณค้นหาทิศทางที่มีผลิตภัณฑ์จุดสูงสุด
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
กุญแจ? มันไม่ได้ดูจริงจังมาก
วิธีหนึ่งที่ไม่ได้กล่าวถึงในที่นี้คือการรักษาเวกเตอร์ให้เป็นจำนวนเชิงซ้อน พวกเขาไม่ต้องการตรีโกณมิติและสามารถใช้งานได้ง่ายในการเพิ่มการหมุนการคูณหรือการปัดเศษโดยเฉพาะอย่างยิ่งเมื่อคุณมีส่วนหัวของคุณแสดงเป็นคู่ของตัวเลข
ในกรณีที่คุณไม่คุ้นเคยกับพวกเขาทิศทางจะถูกแสดงในรูปของ a + b (i) โดยมีองค์ประกอบที่แท้จริงและ b (i) เป็นจินตภาพ ถ้าคุณนึกภาพระนาบคาร์ทีเซียนโดยที่ X เป็นจริงและ Y เป็นจินตภาพ 1 จะเป็นทิศตะวันออก (ขวา) ฉันจะอยู่ทางทิศเหนือ
นี่คือส่วนสำคัญ: ทิศทางที่สำคัญ 8 ทิศทางแสดงเฉพาะตัวเลข 1, -1 หรือ 0 สำหรับองค์ประกอบที่แท้จริงและจินตภาพ ดังนั้นสิ่งที่คุณต้องทำคือลดพิกัด X, Y ของคุณเป็นอัตราส่วนและปัดเศษให้เป็นจำนวนเต็มที่ใกล้เคียงที่สุดเพื่อให้ได้ทิศทาง
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 ดังนั้นทิศทางที่ใกล้ที่สุดคือทิศตะวันออก
ข้างต้นไม่จำเป็นต้องใช้โครงสร้างตัวเลขที่ซับซ้อน แต่การคิดถึงสิ่งเหล่านี้ทำให้การหาทิศทางสำคัญได้เร็วขึ้น คุณสามารถทำคณิตศาสตร์เวกเตอร์ได้ตามปกติถ้าคุณอยากได้หัวเรื่องสุทธิของเวกเตอร์สองตัวหรือมากกว่านั้น (ในจำนวนที่ซับซ้อนคุณจะไม่เพิ่ม แต่จะเพิ่มทวีคูณตามผลลัพธ์)
Max(x, y)
ควรMax(Abs(x, y))
ทำงานให้กับจตุภาคลบ ฉันลองแล้วได้ผลลัพธ์เช่นเดียวกับ izb - นี่สลับทิศทางเข็มทิศในมุมที่ผิด ฉันเดาว่ามันจะเปลี่ยนเมื่อ header.y / Heading.x ข้าม 0.5 (ดังนั้นค่าที่ปัดเศษจาก 0 ถึง 1) ซึ่งก็คือ arctan (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"};
}
E = 0 NE = 1, N = 2, NW = 3, W = 4, SW = 5, S = 6, SE = 7
f (x, y) = mod ((4-2 * (1 + เครื่องหมาย (x)) * (1-sign (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)
สิ่งนี้จะช่วยให้คุณมีค่าคงที่โดยใช้ bitfields:
// 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
การปรับปรุงประสิทธิภาพเล็กน้อยจะใส่<
-checks ในสาขาอื่นของ>
-checks ที่สอดคล้องกันแต่ฉันละเว้นจากการทำเช่นนั้นเพราะมันเป็นอันตรายต่อการอ่าน
if (x > 0.9) dir |= DIR_E
และส่วนที่เหลือทั้งหมด ควรดีกว่ารหัสต้นฉบับของ Phillipp และราคาถูกกว่าการใช้ L2 norm และ atan2 เล็กน้อย อาจจะ .. หรืออาจจะไม่