簡體   English   中英

如何判斷一個點是在線的右側還是左側

[英]How to tell whether a point is to the right or left side of a line

我有一組要點。 我想將它們分成兩個不同的集合。 為此,我選擇了兩個點( ab )並在它們之間畫了一條假想線。 現在我想把這條線左邊的所有點都放在一組里,把這條線右邊的點放在另一組里。

我如何判斷任何給定點z是在左側還是右側? 我試圖計算a-zb之間的角度——小於 180 的角度在右側,大於 180 的角度在左側——但是由於 ArcCos 的定義,計算出的角度總是小於 180°。 是否有計算大於 180° 的角度的公式(或選擇右側或左側的任何其他公式)?

試試這個使用叉積的代碼:

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。

使用向量(AB,AM)行列式的符號,其中M(X,Y)是查詢點:

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

線上為0 ,一側為+1 ,另一側為-1

你看行列式的符號

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

一側的點為正,另一側為負(直線本身的點為零)。

矢量(y1 - y2, x2 - x1)垂直於線,並且始終指向右側(或始終指向左側,如果您的平面方向與我的不同)。

然后,您可以計算該向量與(x3 - x1, y3 - y1)的點積,以確定該點是否與垂直向量(點積 > 0 )位於直線的同一側。

使用直線ab方程,在與要排序的點相同的 y 坐標處獲得直線上的 x 坐標。

  • 如果點的 x > 線的 x,則該點位於線的右側。
  • 如果點的 x < 線的 x,點在線的左邊。
  • 如果點的 x == 線的 x,則點在線上。

首先檢查是否有垂直線:

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

現在為xy插入(x3, y3) 下面是一些偽代碼,詳細說明應該發生什么:

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

我在 java 中實現了這個並運行了一個單元測試(來源如下)。 上述解決方案均無效。 這段代碼通過了單元測試。 如果有人發現未通過的單元測試,請告訴我。

代碼: 注意:如果兩個數字非常接近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));
}

我想提供一個受物理學啟發的解決方案。

想象一下沿線施加的力,您正在測量該點周圍的力的扭矩。 如果扭矩為正(逆時針),則該點位於直線的“左側”,但如果扭矩為負,則該點位於直線的“右側”。

所以如果力矢量等於定義線的兩點的跨度

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

假設點是 (Ax,Ay) (Bx,By) 和 (Cx,Cy),您需要計算:

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

如果點 C 位於由點 A 和 B 形成的線上,則該值將為零,並且根據邊的不同將具有不同的符號。 這是哪一側取決於您的 (x,y) 坐標的方向,但您可以將 A、B 和 C 的測試值插入此公式以確定負值是向左還是向右。

這是一個版本,再次使用交叉產品邏輯,用 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

@AVB 在 ruby​​ 中的回答

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

如果det為正則其上方,如果負則其下方。 如果為0,它就行了。

基本上,我認為有一個更簡單直接的解決方案,對於任何給定的多邊形,假設由四個頂點(p1,p2,p3,p4)組成,在多邊形中找到兩個極端相反的頂點,在另一個話,找到例如最左上角的頂點(比方說 p1)和位於最右下角的相反頂點(比方說)。 因此,鑒於您的測試點 C(x,y),現在您必須在 C 和 p1 以及 C 和 p4 之間進行雙重檢查:

if cx > p1x AND cy > p1y ==> 表示 C 位於 p1 的下方和右側 if cx < p2x AND cy < p2y ==> 表示 C 位於 p4 的上方和左側

結論,C在矩形內。

謝謝 :)

現有解決方案的問題:

雖然我發現 Eric Bainville 的回答是正確的,但我發現它完全不足以理解:

  • 兩個向量怎么會有行列式? 我認為這適用於矩陣?
  • 什么是sign
  • 如何將兩個向量轉換為矩陣?

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

  • 什么是Bx
  • 什么是Y Y不是一個向量,而不是一個標量嗎?
  • 為什么解決方案是正確的——其背后的原因是什么?

此外,我的用例涉及復雜的曲線而不是簡單的直線,因此需要進行一些重新調整:

重構答案

Point a = new Point3d(ax, ay, az); // point on line
Point b = new Point3d(bx, by, bz); // point on line

如果您想查看您的點是否高於/低於曲線,那么您需要獲得您感興趣的特定曲線的一階導數 - 也稱為曲線上點的切線。 如果你能這樣做,那么你就可以突出你的興趣點。 當然,如果你的曲線是一條線,那么你只需要沒有切線的興趣點。 切線是直線。

Vector3d lineVector = curve.GetFirstDerivative(a); // where "a" is a point on the curve. You may derive point b with a simple displacement calculation:

Point3d b = new Point3d(a.X, a.Y, a.Z).TransformBy(
                 Matrix3d.Displacement(curve.GetFirstDerivative(a))
                );

Point m = new Point3d(mx, my, mz) // the point you are interested in.

解決方案:

return (b.X - a.X) * (m.Y - a.Y) - (b.Y - a.Y) * (m.X - a.X) < 0; // the answer

為我工作! 請參閱上圖中的證明。 青磚滿足條件,但外面的磚被過濾掉了。 在我的用例中 - 我只想要接觸圓圈的磚塊。

所有綠色項目都在曲線內

答案背后的理論

我會回來解釋這個。 總有一天。 不知何故...

了解 netters 提供的解決方案的另一種方法是了解一些幾何含義。

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: T 在 pr 線上
  2. i+j <1: T 對 Sq
  3. i+j >1: Snq 上的 T
  4. i+j =0: T = Q
  5. i+j <0:Sq 和 Q 之后的 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

並且i+j+k=1意味着 T 在平面 [P,Q,R] 上並且i+j+k-1的符號意味着它的邊性。 由此我們有:

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

和 A,B 在平面 [P,Q,R] 的同一側,如果

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

直線方程是 y-y1 = m(x-x1)

這里 m 是 y2-y1 / x2-x1

現在將 m 放入方程並將條件放在 y < m(x-x1) + y1 上,然后它是左側點

例如。

for i in rows:

  for j in cols:

    if j>m(i-a)+b:

      image[i][j]=0

A(x1,y1) B(x2,y2) 長度為 L=sqrt( (y2-y1)^2 + (x2-x1)^2 ) 的線段

和一個點 M(x,y)

進行坐標變換,以便成為新的起點 A 和新 X 軸的點 B

我們有了點 M 的新坐標

這是 newX = ((x-x1) (x2-x1)+(y-y1) (y2-y1)) / L
從 (x-x1)*cos(t)+(y-y1)*sin(t) 其中 cos(t)=(x2-x1)/L, sin(t)=(y2-y1)/L

newY = ((y-y1) (x2-x1)-(x-x1) (y2-y1)) / L
從 (y-y1)*cos(t)-(x-x1)*sin(t)

因為“左”是 Y 為正的 X 軸的一側,如果 newY(即 M 與 AB 的距離)為正,則它在 AB 的左側(新的 X 軸)您可以省略除以 L(始終為正),如果您只想要符號

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM