Bir noktanın çizginin sağında mı yoksa solunda mı olduğu nasıl anlaşılır


130

Bir takım puanım var. Onları 2 farklı gruba ayırmak istiyorum. Bunu yapmak için iki nokta ( a ve b ) seçerim ve aralarına hayali bir çizgi çizerim. Şimdi bu çizgiden kalan tüm noktaların bir sette ve bu çizgiden sağda kalanların diğer sette olmasını istiyorum.

Herhangi bir z noktası için sol veya sağ sette olduğunu nasıl anlarım ? Azb arasındaki açıyı hesaplamaya çalıştım - 180'den küçük açılar sağ tarafta, sol tarafta 180'den büyük - ancak ArcCos'un tanımı nedeniyle hesaplanan açılar her zaman 180 ° 'den küçük. 180 ° 'den büyük açıları hesaplamak için bir formül var mı (veya sağ veya sol tarafı seçebileceğiniz başka bir formül) var mı?


Sağ veya sol nasıl tanımlanır? A) P1'den P2'ye veya B) düzlemdeki çizginin soluna veya sağına bakmak açısından.
phkahler

2
Açıklığa kavuşturmak için, sorunuzun ikinci kısmına doğru açıyı hesaplamak için acos () yerine atan2 () kullanabilirsiniz. Ancak, Eric Bainville'in de belirttiği gibi, çapraz çarpım kullanmak bunun için en iyi çözümdür.
dionyziz

Aşağıdaki çözümlerin çoğu işe yaramıyor çünkü a ve b noktalarını (çizgimizi tanımlamak için kullandığımız noktalar) birbirinin yerine koyarsanız karşıt yanıtlar veriyorlar. Clojure'da iki noktayı üçüncü nokta ile karşılaştırmadan önce sözlükbilimsel olarak sıralayan bir çözüm veriyorum.
Purplejacket

Yanıtlar:


202

Vektörlerin determinantının işaretini kullanın (AB,AM), burada M(X,Y)sorgu noktası:

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

Çizgide 0ve +1bir tarafta, -1diğer tarafta.


10
+1 güzel, dikkat edilmesi gereken bir şey var: yuvarlama hatası, nokta neredeyse çizginin üzerindeyken bir sorun olabilir. Çoğu kullanım için sorun değil , ancak zaman zaman insanları ısırır.
Stephen Canon

16
Kendinizi bu testteki yuvarlama hatasının size sorun yarattığı bir durumda bulursanız, Jon Shewchuk'un "Hesaplamalı Geometri için Hızlı Sağlam Öngörüler" e bakmak isteyeceksiniz.
Stephen Canon

14
Açıklık getirmek için, bu, çizgi (ba) ile vektör arasındaki çapraz çarpımın a (ma) 'dan noktaya kadar olan Z-bileşeniyle aynıdır. En sevdiğiniz vektör sınıfında: position = sign ((ba) .cross (ma) [2])
larsmoa

3
A & B'yi değiş tokuş etmek aynı satırı korumak değil, işaretini değiştirmek positionsmi?
Jayen

6
Evet. A, B, "A'da durup B'ye bakarken solunuzda" olduğu gibi oryantasyonu tanımlar.
Eric Bainville

224

Çapraz çarpım kullanan bu kodu deneyin :

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

Burada a = çizgi noktası 1; b = çizgi noktası 2; c = kontrol edilecek nokta.

Formül 0'a eşitse, noktalar eşdoğrusaldır.

Çizgi yataysa, nokta çizginin üzerindeyse bu true döndürür.


6
Çizgi dikeyse?
Tofeeq Ahmad

9
iç çarpım mı demek istiyorsun?
Baiyan Huang

