Làm thế nào để biết một điểm nằm ở bên phải hay bên trái của một dòng


130

Tôi có một bộ điểm. Tôi muốn tách chúng thành 2 bộ riêng biệt. Để làm điều này, tôi chọn hai điểm ( ab ) và vẽ một đường tưởng tượng giữa chúng. Bây giờ tôi muốn có tất cả các điểm còn lại từ dòng này trong một bộ và những điểm nằm ngay từ dòng này trong bộ khác.

Làm thế nào tôi có thể nói cho bất kỳ điểm z đã cho dù nó ở bên trái hay bên phải? Tôi đã cố gắng tính góc giữa azb - các góc nhỏ hơn 180 nằm ở phía bên tay phải, lớn hơn 180 ở phía bên tay trái - nhưng do định nghĩa của ArcCos, các góc tính toán luôn nhỏ hơn 180 °. Có một công thức để tính các góc lớn hơn 180 ° (hoặc bất kỳ công thức nào khác để chọn bên phải hoặc bên trái) không?


Làm thế nào là phải hay trái được định nghĩa? A) về mặt nhìn từ P1 đến P2 hoặc B) bên trái hoặc bên phải của đường thẳng trong mặt phẳng.
phkahler

2
Để làm rõ, đến phần thứ hai của câu hỏi của bạn, bạn có thể sử dụng atan2 () thay vì acos () để tính góc chính xác. Tuy nhiên, sử dụng một sản phẩm chéo là giải pháp tốt nhất cho điều này như Eric Bainville đã chỉ ra.
dionyziz

Nhiều giải pháp dưới đây không hoạt động vì chúng đưa ra câu trả lời ngược lại nếu bạn trao đổi điểm a và b (các điểm mà chúng tôi đang sử dụng để xác định đường của chúng tôi). Tôi đưa ra một giải pháp trong Clojure sắp xếp hai điểm theo từ vựng trước khi so sánh chúng với điểm thứ ba.
Purplejquet

Câu trả lời:


202

Sử dụng dấu của định thức của vectơ (AB,AM), M(X,Y)điểm truy vấn:

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

0ở trên dòng, và +1ở một bên, -1ở bên kia.


10
+1 tốt, với một điều cần lưu ý: lỗi làm tròn có thể là một mối quan tâm khi điểm rất gần trên đường thẳng. Không phải là một vấn đề cho hầu hết các sử dụng, nhưng nó thỉnh thoảng cắn người.
Stephen Canon

16
Nếu bạn thấy mình trong một tình huống mà lỗi làm tròn trong bài kiểm tra này gây ra vấn đề cho bạn, bạn sẽ muốn tra cứu "Dự đoán nhanh mạnh mẽ cho hình học tính toán" của Jon Shewchuk.
Stephen Canon

14
Để làm rõ, đây giống như thành phần Z của sản phẩm chéo giữa dòng (ba) và vectơ đến điểm từ a (ma). Trong lớp vectơ yêu thích của bạn: vị trí = dấu ((ba) .cross (ma) [2])
larsmoa

3
sẽ không trao đổi A & B giữ cùng một dòng, nhưng thay đổi dấu hiệu của positions?
Jayen

6
Đúng. A, B xác định hướng, như trong "bên trái của bạn khi đứng tại A và nhìn vào B".
Eric Bainville

224

Hãy thử mã này sử dụng một sản phẩm chéo :

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;
}

Trong đó a = điểm 1; b = điểm 2; c = điểm để kiểm tra chống lại.

Nếu công thức bằng 0, các điểm là colinear.

Nếu đường thẳng nằm ngang thì điều này trả về giá trị true nếu điểm nằm trên đường thẳng.


6
Nếu đường thẳng đứng thì sao?
Tofeeq Ahmad

9
bạn có nghĩa là sản phẩm chấm?
Baiyan Huang

13
@lzprgmr: Không, đây là sản phẩm chéo, tương đương là yếu tố quyết định của ma trận 2D. Xét ma trận 2D được xác định bởi các hàng (a, b) và (c, d). Yếu tố quyết định là quảng cáo - bc. Biểu mẫu ở trên là biến đổi một dòng được biểu thị bởi 2 điểm thành một vectơ, (a, b), sau đó xác định một vectơ khác bằng PointA và PointC để lấy (c, d): (a, b) = (PointB.x - PointA.x, PointB.y - PointA.y) (c, d) = (PointC.x - PointA.x, PointC.y - PointA.y) Do đó, định thức này giống như đã nêu trong bài.
AndyG

