점이 선의 오른쪽인지 왼쪽인지 알려주는 방법


130

포인트가 있습니다. 나는 그것들을 2 개의 별개의 세트로 나누고 싶다. 이를 위해 두 개의 점 ( ab )을 선택하고 그 사이에 가상의 선을 그립니다. 이제 한 줄 에이 줄에서 남은 모든 점과 다른 줄 에이 줄에서 나온 모든 점을 갖고 싶습니다.

주어진 점 z 에 대해 왼쪽 또는 오른쪽 세트인지 어떻게 알 수 있습니까? azb 사이의 각도를 계산하려고 했습니다. 180보다 작은 각도는 오른쪽에 있고 왼쪽에는 180보다 큽니다. 그러나 ArcCos의 정의로 인해 계산 된 각도는 항상 180 °보다 작습니다. 180 °보다 큰 각도를 계산하는 공식이 있습니까 (또는 오른쪽 또는 왼쪽을 선택하는 다른 공식)?


오른쪽 또는 왼쪽은 어떻게 정의됩니까? A) P1에서 P2로 또는 B) 평면에서 선의 왼쪽 또는 오른쪽으로 볼 때.
phkahler

2
명확히하기 위해 질문의 두 번째 부분에서 acos () 대신 atan2 ()를 사용하여 올바른 각도를 계산할 수 있습니다. 그러나 Eric Bainville이 지적한 것처럼 크로스 제품을 사용하는 것이 가장 좋은 솔루션입니다.
dionyziz

아래의 많은 솔루션은 점 a와 b (선을 정의하는 데 사용하는 점)를 교환하면 반대 답변을 제공하기 때문에 작동하지 않습니다. Clojure에서 두 점을 사전 식으로 먼저 정렬하여 세 번째 점과 비교하는 솔루션을 제공합니다.
Purplejacket

답변:


202

벡터 결정자의 부호를 사용하십시오. (AB,AM)여기서 M(X,Y)쿼리 지점은 다음과 같습니다.

position = sign((Bx - Ax) * (Y - Ay) - (By - Ay) * (X - Ax))

그것은 0선상에 있고 +1, 한쪽 -1에는 다른쪽에 있습니다.


10
주의해야 할 점이 하나 있습니다. 반올림 오류는 점이 거의 선에있을 때 문제가 될 수 있습니다. 대부분의 사용 에는 문제 가되지 않지만 때때로 사람들을 문지릅니다.
Stephen Canon

16
이 테스트에서 반올림 오류로 인해 문제가 발생하는 상황에서 Jon Shewchuk의 "계산 기하학을위한 빠른 견고성 조건"을 찾아보십시오.
Stephen Canon

14
명확하게하기 위해, 이것은 선 (ba)과 벡터 사이의 교차 곱 (cross product)의 Z 성분과 동일합니다 (ma). 좋아하는 벡터 클래스에서 : position = sign ((ba) .cross (ma) [2])
larsmoa

3
A & B를 바꾸지 않고 같은 줄을 유지하지만 positions? 의 부호를 변경하십시오 .
Jayen

6
예. A, B는 "A에 서서 B를 볼 때 왼쪽으로"와 같이 방향을 정의합니다.
Eric Bainville

224

교차 제품을 사용하는이 코드를 사용해보십시오 .

public bool isLeft(Point a, Point b, Point c){
     return ((b.X - a.X)*(c.Y - a.Y) - (b.Y - a.Y)*(c.X - a.X)) > 0;
}

여기서 a = 라인 포인트 1; b = 라인 포인트 2; c = 점검 할 지점.

수식이 0과 같으면 점이 동일 선상에있는 것입니다.

선이 가로이면 점이 선 위에 있으면 true를 반환합니다.


6
선이 수직이라면?
Tofeeq Ahmad

9
내적을 의미합니까?
Baiyan Huang

13
@ lzprgmr : 아니오, 이것은 2D 행렬의 결정 요인 인 교차 곱입니다. 행 (a, b) 및 (c, d)로 정의 된 2D 행렬을 고려하십시오. 결정자는 ad-bc입니다. 위의 형식은 2 개의 점으로 표시되는 선을 하나의 벡터 (a, b)로 변환 한 다음 PointA와 PointC를 사용하여 (c, d)를 얻기 위해 다른 벡터 를 정의합니다 . (a, b) = (PointB.x- PointA.x, PointB.y-PointA.y) (c, d) = (PointC.x-PointA.x, PointC.y-PointA.y) 결정 요인은 게시물에 명시된 것과 같습니다.
AndyG

