두 벡터 사이의 시계 방향 각도를 직접 계산하는 방법


79

두 벡터 (2D, 3D) 사이의 시계 방향 각도를 알고 싶습니다.

내적의 기본적인 방법은 내적 각도 (0-180도)를 제공하며 결과가 필요한 각도인지 또는 그 보수인지 결정하기 위해 if 문을 사용해야합니다.

시계 방향 각도를 직접 계산하는 방법을 알고 있습니까?


6
왜 사용하지 std::atan2()않습니까?

2
3D 벡터의 "시계 방향 각도"를 어떻게 정의합니까?
Martin R

@ H2CO3 이것은 2D 각도에 가장 적합한 솔루션 인 것 같습니다.
미르 Ispas

@MartinR "시계 방향"은 가장 가까운 "방향"이 아닌 특정 "방향"으로 각도를 원한다고 말하는 일반적인 용어입니다. Nickolay O.는 그의 대답에 describind이 "방향"의 방법 지정
미르 Ispas을

4
@Felics : "시계 방향"은 2D에서는 잘 정의되지만 3D에서는 정의되지 않습니다. 외적의 z 좌표를 확인하는 것은 (Nickolay O.의 답변에서와 같이) 3D에서 "x / y 평면에서 위에서 바라 보는 관찰자의 경우 시계 방향"을 의미합니다.
Martin R

답변:


192

2D 케이스

내적 이 각도의 코사인에 비례하는 것처럼 행렬식 은 사인에 비례합니다. 따라서 다음과 같이 각도를 계산할 수 있습니다.

dot = x1*x2 + y1*y2      # dot product between [x1, y1] and [x2, y2]
det = x1*y2 - y1*x2      # determinant
angle = atan2(det, dot)  # atan2(y, x) or atan2(sin, cos)

이 각도의 방향은 좌표계의 방향과 일치합니다. A의 좌표 왼손잡이 시스템 , 즉 x는 오른쪽을 가리키는 및 Y를 컴퓨터 그래픽을위한 공통으로 아래로, 이것은 당신이 시계 방향으로 각도에 대한 긍정적 인 신호를 얻을 의미합니다. 좌표계의 방향이 y를 위로 하여 수학적이면 수학 의 관례대로 시계 반대 방향의 각도를 얻습니다. 입력 순서를 변경하면 기호가 변경되므로 기호가 마음에 들지 않으면 입력을 바꾸십시오.

3D 케이스

3D에서 임의로 배치 된 두 벡터는 둘 다에 수직 인 자체 회전 축을 정의합니다. 이 회전축은 고정 된 방향으로 제공되지 않으므로 회전 각도의 방향을 고유하게 고정 할 수 없습니다. 한 가지 일반적인 규칙은 각도를 항상 양수로 만들고 양의 각도에 맞도록 축 방향을 지정하는 것입니다. 이 경우 정규화 된 벡터의 내적은 각도를 계산하기에 충분합니다.

dot = x1*x2 + y1*y2 + z1*z2    #between [x1, y1, z1] and [x2, y2, z2]
lenSq1 = x1*x1 + y1*y1 + z1*z1
lenSq2 = x2*x2 + y2*y2 + z2*z2
angle = acos(dot/sqrt(lenSq1 * lenSq2))

3D에 포함 된 평면

한 가지 특별한 경우는 벡터가 임의로 배치되지 않고 알려진 법선 벡터 n이 있는 평면 내에있는 경우 입니다. 그러면 회전 축도 n 방향이되고 n 방향은 해당 축의 방향을 고정합니다. 이 경우 n행렬식 에 포함하여 위의 2D 계산 을 적용하여 크기를 3 × 3으로 만들 수 있습니다.

dot = x1*x2 + y1*y2 + z1*z2
det = x1*y2*zn + x2*yn*z1 + xn*y1*z2 - z1*y2*xn - z2*yn*x1 - zn*y1*x2
angle = atan2(det, dot)