6
Tôi nghĩ rằng sự nhầm lẫn về việc đây là sản phẩm chéo hay sản phẩm chấm là bởi vì nó có hai chiều. Đây sản phẩm chéo, có hai chiều: mathworld.wolfram.com/Cross SẢNt.html
brianmearns

4
Đối với những gì nó có giá trị, điều này có thể được đơn giản hóa một chút return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);, nhưng trình biên dịch có thể tối ưu hóa điều đó bằng mọi cách.
Nicu Stiurca

44

Bạn nhìn vào dấu hiệu của yếu tố quyết định

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

Nó sẽ dương cho các điểm ở một bên và âm ở bên kia (và 0 cho các điểm trên chính đường đó).


1
Mở rộng về câu trả lời này, trong trường hợp mọi người không biết sản phẩm chéo trông như thế nào. Bước trực quan tiếp theo là ((x2-x1) * (y3-y1)) - ((y2 - y1) * (x3-x1))
Franky Rivera

10

Vectơ (y1 - y2, x2 - x1)vuông góc với đường thẳng và luôn luôn chỉ phải (hoặc luôn luôn chỉ sang trái, nếu hướng máy bay của bạn khác với hướng của tôi).

Sau đó, bạn có thể tính tích của sản phẩm chấm của vectơ đó và (x3 - x1, y3 - y1)để xác định xem điểm có nằm cùng phía của đường thẳng với vectơ vuông góc (sản phẩm chấm> 0) hay không.


5

Sử dụng phương trình của đường ab , lấy tọa độ x trên đường thẳng có cùng tọa độ y với điểm cần sắp xếp.

  • Nếu điểm x> của dòng x, điểm nằm ở bên phải của dòng.
  • Nếu điểm x <line's x, điểm nằm ở bên trái của dòng.
  • Nếu điểm x == của dòng x, điểm nằm trên đường thẳng.

Điều này là sai, vì như bạn có thể thấy từ nhận xét của Aaginor về câu trả lời đầu tiên, chúng tôi không muốn tìm hiểu xem điểm nằm ở bên trái hay bên phải của dòng AB TRỰC TIẾP, tức là nếu bạn đang đứng trên A và nhìn về phía B là bên trái hay bên phải của bạn?
dionyziz

1
@dionyziz - Hả? Câu trả lời của tôi không chỉ định "hướng" cho đường thẳng qua AB. Câu trả lời của tôi giả sử "trái" là hướng -x của hệ thống định hướng. Câu trả lời được chấp nhận đã chọn xác định vectơ AB và xác định bên trái bằng cách sử dụng sản phẩm chéo. Câu hỏi ban đầu không xác định nghĩa của "trái" là gì.
mbeckish

3
LƯU Ý: Nếu bạn sử dụng phương pháp này (chứ không phải là sản phẩm chéo đã được phê duyệt là câu trả lời), hãy lưu ý đến một cạm bẫy khi đường tiếp cận theo chiều ngang. Các lỗi toán học tăng lên và chạm vô cực nếu chính xác theo chiều ngang. Giải pháp là sử dụng trục nào có đồng bằng lớn hơn giữa hai điểm. (Hoặc có thể là đồng bằng nhỏ hơn .. đây là trên đỉnh đầu của tôi.)
ToolmakerSteve

đây hoàn toàn là những gì tôi đang tìm kiếm tôi không muốn biết A ở trên hay dưới B. Tôi chỉ muốn biết liệu nó có trái (hướng x âm) của dòng không!
Jayen

5

Trước tiên hãy kiểm tra nếu bạn có một đường thẳng đứng:

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

Sau đó, tính độ dốc: m = (y2-y1)/(x2-x1)

Sau đó, tạo một phương trình của đường bằng cách sử dụng dạng dốc điểm : y - y1 = m*(x-x1) + y1. Để giải thích cho tôi, hãy đơn giản hóa nó thành dạng chặn dốc (không cần thiết trong thuật toán của bạn) : y = mx+b.