6
이것이 크로스 제품인지 또는 도트 제품인지에 대한 혼란은 그것이 2 차원이기 때문이라고 생각합니다. 그것은 2 차원 외적 : mathworld.wolfram.com/CrossProduct.html는
brianmearns

4
가치있는 것은 이것을 약간 단순화 할 수 return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);있지만 컴파일러는 어쨌든 최적화 할 것입니다.
Nicu Stiurca

44

당신은 결정 요인의 표시를 봅니다.

| x2-x1  x3-x1 |
| y2-y1  y3-y1 |

한쪽의 점은 양수이고 다른 쪽은 음수입니다 (선 자체의 점은 0).


1
이 답변을 확장하면 사람들이 교차 제품의 모양을 모를 경우에 대비합니다. 다음 단계는 시각 ((X1-X2) * (Y1-Y3)) - (+ (Y2 - Y1) * (X1-X3))
프랭키 리베라

10

벡터 (y1 - y2, x2 - x1)는 선에 수직이며 항상 오른쪽을 가리 킵니다 (평면 방향이 광산과 다른 경우 항상 왼쪽을 가리 킵니다).

그런 다음 해당 벡터의 내적을 계산하고 점이 (x3 - x1, y3 - y1)수직 벡터와 같은 선의 측면에 있는지 여부를 확인할 수 있습니다 (내적> 0).


5

ab방정식을 사용하여 정렬 할 점과 동일한 y 좌표에서 선의 x 좌표를 가져옵니다.

  • 점의 x> 선의 x이면 점은 선의 오른쪽에 있습니다.
  • 점의 x <선의 x이면 점은 선의 왼쪽에 있습니다.
  • 점의 x == 선의 x이면 점이 선에있는 것입니다.

첫 번째 답변에 대한 Aaginor의 의견에서 알 수 있듯이 지점이 DIRECTED 라인 AB의 왼쪽 또는 오른쪽에 있는지 여부를 파악하고 싶지 않기 때문에 잘못되었습니다. B쪽으로 왼쪽이나 오른쪽에 있습니까?
dionyziz

1
@dionyziz-응? 내 대답은 AB를 통해 줄에 "방향"을 할당하지 않습니다. 내 대답은 "왼쪽"이 corrdinate 시스템의 -x 방향이라고 가정합니다. 답은 벡터 AB 를 정의하고 교차 곱을 사용하여 좌변 을 정의하기로 결정했습니다 . 원래 질문은 "왼쪽"의 의미를 지정하지 않습니다.
mbeckish

3
참고 : 답변으로 승인 된 교차 제품 방법 대신이 방법을 사용하는 경우 선이 수평에 가까워 질 때 함정에 유의하십시오. 수학 오류는 증가하고 정확하게 수평 인 경우 무한대에 도달합니다. 해결책은 두 점 사이의 델타가 큰 축을 사용하는 것입니다. (또는 아마도 더 작은 델타 .. 이것은 내 머리 꼭대기에서 벗어난 것입니다.)
ToolmakerSteve

이것은 내가 찾던 것입니다. A가 B보다 위 또는 아래에 있는지 알고 싶지 않습니다. 나는 그것이 줄의 왼쪽 (음의 x 방향)인지 알고 싶습니다!
Jayen

5

먼저 세로줄이 있는지 확인하십시오.

if (x2-x1) == 0
  if x3 < x2
     it's on the left
  if x3 > x2
     it's on the right
  else
     it's on the line

그런 다음 기울기를 계산하십시오. m = (y2-y1)/(x2-x1)

그런 다음 점 경사 형식을 사용하여 선의 방정식을 작성하십시오 y - y1 = m*(x-x1) + y1. 설명을 위해 기울기 절편 형태로 단순화하십시오 (알고리즘에는 필요하지 않음) y = mx+b.

이제 플러그 (x3, y3)xy. 다음은 어떻게해야하는지 자세히 설명하는 의사 코드입니다.

if m > 0
  if y3 > m*x3 + b
    it's on the left
  else if y3 < m*x3 + b
    it's on the right
  else
    it's on the line
else if m < 0
  if y3 < m*x3 + b
    it's on the left
  if y3 > m*x3+b
    it's on the right
  else
    it's on the line
else
  horizontal line; up to you what you do