이것이 작동하는 한 가지 조건은 정규 벡터 n 에 단위 길이가 있다는 것입니다. 그렇지 않은 경우 정규화해야합니다.

트리플 제품으로

이 결정자 는 제안 된 편집에서 @Excrubulent가 지적한 것처럼 트리플 제품 으로 표현 될 수도 있습니다 .

det = n · (v1 × v2)

이것은 일부 API에서 구현하기가 더 쉬울 수 있으며 여기서 진행되는 작업에 대한 다른 관점을 제공합니다. 외적은 각도의 사인에 비례하고 평면에 수직이므로 n 의 배수가 됩니다. 따라서 내적은 기본적으로 해당 벡터의 길이를 측정하지만 올바른 기호가 부착되어 있습니다.


4
찬성 투표를하세요-다른 답변이 올바른지 아닌지 고민 할 수 없습니다. 귀하의 답변이 가장 명확하고 가장 읽기 쉬우므로 저에게 도움이되었습니다.
Excrubulent

2
2D의 경우 (0,180) 및 (-180,0)을 얻습니다. 결과가 음수인지 확인하고 멋진 시계 방향 각도를 얻기 위해 360을 더할 수 있습니다 (예 : -180이면 360 더하기 180, -90이면 270 더하기 등). 그것이 내 계산인지 아니면 qAtan2(y, x)(Qt 프레임 워크에서) 구현 인지 모르겠지만 누군가 나와 같은 문제가 있다면 도움이 될 수 있습니다.
rbaleksandar dec.

9
@rbaleksandar : atan2일반적으로 [-180 °, 180 °] 범위에 있습니다. 얻으려면 [0 °, 360 °] 경우의 구분없이, 하나는 교체 할 수 있습니다 atan2(y,x)atan2(-y,-x) + 180°.
MvG

1
@jianz : 각도는 좌표계에 대한 양의 각도입니다. 경우에 x는 오른쪽이고 Y가 최대이고, 그 각은 시계 반대 방향이다. 경우 y는 아래로, 그것은 시계 방향입니다. 대부분의 컴퓨터 그래픽 환경은 후자를 사용합니다. 방향을 바꾸려면 입력 순서를 변경하면 행렬식의 부호가 반전됩니다.
MvG

3
Noooooo 절대로 내적을 받아들이지 마세요! 그것은 수학적으로 정확하지만 실제로는 끔찍하게 부정확합니다. 3d 방법을 다른 atan2 (det, dot); 이 경우 det는 외적의 길이입니다.
Don Hatch

5

각도를 계산하려면 atan2(v1.s_cross(v2), v1.dot(v2))2D 케이스 를 호출하면 됩니다. s_cross교차 생산의 스칼라 아날로그는 어디에 있습니까 (평행 사변형의 부호있는 영역). 웨지 생산이 될 2D 케이스의 경우. 3D의 경우 시계 방향으로 회전을 정의해야합니다. 평면의 한 쪽에서 시계 방향은 한 방향이고 다른 쪽에서는 다른 방향이므로 =)

편집 : 이것은 시계 반대 방향 각도이고 시계 방향 각도는 반대입니다.


v1.cross (v2)는 스칼라가 아닌 벡터이며 이와 같이 사용할 수 없습니다. Nickolay O.는 각도의 '방향'을 찾는 방법을 그의 답변에서 설명합니다. 2D 각도를 얻을 수있는 한 가지 방법은 다음과 같습니다 각도 = atan2f (버전 2.x, v2.y) - atan2f (v1.x, v1.y)
미르 Ispas

1
@Felics 2D 교차 생산에서 종종 쐐기 생산을 의미합니다. en.wikipedia.org/wiki/Wedge_product 이것은 평행 사변형의 서명 된 영역입니다. 2D의 경우 공식은 dot = | v1 || v2 | * cos 및 cross = | v1 || v2 | sin이므로 절대적으로 정확합니다. 이것이 atan2가 전체 원 범위에서 정확한 각도를 제공하는 이유입니다. 나는 3 차원 경우에 말했듯이 그리고 당신은 시계 방향으로 방향을 일부 확장해야 할 몇 가지 가정을 할 필요가
kassak

1
@Felics : atan2f첫 번째 인수로 y 좌표가 있으므로 angle = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x).
Martin R

1
@kassak : 당신은 대체 할 수 crossdot2 차원 경우 명시 적 공식에 의해,에 대한 모든 의심을 제거 할 것이라고 cross3 차원 벡터를 반환 (하지만 당신이 무시할 수는 권장 사항입니다). -그렇지 않으면 하나의 atan2f함수 호출 만 필요하기 때문에이 솔루션을 좋아합니다 .
Martin R

@Martin R 좋은 조언에 감사드립니다. 나는 명확 식의 의미를 만드는 일부 수정했습니다
kassak

4

이 답변은 MvG와 동일하지만 다르게 설명합니다 (MvG의 솔루션이 작동하는 이유를 이해하려는 노력의 결과입니다). 다른 사람들이 유용하다고 생각할 기회가있을 때 게시하고 있습니다.

주어진 법선 ( ) 의 관점에 대한 theta에서에서 x까지의 시계 반대 방향 각도 는 다음과 같이 지정됩니다.yn||n|| = 1

atan2 (dot (n, cross (x, y)), dot (x, y))

(1) = atan2 (|| x || || y || sin (theta), || x || || y || cos (theta))

(2) = atan2 (sin (theta), cos (theta))

(3) = x 축과 벡터 사이의 반 시계 방향 각도 (cos (theta), sin (theta))

(4) = 세타

여기서는 ||x||의 크기를 나타냅니다 x.

단계 (1)은

십자가 (x, y) = || x || || y || sin (theta) n,

그래서

dot (n, cross (x, y))

= dot (n, || x || || y || sin (theta) n)

= || x || || y || sin (theta) dot (n, n)

이것은

|| x || || y || sin (세타)

만약 ||n|| = 1 .

단계 (2)의 정의로부터 다음 atan2것을주의 atan2(cy, cx) = atan2(y,x)여기서, c는 스칼라이다. 단계 (3)은 atan2. 단계 (4)는 cos및 의 기하학적 정의를 따릅니다 sin.


2

두 벡터의 스칼라 (점) 곱을 사용하면 두 벡터 사이 각도의 코사인을 얻을 수 있습니다. 각도의 '방향'을 얻으려면 외적도 계산해야합니다. z 좌표를 통해 각도가 시계 방향인지 아닌지 (즉, 360도에서 추출해야하는지 여부)를 확인할 수 있습니다.


1
이것조차도 내가 피하고 싶은 것입니다. 어떤 값을 계산하고 계산 된 값이 내 각도 또는 내 각도의 보수를 나타내는 지 결정하는 것입니다.
미르 Ispas

이것이 가능한지 알고 싶습니다 :) 더 나은 방법이 있다면 (아마도!) 일을하는 비효율적 인 방법을 사용하는 이유. 더 나은 방법이 없다면 나는 "표준"일 것입니다. 그러나 더 나은 것을 요구하는 것이 항상 좋습니다!
Mircea Ispas 2012

) 사실, 표준 방법을 항상 효율적이지
Nickolay Olshevsky

@NickolayOlshevsky z 좌표를 통해 확인 한다는 것은 정확히 무엇을 의미 합니까? 어떻게해야합니까?
Ogen 2014-04-12

내가 기억하는 한 z 좌표의 부호를 확인해야합니다.
Nickolay Olshevsky 2014

1

2D 방법의 경우 코사인 법칙과 "방향"방법을 사용할 수 있습니다.

