x와 y로 표현 된 2D 벡터가 있다면, 그것을 가장 가까운 나침반 방향으로 변환하는 좋은 방법은 무엇입니까?
예 :
x:+1, y:+1 => NE
x:0, y:+3 => N
x:+10, y:-2 => E // closest compass direction
x와 y로 표현 된 2D 벡터가 있다면, 그것을 가장 가까운 나침반 방향으로 변환하는 좋은 방법은 무엇입니까?
예 :
x:+1, y:+1 => NE
x:0, y:+3 => N
x:+10, y:-2 => E // closest compass direction
답변:
가장 간단한 방법은 atan2()
Tetrad가 주석에서 제안한 것처럼을 사용하여 벡터의 각도를 얻은 다음 (가상 코드)와 같이 크기를 조정하고 둥글게하는 것입니다.
// 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π 로 나누면 라디안에서 전체 원의 분수로 변환되고 8을 곱하면 8/8의 원으로 변환 한 다음 가장 가까운 정수로 반올림합니다. 마지막으로, 랩 어라운드를 처리하기 위해 모듈로 8을 줄이므로 0과 8이 모두 동쪽으로 올바르게 매핑됩니다.atan2()
이유 + 8
I 과거 상기 스킵 일부 언어이다 atan2()
부정적인 결과를 반환 할 수있다 (예들 - π 에 + π 대신 0~2에서보다 π )와 나머지 연산자 ( %
마이너스 값을 반환하도록 정의 될 수있다) 부정 인수 (또는 부정 인수에 대한 동작은 정의되지 않았을 수 있음). 8
축소 전에 입력에 (즉, 한 번의 전체 회전)을 추가 하면 다른 방식으로 결과에 영향을 미치지 않으면 서 인수가 항상 양수입니다.
언어에서 편리한 가장 가까운 함수로 반올림하지 않으면 잘리는 정수 변환을 대신 사용하여 다음과 같이 인수에 0.5를 추가하십시오.
int octant = int( 8 * angle / (2*PI) + 8.5 ) % 8; // int() rounds down
일부 언어에서 기본 부동 소수점 정수 변환은 음수 입력을 아래로가 아니라 0으로 올림합니다. 이는 입력이 항상 양수인지 확인하는 또 다른 이유입니다.
물론, 8
해당 줄에서 발생하는 모든 항목을 다른 숫자 (예 : 16 진지도에있는 경우 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
enum :을 사용하십시오 { E, N, W, S }
.
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]
정밀도를 높이려면 방향 열거 형에 값을 추가하면됩니다.
이 알고리즘은 나침반 주위의 증가하는 값을 확인하여 마지막으로 확인한 위치와 새 위치 사이에 각도가 있는지 확인합니다. 이것이 우리가 -PI + 증가 / 2에서 시작하는 이유입니다. 각 방향 주위에 동일한 공간을 포함하도록 검사를 오프셋하려고합니다. 이 같은:
West의 반환 값 atan2()
이 불연속 이기 때문에 West는 두 개로 나뉩니다 .
atan2
것이지만 0 도는 북쪽이 아니라 동쪽 일 것입니다.
angle >=
위의 코드 에서 점검 할 필요는 없습니다 . 예를 들어 각도가 45보다 작은 경우 북쪽이 이미 반환되었으므로 동쪽 확인에 대해 각도> = 45인지 확인할 필요가 없습니다. 마찬가지로 서쪽으로 돌아 오기 전에 수표를 전혀 필요로하지 않습니다. 유일한 가능성입니다.
if
16 방향 이상으로 가고 싶다면 많은 말을하지 마십시오 .
벡터를 다룰 때마다 특정 프레임에서 각도로 변환하는 대신 기본 벡터 작업을 고려하십시오.
쿼리 벡터 v
와 단위 벡터 집합이 주어지면 s
가장 정렬 된 벡터는 s_i
최대화 하는 벡터 입니다 dot(v,s_i)
. 이는 매개 변수에 대해 고정 길이가 주어진 내적은 방향이 같은 벡터의 경우 최대 값을 가지며 반대 방향 인 벡터의 경우 최소값을 가지므로 매끄럽게 변경되기 때문입니다.
이것은 2보다 큰 차원으로 일반화되고, 임의의 방향으로 확장 가능하며 무한 그라데이션과 같은 프레임 특정 문제를 겪지 않습니다.
구현 측면에서, 이것은 각 기본 방향의 벡터에서 해당 방향을 나타내는 식별자 (열, 문자열, 필요한 것)와 연결되는 것으로 요약됩니다. 그런 다음 방향 설정을 반복하여 가장 높은 내적을 가진 방향을 찾습니다.
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는 실제 구성 요소이고 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이됩니다. 가장 가까운 방향은 동쪽입니다.
위의 내용은 실제로 복잡한 숫자 구조를 사용할 필요는 없지만 그렇게 생각하면 8 가지 기본 방향을 더 빨리 찾을 수 있습니다. 둘 이상의 벡터의 순 표제를 얻으려면 일반적인 방법으로 벡터 수학을 수행 할 수 있습니다. (복소수이므로 더하기가 아니라 결과에 곱하기)
Max(x, y)
Max(Abs(x, y))
마이너스 사분면에서 작동 해야합니다 . 나는 그것을 시도하고 izb와 같은 결과를 얻었다-이것은 나침반 각도를 잘못된 각도로 바꾼다. heading.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 + sign (x)) * (1-sign (y ^ 2))-(2 + sign (x)) * sign (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
약간의 성능 향상은 <
-checks를 해당 >
-checks 의 else-branch에 넣는 것이지만 가독성에 해를 끼치기 때문에 그렇게하지 않았습니다.
if (x > 0.9) dir |= DIR_E
나머지를 기반으로 작은 점검 테이블을 작성할 수 있습니다. L2 표준 및 atan2를 사용하는 것보다 Phillipp의 원래 코드보다 낫고 약간 저렴해야합니다. 아마 .. 아니면 아닐 수도 있습니다.