3
실패 : 수직선에 대해 경사 계산이 유효하지 않습니다. 끝없는 if / else 물건. 이것이 OP가 왼쪽 / 오른쪽의 의미인지 확실하지 않습니다. 따라서 90도 회전하면 "위"가 오른쪽 또는 왼쪽이므로이 코드를 반으로 자릅니다.
phkahler

1
이 답변에는 몇 가지 문제가 있습니다. 수직선은 0으로 나누기를 유발합니다. 게다가 선의 기울기가 양수인지 음수인지에 대해 걱정하지 않기 때문에 실패합니다.

2
@ phkahler는 수직선 문제를 해결했습니다. 확실히 하나의 테스트 사례를 잊어 버린 것이 아니라 친절한 단어에 감사드립니다. "끝없는 if / else"는 수학적 이론을 설명하는 것입니다. OP의 질문에는 프로그래밍에 대한 언급이 없습니다. @woodchips는 수직선 문제를 해결했습니다. 기울기는 변수 m입니다. 그것이 긍정적인지 부정인지를 확인합니다.
maksim

5

나는 이것을 자바로 구현하고 단위 테스트 (아래 소스)를 실행했다. 위의 해결책 중 어느 것도 작동하지 않습니다. 이 코드는 단위 테스트를 통과합니다. 누구도 통과하지 못하는 단위 테스트를 찾으면 알려주십시오.

코드 : 참고 : nearlyEqual(double,double)두 숫자가 매우 가까운 경우 true를 반환합니다.

/*
 * @return integer code for which side of the line ab c is on.  1 means
 * left turn, -1 means right turn.  Returns
 * 0 if all three are on a line
 */
public static int findSide(
        double ax, double ay, 
        double bx, double by,
        double cx, double cy) {
    if (nearlyEqual(bx-ax,0)) { // vertical line
        if (cx < bx) {
            return by > ay ? 1 : -1;
        }
        if (cx > bx) {
            return by > ay ? -1 : 1;
        } 
        return 0;
    }
    if (nearlyEqual(by-ay,0)) { // horizontal line
        if (cy < by) {
            return bx > ax ? -1 : 1;
        }
        if (cy > by) {
            return bx > ax ? 1 : -1;
        } 
        return 0;
    }
    double slope = (by - ay) / (bx - ax);
    double yIntercept = ay - ax * slope;
    double cSolution = (slope*cx) + yIntercept;
    if (slope != 0) {
        if (cy > cSolution) {
            return bx > ax ? 1 : -1;
        }
        if (cy < cSolution) {
            return bx > ax ? -1 : 1;
        }
        return 0;
    }
    return 0;
}

단위 테스트는 다음과 같습니다.

@Test public void testFindSide() {
    assertTrue("1", 1 == Utility.findSide(1, 0, 0, 0, -1, -1));
    assertTrue("1.1", 1 == Utility.findSide(25, 0, 0, 0, -1, -14));
    assertTrue("1.2", 1 == Utility.findSide(25, 20, 0, 20, -1, 6));
    assertTrue("1.3", 1 == Utility.findSide(24, 20, -1, 20, -2, 6));

    assertTrue("-1", -1 == Utility.findSide(1, 0, 0, 0, 1, 1));
    assertTrue("-1.1", -1 == Utility.findSide(12, 0, 0, 0, 2, 1));
    assertTrue("-1.2", -1 == Utility.findSide(-25, 0, 0, 0, -1, -14));
    assertTrue("-1.3", -1 == Utility.findSide(1, 0.5, 0, 0, 1, 1));

    assertTrue("2.1", -1 == Utility.findSide(0,5, 1,10, 10,20));
    assertTrue("2.2", 1 == Utility.findSide(0,9.1, 1,10, 10,20));
    assertTrue("2.3", -1 == Utility.findSide(0,5, 1,10, 20,10));
    assertTrue("2.4", -1 == Utility.findSide(0,9.1, 1,10, 20,10));

    assertTrue("vertical 1", 1 == Utility.findSide(1,1, 1,10, 0,0));
    assertTrue("vertical 2", -1 == Utility.findSide(1,10, 1,1, 0,0));
    assertTrue("vertical 3", -1 == Utility.findSide(1,1, 1,10, 5,0));
    assertTrue("vertical 3", 1 == Utility.findSide(1,10, 1,1, 5,0));

    assertTrue("horizontal 1", 1 == Utility.findSide(1,-1, 10,-1, 0,0));
    assertTrue("horizontal 2", -1 == Utility.findSide(10,-1, 1,-1, 0,0));
    assertTrue("horizontal 3", -1 == Utility.findSide(1,-1, 10,-1, 0,-9));
    assertTrue("horizontal 4", 1 == Utility.findSide(10,-1, 1,-1, 0,-9));

    assertTrue("positive slope 1", 1 == Utility.findSide(0,0, 10,10, 1,2));
    assertTrue("positive slope 2", -1 == Utility.findSide(10,10, 0,0, 1,2));
    assertTrue("positive slope 3", -1 == Utility.findSide(0,0, 10,10, 1,0));
    assertTrue("positive slope 4", 1 == Utility.findSide(10,10, 0,0, 1,0));

    assertTrue("negative slope 1", -1 == Utility.findSide(0,0, -10,10, 1,2));
    assertTrue("negative slope 2", -1 == Utility.findSide(0,0, -10,10, 1,2));
    assertTrue("negative slope 3", 1 == Utility.findSide(0,0, -10,10, -1,-2));
    assertTrue("negative slope 4", -1 == Utility.findSide(-10,10, 0,0, -1,-2));

    assertTrue("0", 0 == Utility.findSide(1, 0, 0, 0, -1, 0));
    assertTrue("1", 0 == Utility.findSide(0,0, 0, 0, 0, 0));
    assertTrue("2", 0 == Utility.findSide(0,0, 0,1, 0,2));
    assertTrue("3", 0 == Utility.findSide(0,0, 2,0, 1,0));
    assertTrue("4", 0 == Utility.findSide(1, -2, 0, 0, -1, 2));
}

2

점이 (Ax, Ay) (Bx, By) 및 (Cx, Cy)라고 가정하면 다음을 계산해야합니다.

(Bx-Ax) * (Cy-Ay)-(By-Ay) * (Cx-Ax)

점 C가 점 A와 B로 형성된 선 위에 있으면 0과 같고 측면에 따라 다른 부호를 갖습니다. 어느 쪽이 (x, y) 좌표의 방향에 따라 달라지나 A, B 및 C의 테스트 값을이 수식에 연결하여 음수 값이 왼쪽인지 오른쪽인지를 결정할 수 있습니다.


2

물리에서 영감을 얻은 솔루션을 제공하고 싶었습니다.

선을 따라 가해지는 힘을 상상해 보시면 점에 대한 힘의 토크를 측정하는 것입니다. 토크가 양수 (시계 반대 방향)이면 점은 선의 "왼쪽"에 있지만 토크가 음수이면 점은 선의 "오른쪽"입니다.

따라서 힘 벡터가 선을 정의하는 두 점의 범위와 같으면

fx = x_2 - x_1
fy = y_2 - y_1

다음 테스트 (px,py)의 부호에 따라 포인트의 측면을 테스트합니다.

var torque = fx*(py-y_1)-fy*(px-x_1)
if  torque>0  then
     "point on left side"
else if torque <0 then
     "point on right side"  
else
     "point on line"
end if

1

기본적으로, 나는 주어진 다각형에 대해 훨씬 쉽고 직선적 인 해결책이 있다고 생각합니다 .4 개의 정점 (p1, p2, p3, p4)으로 구성하고 다각형에서 두 개의 정반대 정점을 찾으십시오. 즉, 예를 들어 가장 왼쪽 상단 정점 (p1이라고 말함)과 가장 오른쪽 하단에 위치한 반대 정점을 찾습니다 (말씀). 따라서 테스트 지점 C (x, y)가 주어지면 이제 C와 p1과 C와 p4 사이를 다시 확인해야합니다.

cx> p1x AND cy> p1y ==>가 cx <p2x AND cy <p2y ==>가 C가 p4의 위와 왼쪽임을 의미하면 C가 p1보다 낮고 오른쪽에 있음을 의미하는 경우

결론적으로 C는 사각형 안에 있습니다.

감사 :)


1
(1) 요청 된 것과 다른 질문에 대답합니까? 사각형이 두 축과 정렬 될 때 "바운드 박스"테스트와 같은 소리가납니다. (2)보다 자세하게는 4 점 사이의 가능한 관계를 가정합니다. 예를 들어, 사각형을 가지고 45도 회전하여 다이아몬드를 갖습니다. 그 다이아몬드에는 "왼쪽 상단 지점"과 같은 것이 없습니다. 가장 왼쪽 지점이 맨 위 또는 맨 아래가 아닙니다. 물론 4 점은 낯선 모양을 형성 할 수도 있습니다. 예를 들어 한 방향으로 3 포인트 떨어져 있고 다른 방향으로 4 포인트 떨어져있을 수 있습니다. 계속 노력하십시오!
ToolmakerSteve

