Cara mengetahui apakah suatu titik berada di sisi kanan atau kiri suatu garis


130

Saya punya satu set poin. Saya ingin memisahkan mereka menjadi 2 set yang berbeda. Untuk melakukan ini, saya memilih dua poin ( a dan b ) dan menggambar garis imajiner di antara mereka. Sekarang saya ingin memiliki semua poin yang tersisa dari baris ini dalam satu set dan mereka yang benar dari baris ini di set lainnya.

Bagaimana saya bisa tahu untuk titik tertentu z apakah itu di sebelah kiri atau di set yang tepat? Saya mencoba menghitung sudut antara azb - sudut yang lebih kecil dari 180 berada di sisi kanan, lebih besar dari 180 di sisi kiri - tetapi karena definisi ArcCos, sudut yang dihitung selalu lebih kecil dari 180 °. Apakah ada rumus untuk menghitung sudut lebih besar dari 180 ° (atau rumus lain untuk memilih sisi kanan atau kiri)?


Bagaimana definisi kanan atau kiri? A) dalam hal melihat dari P1 ke P2 atau B) ke kiri atau kanan garis di pesawat.
phkahler

2
Untuk memperjelas, pada bagian kedua dari pertanyaan Anda, Anda dapat menggunakan atan2 () alih-alih acos () untuk menghitung sudut yang benar. Namun, menggunakan produk silang adalah solusi terbaik untuk ini seperti yang ditunjukkan Eric Bainville.
dionyziz

Banyak solusi di bawah ini tidak berfungsi karena mereka memberikan jawaban yang berlawanan jika Anda menukar poin a dan b (poin yang kami gunakan untuk mendefinisikan baris kami). Saya memberikan solusi di Clojure yang mengurutkan dua poin secara leksikografis terlebih dahulu sebelum membandingkannya dengan poin ketiga.
Purplejacket

Jawaban:


202

Gunakan tanda penentu vektor (AB,AM), di mana M(X,Y)titik kueri:

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

Itu ada 0di garis, dan +1di satu sisi, -1di sisi lain.


10
+1 bagus, dengan satu hal yang harus diperhatikan: kesalahan pembulatan dapat menjadi perhatian ketika intinya sangat dekat. Bukan masalah untuk sebagian besar kegunaan, tetapi itu menggigit orang dari waktu ke waktu.
Stephen Canon

16
Jika Anda menemukan diri Anda dalam situasi di mana kesalahan pembulatan pada tes ini menyebabkan Anda mengalami masalah, Anda perlu mencari "Predikat Kuat Cepat untuk Geometri Komputasi" karya Jon Shewchuk.
Stephen Canon

14
Untuk klarifikasi, ini sama dengan komponen-Z dari produk silang antara garis (ba) dan vektor ke titik dari a (ma). Di kelas vektor favorit Anda: position = sign ((ba) .cross (ma) [2])
larsmoa

3
tidak akan menukar A & B menjaga garis yang sama, tetapi mengubah tanda positions?
Jayen

6
Iya. A, B mendefinisikan orientasi, seperti "di sebelah kiri Anda ketika berdiri di A dan melihat B".
Eric Bainville

224

Coba kode ini yang menggunakan produk silang :

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

Dimana a = garis titik 1; b = titik garis 2; c = titik untuk memeriksa.

Jika rumusnya sama dengan 0, poinnya adalah colinear.

Jika garis horizontal, maka ini mengembalikan true jika titik di atas garis.


6
Jika garis itu vertikal?
Tofeeq Ahmad

9
maksud Anda produk titik?
Baiyan Huang

13
@ lzprgmr: Tidak, ini adalah produk silang, yang setara dengan penentu matriks 2D. Pertimbangkan matriks 2D yang didefinisikan oleh baris (a, b) dan (c, d). Penentu adalah ad - bc. Bentuk di atas mengubah garis yang diwakili oleh 2 titik menjadi satu vektor, (a, b), dan kemudian mendefinisikan vektor lain menggunakan PointA dan PointC untuk mendapatkan (c, d): (a, b) = (PointB.x - PointA.x, PointB.y - PointA.y) (c, d) = (PointC.x - PointA.x, PointC.y - PointA.y) Oleh karena itu determinan sama seperti yang dinyatakan dalam pos.
AndyG

6
Saya pikir kebingungan mengenai apakah ini produk silang atau produk titik karena berada dalam dua dimensi. Ini adalah produk silang, dalam dua dimensi: mathworld.wolfram.com/CrossProduct.html
brianmearns

4
Untuk apa nilainya, ini bisa sedikit disederhanakan return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);, tetapi kompiler mungkin mengoptimalkannya.
Nicu Stiurca

44

Anda melihat tanda penentu

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

Ini akan menjadi positif untuk poin di satu sisi, dan negatif di sisi lain (dan nol untuk poin di garis itu sendiri).


1
Memperluas jawaban ini, Seandainya orang tidak tahu seperti apa produk silang itu. Langkah visual berikutnya adalah ((x2-x1) * (y3-y1)) - ((y2 - y1) * (x3-x1))
Franky Rivera

10