세그먼트 P3 : P2에서 시계 방향으로 스위핑하는 세그먼트 P3 : P1의 각도를 계산합니다.

 
    P1 P2

        P3
    double d = direction(x3, y3, x2, y2, x1, y1);

    // c
    int d1d3 = distanceSqEucl(x1, y1, x3, y3);

    // b
    int d2d3 = distanceSqEucl(x2, y2, x3, y3);

    // a
    int d1d2 = distanceSqEucl(x1, y1, x2, y2);

    //cosine A = (b^2 + c^2 - a^2)/2bc
    double cosA = (d1d3 + d2d3 - d1d2)
        / (2 * Math.sqrt(d1d3 * d2d3));

    double angleA = Math.acos(cosA);

    if (d > 0) {
        angleA = 2.*Math.PI - angleA;
    }

This has the same number of transcendental

위의 제안과 같은 연산과 하나 이상의 부동 소수점 연산 만 있습니다.

사용하는 방법은 다음과 같습니다.

 public int distanceSqEucl(int x1, int y1, 
    int x2, int y2) {

    int diffX = x1 - x2;
    int diffY = y1 - y2;
    return (diffX * diffX + diffY * diffY);
}

public int direction(int x1, int y1, int x2, int y2, 
    int x3, int y3) {

    int d = ((x2 - x1)*(y3 - y1)) - ((y2 - y1)*(x3 - x1));

    return d;
}

0

"직접적인 방법"이란 if 진술을 , 정말 일반적인 해결책은 없다고 생각합니다.

그러나 특정 문제가 각도 이산화에서 정밀도를 잃을 수 있고 유형 변환에서 시간을 잃어도 괜찮다면 [-pi, pi) 허용되는 파이 각도 범위를 일부 부호있는 정수 유형의 허용 범위에 매핑 할 수 있습니다. . 그러면 무료로 상보성을 얻을 수 있습니다. 그러나 나는 실제로이 트릭을 실제로 사용하지 않았습니다. 대부분의 경우 float-to-integer 및 integer-to-float 변환의 비용이 직접성의 이점을 능가합니다. 이 각도 계산이 많이 수행되면 자동 벡터화 또는 병렬화 가능한 코드 작성에 대한 우선 순위를 설정하는 것이 좋습니다.

또한 문제 세부 정보가 각도 방향에 대해 더 확실한 결과가있을 수있는 경우 컴파일러의 내장 함수를 사용하여이 정보를 컴파일러에 제공 할 수 있으므로 분기를보다 효율적으로 최적화 할 수 있습니다. 예를 들어 gcc의 경우 그 __builtin_expect기능입니다. (리눅스 커널에서와 같이) 이러한 매크로 likelyunlikely매크로 로 래핑 할 때 사용하는 것이 다소 더 편리합니다 .

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)

-1

xa, ya와 xb, yb의 두 벡터 사이의 시계 방향 각도, 2D 케이스에 대한 공식.

각도 (vec.a-vec, b) = pi () / 2 * ((1+ 기호 (ya)) * (1- 기호 (xa ^ 2))-(1+ 기호 (yb)) * (1- 부호 (xb ^ 2)))

                        +pi()/4*((2+sign(ya))*sign(xa)-(2+sign(yb))*sign(xb))

                        +sign(xa*ya)*atan((abs(ya)-abs(xa))/(abs(ya)+abs(xa)))

                        -sign(xb*yb)*atan((abs(yb)-abs(xb))/(abs(yb)+abs(xb)))

-2

복사하여 붙여 넣기 만하면됩니다.

angle = (acos((v1.x * v2.x + v1.y * v2.y)/((sqrt(v1.x*v1.x + v1.y*v1.y) * sqrt(v2.x*v2.x + v2.y*v2.y))))/pi*180);

천만에요 ;-)


5
이 코드 스 니펫은 문제 해결에 도움 이되는 이유방법에 대한 설명을 포함하여 질문에 답할 수 있지만 , 특히 이와 같은 오래된 질문과 관련하여 답변의 품질과 수명을 향상시킵니다. "좋은 답변은 어떻게 작성합니까?"를 참조하십시오. .
slothiful 19
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.