1

루비에서 @AVB의 답변

det = Matrix[
  [(x2 - x1), (x3 - x1)],
  [(y2 - y1), (y3 - y1)]
].determinant

경우 det그 아래는 위의 경우 부정적 긍정적이다. 0이면 라인에 있습니다.


1

Clojure로 작성된 교차 제품 논리를 사용하는 버전이 있습니다.

(defn is-left? [line point]
  (let [[[x1 y1] [x2 y2]] (sort line)
        [x-pt y-pt] point]
    (> (* (- x2 x1) (- y-pt y1)) (* (- y2 y1) (- x-pt x1)))))

사용법 예 :

(is-left? [[-3 -1] [3 1]] [0 10])
true

다시 말해, 점 (0, 10)은 (-3, -1) 및 (3, 1)에 의해 결정된 선의 왼쪽에 있습니다.

참고 :이 구현은 다른 (지금까지) 아무도하지 않는 문제를 해결합니다! 선을 결정하는 포인트를 줄 때 순서가 중요 합니다. 즉, 그것은 어떤 의미에서 "지시 된 선"입니다. 따라서 위의 코드에서이 호출은 다음과 같은 결과를 생성합니다 true.

(is-left? [[3 1] [-3 -1]] [0 10])
true

이 코드 스 니펫 때문입니다.

(sort line)

마지막으로 다른 교차 제품 기반 솔루션과 마찬가지로이 솔루션은 부울을 반환하며 공선성에 대한 세 번째 결과를 제공하지 않습니다. 그러나 다음과 같은 의미가 있습니다.

(is-left? [[1 1] [3 1]] [10 1])
false

0

네터가 제공하는 솔루션 느낌을 얻는 다른 방법은 약간의 지오메트리 의미를 이해하는 것입니다.

pqr = [P, Q, R]이 선 [P, R] 으로 2 개의면으로 나뉘어 진 평면을 형성하는 점이라고 하자 . pqr 평면 A, B의 두 점이 같은쪽에 있는지 확인해야합니다.

pqr 평면의 모든 점 T 는 2 개의 벡터로 나타낼 수 있습니다. v = PQ 및 u = RQ

T '= TQ = i * v + j * u

이제 기하학적 의미 :

  1. i + j = 1 : pr 라인의 T
  2. i + j <1 : 스퀘어의 T
  3. i + j> 1 : Snq의 T
  4. i + j = 0 : T = Q
  5. i + j <0 : Sq 및 T를 초과하는 T

i+j: <0 0 <1 =1 >1 ---------Q------[PR]--------- <== this is PQR plane ^ pr line

일반적으로

  • i + j는 T가 Q 또는 선 [P, R]에서 얼마나 떨어져 있는지 측정 한 값 이며
  • i + j-1 의 부호는 T의 측면을 의미합니다.

ij 의 다른 지오메트리 의미 (이 솔루션과 관련이 없음)는 다음과 같습니다.

  • i , j 는 새로운 좌표계에서 T에 대한 스칼라이며 여기서 v, u 는 새로운 축이고 Q 는 새로운 원점입니다.
  • i , j 는 각각 P, R에 대한 당기는 힘 으로 볼 수 있습니다 . i가 클수록 T가 R 에서 멀어 집니다 ( P 에서 더 큰 당김 ).

i, j 의 값은 방정식을 풀어서 얻을 수 있습니다.

i*vx + j*ux = T'x
i*vy + j*uy = T'y
i*vz + j*uz = T'z

그래서 우리는 비행기에서 2 점 A, B를받습니다 :

A = a1 * v + a2 * u B = b1 * v + b2 * u

A, B가 같은쪽에 있다면, 이것은 사실입니다 :

sign(a1+a2-1) = sign(b1+b2-1)

이것은 질문에도 적용됩니다. A, B는 [P, Q, R] 평면의 같은쪽에 있습니다 .

T = i * P + j * Q + k * R

그리고 난 + J + K = 1 T 비행기 [P, Q, R]과의 부호에 있음을 의미 I + J +는 K-1 의 sideness을 의미한다. 이것으로부터 우리는 :

A = a1 * P + a2 * Q + a3 * R B = b1 * P + b2 * Q + b3 * R

A, B는 평면 [P, Q, R]의 같은면에 있습니다.

sign(a1+a2+a3-1) = sign(b1+b2+b3-1)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.