13
@lzprgmr: Hayır, bu bir çapraz çarpımdır, eşdeğer olarak bir 2D matrisin determinantıdır. (A, b) ve (c, d) satırlarıyla tanımlanan 2B matrisi düşünün. Belirleyici, ad - bc'dir. Şekilde bir vektör içine 2 puan ile temsil edilen bir çizgi, (a, b) dönüştürme ve daha sonra tanımlama Yukarıdaki başka (c, d) elde etmek için PointA ve PointC kullanılarak vektörü: (a, b) = (PointB.x - PointA.x, PointB.y - PointA.y) (c, d) = (PointC.x - PointA.x, PointC.y - PointA.y) Bu nedenle determinant, gönderide belirtildiği gibidir.
AndyG

6
Bunun çapraz çarpım mı yoksa iç çarpım mı olduğu konusundaki kafa karışıklığının iki boyutlu olmasından kaynaklandığını düşünüyorum. Bu ise , iki boyutta çarpım: mathworld.wolfram.com/CrossProduct.html
brianmearns

4
Değeri ne olursa olsun, bu biraz basitleştirilebilir return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);, ancak derleyici muhtemelen bunu yine de optimize eder.
Nicu Stiurca

44

Determinantının işaretine bakıyorsun

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

Bir taraftaki noktalar için pozitif, diğer taraftaki noktalar için negatif (ve çizginin kendisindeki noktalar için sıfır) olacaktır.


1
Bu cevabı genişleterek, insanların çapraz çarpımın neye benzediğini bilmemesi durumunda. Sonraki görsel adım ((x2-x1) * (y3-y1)) - ((y2 - y1) * (x3-x1))
Franky Rivera

10

Vektör (y1 - y2, x2 - x1), çizgiye diktir ve her zaman sağa dönüktür (veya eğer düzlem oryantasyonu benimkinden farklıysa, her zaman sola dönüktür).

Daha sonra bu vektörün iç çarpımını hesaplayabilir ve noktanın (x3 - x1, y3 - y1), dik vektörle (iç çarpım> 0) çizginin aynı tarafında olup olmadığını belirleyebilirsiniz.


5

Ab çizgisinin denklemini kullanarak, sıralanacak nokta ile aynı y koordinatındaki doğru üzerindeki x koordinatını alın.

  • Eğer nokta x> doğruysa, nokta doğrunun sağındadır.
  • Eğer nokta x <doğru x ise, nokta çizginin solundadır.
  • Eğer nokta x == doğru x ise, nokta doğru üzerindedir.

Bu yanlıştır, çünkü Aaginor'un ilk cevaba yaptığı yorumdan da görebileceğiniz gibi, noktanın AB DOĞRUDAN çizgisinin solunda mı yoksa sağında mı olduğunu anlamak istemiyoruz, yani A üzerinde durup bakıyorsanız B'ye doğru solunuzda mı yoksa sağınızda mı?
dionyziz

1
@dionyziz - Huh? Cevabım AB'den geçen çizgiye bir "yön" atamıyor. Cevabım "sol" un eşgüdüm sisteminin -x yönü olduğunu varsayar. Kabul edilen cevap, bir AB vektörü tanımlamayı ve çapraz çarpım kullanarak sola tanımlamayı seçti . Orijinal soru, "sol" ile ne kastedildiğini belirtmez.
mbeckish

3
NOT: Bu yaklaşımı kullanırsanız (yanıt olarak onaylanan çapraz ürün yerine), çizgi yatay yaklaşırken bir tuzağa dikkat edin. Matematik hataları artar ve tam olarak yataysa sonsuza ulaşır. Çözüm, hangi eksenin iki nokta arasında daha büyük delta'ya sahip olduğunu kullanmaktır. (Ya da belki daha küçük delta .. bu kafamın tepesinde.)
ToolmakerSteve

bu tamamen aradığım şeydi. A'nın B'nin üstünde mi yoksa altında mı olduğunu bilmek istemiyorum sadece çizginin solunda mı (negatif x yönü) olduğunu bilmek istiyorum!
Jayen

5

Önce dikey bir çizginiz olup olmadığını kontrol edin:

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

Ardından eğimi hesaplayın: m = (y2-y1)/(x2-x1)

Daha sonra, nokta eğim formu hattının bir denklem oluşturmak: y - y1 = m*(x-x1) + y1. Benim açıklama uğruna, eğim-kesişim formu (sizin algoritmada gerekli değildir) bir basitleştirmeye: y = mx+b.