Bây giờ cắm vào (x3, y3)cho xy. Dưới đây là một số mã giả chi tiết những gì sẽ xảy ra:

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
Fail: Tính toán độ dốc không hợp lệ cho các đường thẳng đứng. Vô tận nếu / thứ khác. Không chắc chắn nếu đó là ý nghĩa của OP bởi trái / phải - nếu vậy nhìn vào nó xoay 90 độ sẽ cắt mã này xuống một nửa vì "ở trên" sẽ là phải hoặc trái.
phkahler

1
Câu trả lời này có một số vấn đề. Các đường thẳng đứng gây ra sự phân chia bằng không. Tệ hơn, nó thất bại bởi vì nó không lo lắng về độ dốc của đường là dương hay âm.

2
@phkahler, đã khắc phục sự cố đường dọc. Chắc chắn không phải là một thất bại vì quên một trường hợp thử nghiệm nhưng cảm ơn vì những lời tốt đẹp. "Vô tận nếu / khác" là để giải thích lý thuyết toán học; Không có gì trong câu hỏi của OP đề cập đến lập trình. @woodchips, đã khắc phục sự cố đường dọc. Độ dốc là biến m; Tôi kiểm tra khi nó là tích cực hay tiêu cực.
maksim

5

Tôi đã thực hiện điều này trong java và chạy thử nghiệm đơn vị (nguồn bên dưới). Không có giải pháp nào ở trên hoạt động. Mã này vượt qua bài kiểm tra đơn vị. Nếu bất cứ ai tìm thấy một bài kiểm tra đơn vị không vượt qua, xin vui lòng cho tôi biết.

Mã: LƯU Ý: nearlyEqual(double,double)trả về true nếu hai số rất gần nhau.