Vektor (y1 - y2, x2 - x1)tegak lurus terhadap garis, dan selalu menunjuk ke kanan (atau selalu menunjuk ke kiri, jika orientasi bidang Anda berbeda dari tambang).

Anda kemudian dapat menghitung titik produk dari vektor itu dan (x3 - x1, y3 - y1)untuk menentukan apakah titik tersebut terletak di sisi yang sama dari garis sebagai vektor tegak lurus (titik produk> 0) atau tidak.


5

Dengan menggunakan persamaan garis ab , dapatkan koordinat x pada garis pada koordinat y yang sama dengan titik yang akan diurutkan.

  • Jika titik x> garis x, intinya adalah di sebelah kanan garis.
  • Jika titik x <garis x, titiknya ada di sebelah kiri garis.
  • Jika titik x == garis x, titiknya ada di garis.

Ini salah, karena seperti yang Anda lihat dari komentar Aaginor pada jawaban pertama, kami tidak ingin mencari tahu apakah titiknya ada di kiri atau kanan garis AB LANGSUNG, yaitu jika Anda berdiri di atas A dan melihat menuju B apakah di sebelah kiri atau di kanan Anda?
dionyziz

1
@dionyziz - Hah? Jawaban saya tidak menetapkan "arah" ke garis melalui AB. Jawaban saya mengasumsikan "kiri" adalah arah -x dari sistem koreksi. Jawaban yang diterima memilih untuk mendefinisikan vektor AB, dan menentukan ke kiri menggunakan produk silang. Pertanyaan asli tidak menentukan apa yang dimaksud dengan "kiri".
mbeckish

3
CATATAN: Jika Anda menggunakan pendekatan ini (bukan produk-silang yang disetujui sebagai jawaban), waspadai jebakan ketika garis mendekati horizontal. Kesalahan matematika meningkat, dan mencapai tak terhingga jika persis horizontal. Solusinya adalah menggunakan sumbu mana pun yang memiliki delta lebih besar antara dua titik. (Atau mungkin delta yang lebih kecil .. ini di atas kepala saya.)
ToolmakerSteve

ini benar-benar apa yang saya cari. Saya tidak ingin tahu apakah A di atas atau di bawah B. Saya hanya ingin tahu apakah itu dibiarkan (negatif x arah) dari garis!
Jayen

5

Pertama periksa apakah Anda memiliki garis vertikal:

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

Kemudian, hitung kemiringan: m = (y2-y1)/(x2-x1)

Kemudian, membuat persamaan garis menggunakan formulir kemiringan titik: y - y1 = m*(x-x1) + y1. Demi penjelasan saya, menyederhanakan ke bentuk lereng-intercept (tidak diperlukan dalam algoritma Anda): y = mx+b.

Sekarang pasang (x3, y3)untuk xdan y. Berikut ini beberapa pseudocode yang merinci apa yang harus terjadi:

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
Gagal: Perhitungan kemiringan tidak valid untuk garis vertikal. Tak ada habisnya jika / hal lain. Tidak yakin apakah itu yang OP maksudkan dengan kiri / kanan - jika begitu melihatnya diputar 90 derajat akan memotong kode ini menjadi dua karena "di atas" akan kanan atau kiri.
phkahler

1
Jawaban ini memiliki beberapa masalah. Garis vertikal menyebabkan pembagian dengan nol. Lebih buruk, gagal karena tidak khawatir tentang kemiringan garis positif atau negatif.

2
@phkahler, perbaiki masalah garis vertikal. Jelas bukan kegagalan untuk melupakan satu test case tetapi terima kasih atas kata-kata yang baik. "Tak ada habisnya jika / yang lain" adalah untuk menjelaskan teori matematika; tidak ada dalam pertanyaan OP yang menyebutkan pemrograman. @ woodchips, perbaiki masalah garis vertikal. Kemiringan adalah variabel m; Saya memeriksa apakah positif atau negatif.
maksim

5

Saya menerapkan ini di java dan menjalankan tes unit (sumber di bawah). Tidak ada solusi di atas yang berfungsi. Kode ini lulus uji unit. Jika ada yang menemukan tes unit yang tidak lulus, beri tahu saya.

Kode: CATATAN: nearlyEqual(double,double)mengembalikan nilai true jika kedua angka tersebut sangat dekat.

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

Inilah tes unit:

@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

Dengan asumsi poinnya adalah (Ax, Ay) (Bx, By) dan (Cx, Cy), Anda perlu menghitung:

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

Ini akan sama dengan nol jika titik C pada garis yang dibentuk oleh titik A dan B, dan akan memiliki tanda yang berbeda tergantung pada sisi. Sisi mana ini tergantung pada orientasi koordinat (x, y) Anda, tetapi Anda dapat memasukkan nilai tes untuk A, B dan C ke dalam rumus ini untuk menentukan apakah nilai negatif berada di kiri atau ke kanan.


2

Saya ingin memberikan solusi yang terinspirasi oleh fisika.

Bayangkan gaya yang diterapkan di sepanjang garis dan Anda mengukur torsi gaya tentang titik tersebut. Jika torsi positif (berlawanan arah jarum jam) maka intinya adalah ke "kiri" dari garis, tetapi jika torsi negatif, titik adalah "kanan" dari garis.

Jadi jika vektor gaya sama dengan rentang dua titik yang mendefinisikan garis

fx = x_2 - x_1
fy = y_2 - y_1

Anda menguji sisi titik (px,py)berdasarkan tanda tes berikut

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

pada dasarnya, saya pikir ada solusi yang jauh lebih mudah dan lurus ke depan, untuk setiap poligon yang diberikan, katakanlah terdiri dari empat simpul (p1, p2, p3, p4), temukan dua simpul yang saling berlawanan dalam poligon, di tempat lain kata-kata, temukan misalnya simpul paling kiri atas (katakanlah p1) dan simpul sebaliknya yang terletak paling kanan bawah (katakanlah). Oleh karena itu, mengingat titik pengujian Anda C (x, y), sekarang Anda harus melakukan pemeriksaan ganda antara C dan p1 dan C dan p4:

jika cx> p1x AND cy> p1y ==> berarti bahwa C lebih rendah dan ke kanan p1 berikutnya jika cx <p2x AND cy <p2y ==> berarti bahwa C adalah atas dan ke kiri dari p4

Kesimpulannya, C ada di dalam persegi panjang.

Terima kasih :)


1
(1) Menjawab pertanyaan yang berbeda dari yang diminta? Kedengarannya seperti tes "kotak pembatas", ketika sebuah persegi panjang disejajarkan dengan kedua sumbu. (2) Secara lebih rinci: membuat asumsi tentang kemungkinan hubungan antara 4 poin. Misalnya, ambil persegi panjang, dan putar 45 derajat, sehingga Anda memiliki berlian. Tidak ada yang namanya "titik kiri atas" pada berlian itu. Titik paling kiri bukanlah paling atas atau paling bawah. Dan tentu saja, 4 poin bahkan dapat membentuk bentuk yang lebih aneh. Misalnya, 3 titik bisa jauh di satu arah, dan titik 4 di arah lain. Terus mencoba!
ToolmakerSteve

1

@ AVB menjawab dalam ruby

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

Jika detpositif di atas, jika negatif di bawah. Jika 0, itu di telepon.


1

Inilah versi, lagi-lagi menggunakan logika produk silang, yang ditulis dalam 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)))))

Contoh penggunaan:

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

Yang mengatakan bahwa titik (0, 10) adalah di sebelah kiri garis yang ditentukan oleh (-3, -1) dan (3, 1).

CATATAN: Implementasi ini memecahkan masalah yang tidak ada yang lain (sejauh ini) lakukan! Urutan penting saat memberikan poin yang menentukan garis. Yaitu, itu adalah "jalur terarah", dalam arti tertentu. Jadi dengan kode di atas, doa ini juga menghasilkan hasil true:

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

Itu karena potongan kode ini:

(sort line)

Akhirnya, seperti solusi berbasis produk lintas lainnya, solusi ini mengembalikan boolean, dan tidak memberikan hasil ketiga untuk collinearity. Tetapi itu akan memberikan hasil yang masuk akal, misalnya:

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

0

Cara alternatif untuk merasakan solusi yang diberikan oleh netter adalah dengan memahami sedikit implikasi geometri.

Misalkan pqr = [P, Q, R] adalah titik yang membentuk bidang yang dibagi menjadi 2 sisi dengan garis [P, R] . Kita harus mencari tahu apakah dua titik pada bidang pqr , A, B, berada di sisi yang sama.

Setiap titik T pada bidang pqr dapat direpresentasikan dengan 2 vektor: v = PQ dan u = RQ, sebagai:

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

Sekarang implikasi geometri:

  1. i + j = 1: T pada baris pr
  2. i + j <1: T pada Sq
  3. i + j> 1: T pada Snq
  4. i + j = 0: T = Q
  5. i + j <0: T pada Sq dan di luar Q.

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

Secara umum,

  • i + j adalah ukuran seberapa jauh T jauh dari Q atau garis [P, R] , dan
  • tanda i + j-1 berimplikasi pada identitas T.

Signifikansi geometri lain dari i dan j (tidak terkait dengan solusi ini) adalah:

  • i , j adalah skalar untuk T dalam sistem koordinat baru di mana v, u adalah sumbu baru dan Q adalah asal baru;
  • i , j dapat dilihat sebagai gaya tarik untuk P, R , masing-masing. Semakin besar i , semakin jauh T menjauh dari R (tarikan lebih besar dari P ).

Nilai i, j dapat diperoleh dengan menyelesaikan persamaan:

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

Jadi kita diberi 2 poin, A, B di pesawat:

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

Jika A, B berada di sisi yang sama, ini akan benar:

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

Perhatikan bahwa ini berlaku juga untuk pertanyaan: Apakah A, B di sisi yang sama dari pesawat [P, Q, R] , di mana:

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

dan i + j + k = 1 menyiratkan bahwa T ada di pesawat [P, Q, R] dan tanda i + j + k-1 menyiratkan kedudukannya. Dari sini kita memiliki:

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

dan A, B berada di sisi yang sama bidang [P, Q, R] jika

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.