Şimdi takın (x3, y3)için xve y. Ne olması gerektiğini ayrıntılarıyla anlatan bazı sözde kodlar:

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
Başarısız: Eğim hesaplaması dikey çizgiler için geçersiz. Endless if / else şeyler. OP'nin sol / sağ ile kastettiği bu olup olmadığından emin değilim - eğer öyle olsaydı 90 derece döndürüldüğüne bakmak bu kodu ikiye böler, çünkü "yukarı" sağ veya sol olurdu.
phkahler

1
Bu cevabın birkaç sorunu var. Dikey çizgiler sıfıra bölünmeye neden olur. Daha kötüsü, hattın eğiminin pozitif mi yoksa negatif mi olduğu konusunda endişelenmediği için başarısız olur.

2
@phkahler, dikey çizgi sorununu çözdü. Kesinlikle bir test vakasını unutmak için bir başarısızlık değil ama nazik sözler için teşekkürler. "Endless if / else" matematiksel teoriyi açıklamak içindir; OP'nin sorusundaki hiçbir şey programlamadan bahsetmiyor. @woodchips, dikey çizgi sorununu çözdü. Eğim, m değişkenidir; Olumlu ya da olumsuz olduğunda kontrol ederim.
maksim

5

Bunu java'da uyguladım ve bir birim testi yaptım (aşağıdaki kaynak). Yukarıdaki çözümlerin hiçbiri işe yaramıyor. Bu kod, birim testini geçer. Herhangi biri geçmeyen bir birim testi bulursa, lütfen bana bildirin.

Kod: NOT: nearlyEqual(double,double)iki sayı birbirine çok yakınsa doğru döndürür.

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

İşte birim testi:

@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

Puanların (Ax, Ay) (Bx, By) ve (Cx, Cy) olduğunu varsayarsak, şunları hesaplamanız gerekir:

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

Bu, C noktası A ve B noktalarının oluşturduğu doğru üzerindeyse sıfıra eşit olacak ve tarafa bağlı olarak farklı bir işarete sahip olacaktır. Bunun hangi taraf olduğu (x, y) koordinatlarınızın yönüne bağlıdır, ancak negatif değerlerin solda mı yoksa sağda mı olduğunu belirlemek için bu formüle A, B ve C için test değerleri koyabilirsiniz.


2

Fizikten ilham alan bir çözüm sunmak istedim.

Çizgi boyunca uygulanan bir kuvvet düşünün ve kuvvetin nokta etrafındaki torkunu ölçüyorsunuz. Tork pozitifse (saat yönünün tersine) o zaman nokta çizginin "solundadır", ancak tork negatifse nokta, çizginin "sağı" dır.

Öyleyse kuvvet vektörü, doğruyu tanımlayan iki noktanın aralığına eşitse

fx = x_2 - x_1
fy = y_2 - y_1

(px,py)Aşağıdaki testin işaretine göre bir noktanın kenarı için test yaparsınız

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

temel olarak, herhangi bir çokgen için çok daha kolay ve açık bir çözüm olduğunu düşünüyorum, diyelim ki dört köşeden (p1, p2, p3, p4) oluşuyor, çokgende iki uç zıt köşeyi bul, diğerinde kelimeler, örneğin en üst sol köşeyi (p1 diyelim) ve en çok sağ altta bulunan zıt köşeyi (diyelim) bulun. Bu nedenle, test noktanız C (x, y) verildiğinde, şimdi C ile p1 ve C ve p4 arasında iki kez kontrol yapmanız gerekir:

eğer cx> p1x VE cy> p1y ==>, C'nin p1'in altında ve sağında olduğu anlamına gelirse, eğer cx <p2x VE cy <p2y ==>, C'nin p4'ün üstünde ve solunda olduğu anlamına gelirse

Sonuç, C dikdörtgenin içindedir.

Teşekkürler :)