/*
 * @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;
}

Đây là bài kiểm tra đơn vị:

@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

Giả sử các điểm là (Ax, Ay) (Bx, By) và (Cx, Cy), bạn cần tính toán:

(Bx - Axe) * (Cy - Ay) - (Bởi - Ay) * (Cx - Axe)

Điều này sẽ bằng 0 nếu điểm C nằm trên đường thẳng được tạo bởi các điểm A và B, và sẽ có một dấu hiệu khác nhau tùy thuộc vào bên. Mặt này phụ thuộc vào hướng của tọa độ (x, y) của bạn, nhưng bạn có thể cắm các giá trị thử nghiệm cho A, B và C vào công thức này để xác định xem giá trị âm nằm ở bên trái hay bên phải.


2

Tôi muốn cung cấp một giải pháp lấy cảm hứng từ vật lý.

Hãy tưởng tượng một lực tác dụng dọc theo đường thẳng và bạn đang đo mô-men xoắn của lực về điểm. Nếu mô-men xoắn dương (ngược chiều kim đồng hồ) thì điểm nằm ở "bên trái" của đường thẳng, nhưng nếu mô-men xoắn âm thì điểm đó là "bên phải" của đường.

Vì vậy, nếu vectơ lực bằng nhịp của hai điểm xác định đường thẳng

fx = x_2 - x_1
fy = y_2 - y_1

bạn kiểm tra bên cạnh một điểm (px,py)dựa trên dấu hiệu của bài kiểm tra sau

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

về cơ bản, tôi nghĩ rằng có một giải pháp dễ dàng và dễ dàng hơn nhiều, đối với bất kỳ đa giác đã cho nào, giả sử bao gồm bốn đỉnh (p1, p2, p3, p4), tìm hai đỉnh cực đối diện trong đa giác, trong một đa giác khác từ, tìm ví dụ đỉnh cao nhất bên trái (giả sử p1) và đỉnh đối diện nằm ở phía dưới bên phải (giả sử). Do đó, với điểm kiểm tra C (x, y) của bạn, bây giờ bạn phải thực hiện kiểm tra hai lần giữa C và p1 và C và p4:

if cx> p1x AND cy> p1y ==> có nghĩa là C thấp hơn và bên phải của p1 bên cạnh nếu cx <p2x AND cy <p2y ==> có nghĩa là C nằm trên và bên trái của p4

kết luận, C nằm bên trong hình chữ nhật.

Cảm ơn :)


1
(1) Trả lời một câu hỏi khác với câu hỏi? Âm thanh như kiểm tra "hộp giới hạn", khi một hình chữ nhật được căn chỉnh với cả hai trục. (2) Chi tiết hơn: đưa ra giả định về các mối quan hệ có thể có giữa 4 điểm. Ví dụ: lấy một hình chữ nhật và xoay nó 45 độ, để bạn có một viên kim cương. Không có thứ gọi là "điểm trên cùng bên trái" trong viên kim cương đó. Điểm ngoài cùng bên trái không phải là trên cùng hoặc dưới cùng nhất. Và tất nhiên, 4 điểm có thể tạo thành những hình dạng thậm chí xa lạ. Ví dụ: 3 điểm có thể ở xa theo một hướng và điểm thứ 4 theo hướng khác. Tiếp tục cố gắng!
ToolmakerSteve

1

Câu trả lời của @ AVB trong ruby

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

Nếu detlà dương của nó ở trên, nếu âm của nó ở dưới. Nếu 0, nó trên dòng.


1

Đây là một phiên bản, một lần nữa sử dụng logic sản phẩm chéo, được viết bằng 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)))))

Ví dụ sử dụng:

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

Nghĩa là điểm (0, 10) nằm bên trái của đường thẳng được xác định bởi (-3, -1) và (3, 1).

LƯU Ý: Việc triển khai này giải quyết một vấn đề mà không ai khác (cho đến nay) làm! Đặt hàng các vấn đề khi đưa ra các điểm xác định dòng. Tức là, đó là một "đường hướng", theo một nghĩa nào đó. Vì vậy, với đoạn mã trên, lời gọi này cũng tạo ra kết quả của true:

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

Đó là vì đoạn mã này:

(sort line)

Cuối cùng, như với các giải pháp dựa trên sản phẩm chéo khác, giải pháp này trả về giá trị boolean và không đưa ra kết quả thứ ba cho cộng tuyến. Nhưng nó sẽ cho một kết quả có ý nghĩa, ví dụ:

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

0

Một cách khác để có được cảm giác về các giải pháp được cung cấp bởi netters là hiểu một chút ý nghĩa hình học.

Đặt pqr = [P, Q, R] là các điểm tạo thành mặt phẳng được chia thành 2 cạnh theo đường [P, R] . Chúng ta phải tìm hiểu xem hai điểm trên mặt phẳng pqr , A, B, có cùng phía không.

Bất kỳ điểm T nào trên mặt phẳng pqr đều có thể được biểu diễn với 2 vectơ: v = PQ và u = RQ, như:

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

Bây giờ ý nghĩa hình học:

  1. i + j = 1: T trên dòng pr
  2. i + j <1: T trên Sq
  3. i + j> 1: T trên Snq
  4. i + j = 0: T = Q
  5. i + j <0: T trên Sq và ngoài Q.

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

Nói chung,

  • i + j là thước đo khoảng cách T cách Q hoặc đường [P, R]
  • dấu hiệu của i + j-1 ngụ ý sự ngang tàng của T.

Các ý nghĩa hình học khác của ij (không liên quan đến giải pháp này) là:

  • i , j là các vô hướng cho T trong một hệ tọa độ mới trong đó v, u là các trục mới và Q là gốc tọa độ mới;
  • i , j có thể được xem là lực kéo tương ứng cho P, R. I càng lớn , T càng xa R (lực kéo lớn hơn từ P ).

Giá trị của i, j có thể thu được bằng cách giải các phương trình:

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

Vậy ta được 2 điểm, A, B trên mặt phẳng:

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

Nếu A, B ở cùng một phía, điều này sẽ đúng:

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

Lưu ý rằng điều này cũng áp dụng cho câu hỏi: Có phải A, B ở cùng một phía của mặt phẳng [P, Q, R] , trong đó:

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

i + j + k = 1 ngụ ý rằng T nằm trên mặt phẳng [P, Q, R] và dấu hiệu của i + j + k-1 ngụ ý tính ngang của nó. Từ đây, chúng ta có:

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

và A, B cùng phía với mặt phẳng [P, Q, R] nếu

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.