1
(1) Sorulandan farklı bir soruyu yanıtlıyor mu? Bir dikdörtgen her iki eksenle hizalandığında "sınırlayıcı kutu" testi gibi geliyor. (2) Daha ayrıntılı olarak: 4 nokta arasındaki olası ilişkiler hakkında varsayımda bulunur. Örneğin, bir dikdörtgen alın ve 45 derece döndürün, böylece bir elmasınız olsun. O elmasta "sol üst nokta" diye bir şey yok. En soldaki nokta ne en üstte ne de en alttadır. Ve tabii ki 4 nokta daha da garip şekiller oluşturabilir. Örneğin, 3 nokta bir yönde ve 4. nokta başka bir yönde uzakta olabilir. Denemeye devam et!
ToolmakerSteve

1

@ AVB'nin Ruby'deki cevabı

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

Eğer detonun altında onun üstünde, eğer negatif pozitif. 0 ise, satır üzerindedir.


1

İşte yine çapraz çarpım mantığını kullanan, Clojure ile yazılmış bir sürüm.

(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)))))

Örnek kullanım:

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

Yani (0, 10) noktası (-3, -1) ve (3, 1) tarafından belirlenen çizginin solundadır.

NOT: Bu uygulama, diğerlerinin (şimdiye kadar) hiçbirinin çözemediği bir sorunu çözer! Çizgiyi belirleyen noktaları verirken sıra önemlidir . Yani, bir anlamda "yönlendirilmiş bir çizgi". Dolayısıyla, yukarıdaki kodla, bu çağrı aynı zamanda şunların sonucunu da üretir true:

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

Bunun nedeni şu kod parçacığıdır:

(sort line)

Son olarak, diğer çapraz çarpım tabanlı çözümlerde olduğu gibi, bu çözüm bir boole döndürür ve doğrusallık için üçüncü bir sonuç vermez. Ancak mantıklı bir sonuç verecektir, örneğin:

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

0

Netleştiriciler tarafından sağlanan çözümler hakkında fikir edinmenin alternatif bir yolu, küçük bir geometri sonuçlarını anlamaktır.

Let PQR = [P, Q, R] 'noktalar olduğu formlar hattı ile 2 iki bölünmüş olan bir düzlem [P, R]' . Pqr düzlemindeki iki noktanın ( A, B) aynı tarafta olup olmadığını bulacağız .

Pqr düzlemindeki herhangi bir T noktası 2 vektörle temsil edilebilir: v = PQ ve u = RQ, aşağıdaki gibi:

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

Şimdi geometri çıkarımları:

  1. i + j = 1: pr satırında T
  2. i + j <1: Sq üzerinde T
  3. i + j> 1: Snq üzerinde T
  4. i + j = 0: T = Q
  5. i + j <0: Sq ve Q'nun ötesinde T.

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

Genel olarak,

  • i + j, T'nin Q veya [P, R] çizgisinden ne kadar uzakta olduğunun bir ölçüsüdür ve
  • i + j-1'in işareti T'nin tarafını gösterir.

İ ve j'nin (bu çözümle ilgili olmayan) diğer geometri anlamları şunlardır:

  • i , j , yeni bir koordinat sisteminde T için skalerdir; burada v, u yeni eksenler ve Q yeni orijindir;
  • i , j sırasıyla P, R için çekme kuvveti olarak görülebilir . Daha büyük i , uzak T uzak olan R (dan daha büyük bir çekme P ).

İ, j'nin değeri denklemleri çözerek elde edilebilir:

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

Yani uçakta A, B olmak üzere 2 puan veriliyor:

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

A, B aynı taraftaysa, bu doğru olacaktır:

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

Not Bu sorunun için de geçerli olduğu: A, düzlemin aynı tarafında [P, Q, R] 'in B , ki burada:

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

ve i + j + k = 1 , T'nin [P, Q, R] düzleminde olduğunu ve i + j + k-1'in işaretinin kenarlığını ifade ettiğini ima eder. Bundan bizde:

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

ve A, B [P, Q, R] düzleminin aynı tarafındadır.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.