Bir noktanın 2B üçgende olup olmadığını nasıl belirleyebilirim? [kapalı]


258

Bir noktanın bir üçgen içinde olup olmadığını belirlemenin kolay bir yolu var mı? 2D değil, 2D.


16
Üçgen testinde nokta hakkında tam bir makale yazdım. Bariyer merkezli, parametrik ve nokta ürün tabanlı yöntemleri gösterir. Daha sonra, bir nokta tam olarak bir kenarda olduğunda (örneklerle) ortaya çıkan doğruluk problemiyle ilgilenir. Son olarak, uçtan uca mesafeye dayalı tamamen yeni bir yöntem ortaya koyar. totologic.blogspot.fr/2014/01/… Keyfini çıkarın!
Mantık


1
Burada tartışılan herhangi bir yöntemin 3B alanda da geçerli olduğunu belirtmek gerekir. Onlardan önce bir koordinat dönüşümü (ve üçgenin düzlemi üzerindeki noktanın uygun bir şekilde yansıtılması) gerekir. Üçgen 2 boyutlu bir nesnedir.
andreasdr

Sargı düzeninden bağımsız bir çözüm için. İşte çalışan bir keman: jsfiddle.net/ibowankenobi/oex3pzq2
ibrahim tanyalcin

2
Bu soruyu kapatmak için oy kullanıyorum çünkü bu programlama yerine matematikle ilgili ve fikir tabanlı (sizin için "kolay" olan nedir?).
TylerH

Yanıtlar:


265

Genel olarak, en basit (ve oldukça optimal) algoritma, noktanın kenarları tarafından oluşturulan yarım düzlemin hangi tarafında olduğunu kontrol eder.

İşte bu konuda GameDev'deki performans sorunları da dahil olmak üzere bazı yüksek kaliteli bilgiler .

İşte başlamanız için bazı kodlar:

float sign (fPoint p1, fPoint p2, fPoint p3)
{
    return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}

bool PointInTriangle (fPoint pt, fPoint v1, fPoint v2, fPoint v3)
{
    float d1, d2, d3;
    bool has_neg, has_pos;

    d1 = sign(pt, v1, v2);
    d2 = sign(pt, v2, v3);
    d3 = sign(pt, v3, v1);

    has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
    has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);

    return !(has_neg && has_pos);
}

12
Genellikle 2D olarak kullanılır. Barycentric koordinatlar insanları karıştırır. Ayrıca, üçgenin eşgüdümleri ve kordinat noktası göz önüne alındığında, barycentrics kullanımının etkinliği konusunda emin değilim.
Kornel Kisielewicz

7
@Kornel Barycentric versiyon 2D'de de daha verimlidir. Çözümünüz, üçgenin saat yönünde veya saat yönünün tersine sırada belirtilmesine bağlı olarak tam olarak üçgenin kenarlarındaki noktalar için farklı bir sonuç bildirme sorununa da sahiptir.
Andreas Brinck

9
Benim amacım için (bu siteyi bulmamın nedeni) Kornel Kisielewicz tarafından önerilen orijinal cevap çok daha verimli. BYTE boyut koordinatlarına sahip bir LCD ekran ve tamsayı çarpımının çok hızlı bir talimat olduğu ve bölünmenin çok, çok, daha yavaş olduğu çok tipik bir mikroişlemci ile çalışıyorum. Bölünme olmaması nedeniyle sayısal sorunlar da çok daha küçüktür! tüm hesaplamalar kesindir. Teşekkürler Rick

4
Sign () fonksiyonu size yarım düzlemin (p2 ve p3 arasındaki çizgi tarafından oluşturulan) hangi tarafının p1 olduğunu söyler?
David Doria

1
Köşelerin bir sırasını varsayarsanız (saat yönünün tersine söyleyin), bu belirleyicilerin tümünü her zaman hesaplamanıza gerek olmadığını unutmayın. Aslında en iyi durumda, noktanın üçgenin içinde olmadığını bulmak için 1 belirleyici yeterlidir.
Thash

176

Aşağıdaki denklem sistemini çözün:

p = p0 + (p1 - p0) * s + (p2 - p0) * t

Nokta pise üçgenin içinde olduğunu 0 <= s <= 1ve 0 <= t <= 1ve s + t <= 1.

s, tve noktanın barycentric koordinatları1 - s - t olarak adlandırılır .p


1
Bu, yarım düzlemli kontrolden daha hızlıdır, ancak barycentric koordinatlarda yeniyseniz kavramanız biraz zor olabilir.
Daniel Rikowski

8
Kornel'in yöntemindeki önemsiz çıkışlarla (uygulanmadı), aslında sizinkinden çok daha verimli olabilir. Eğer gerçekten s ve t'yi hesaplamaya çalışırsanız ne demek istediğimi anlayacaksınız.

85
Bunu test etmek istedim, bu yüzden @andreasdr çözümüne ve coproc yorumuna dayanarak bir jsfiddle yaptım: jsfiddle.net/PerroAZUL/zdaY8/1
urraka 9:01 '

5
Optimizasyon: s + t <= 1ima eder s <= 1ve t <= 1eğer s >= 0ve t >= 0.
Thomas Eding

7
Makale totologic.blogspot.fr/2014/01/... @Logic posta yoluyla önerdiği beni daha iyi bu çözümü anlamak için yardımcı oldu
Flayn

112

Andreas Brinck'e katılıyorum , barycentric koordinatlar bu görev için çok uygun. Her seferinde bir denklem sistemini çözmeye gerek olmadığını unutmayın: sadece analitik çözümü değerlendirin. Andreas'ın notasyonunu kullanarak çözüm:

s = 1/(2*Area)*(p0y*p2x - p0x*p2y + (p2y - p0y)*px + (p0x - p2x)*py);
t = 1/(2*Area)*(p0x*p1y - p0y*p1x + (p0y - p1y)*px + (p1x - p0x)*py);

Areaüçgenin (imzalı) alanı nerede :

Area = 0.5 *(-p1y*p2x + p0y*(-p1x + p2x) + p0x*(p1y - p2y) + p1x*p2y);

Sadece değerlendirmek s, tve 1-s-t. NoktapSadece üçgenin pozitif olması durumunda üçgenin içindedir.

DÜZENLEME: Alan için yukarıdaki ifadenin üçgen düğüm numaralandırmasının saat yönünün tersine olduğunu varsaydığını unutmayın. Numaralandırma saat yönünde ise, bu ifade negatif bir alana (ancak doğru büyüklükte) dönecektir. Testin kendisi ( s>0 && t>0 && 1-s-t>0) numaralandırmanın yönüne bağlı değildir, çünkü yukarıdaki ifadeler ile çarpılır1/(2*Area) üçgenler aynı zamanda üçgen düğüm yönü değişirse işareti de değiştirir.

Düzenleme 2: daha iyi bir hesaplama verimliliği için, bkz coproc üçgen düğümleri (saat yönünde ya da saat yönünün tersine) oryantasyonu ile, daha önce bölme biliniyorsa bu noktayı yapan aşağıdaki 'yorumunu ( 2*Areaiçin ifadelerde sve tolabilmektedir kaçınılması). Andreas Brinck'in cevabı altındaki yorumlarda Perro Azul'un jsfiddle-koduna da bakınız .


6
Yani bir denklem sistemini :) çözme
Andreas Brinck

1
Evet, benim fikrim, denklem sistemini çözmenin hesaplama maliyetine dayanan yönteminizin herhangi bir eleştirisinin temelsiz olduğu, çünkü bunun algoritmanın bir parçası olarak yapılması gerekmiyor.
43'te andreasdr

13
Verimlilik, bölünmeyerek 2*Area, yani hesaplanarak s´=2*|Area|*sve t´=2*|Area|*t(noktaların yönü - saat yönünde veya saat yönünün tersine - bilinmiyorsa) bilinmeyebilir, Areaelbette işaretinin kontrol edilmesi gerekir, ancak aksi halde belki de hesaplanması gerekir), çünkü kontrol için kontrol s>0etmek yeterlidir s´>0. Ve kontrol 1-s-t>0etmek yerine kontrol etmek yeterlidir s´+t´<2*|Area|.
coproc

1
I eğer ekleyebilir p0->p1->p2olan saat yönünün tersi olarak Kartezyen (genellikle saat içinde ekran koordinatları ), Areabu şekilde hesaplanan olumlu olacaktır.
rhgb

1
@ user2600366 Üçgenin sınırı boyunca p0 -> p1 -> p2 -> p0 yönünde ilerlediğinizde, üçgenin iç kısmına her zaman sağınızda veya her zaman solunuzda sahip olursunuz. Önceki durumda, numaralandırma saat yönündedir, ikinci durumda saat yönünün tersidir.
andreasdr

47

Google ile son bir denemeden ve bu sayfayı bulmadan önce bu kodu yazdım, bu yüzden paylaşacağımı düşündüm. Temel olarak Kisielewicz cevabının optimize edilmiş bir versiyonudur. Ben de Barycentric yöntemine baktım ama Wikipedia makalesine bakıldığında nasıl daha verimli olduğunu görmekte zorlanıyorum (daha derin bir eşdeğerlik olduğunu tahmin ediyorum). Her neyse, bu algoritma bölme kullanmama avantajına sahiptir; potansiyel bir problem, yönlendirmeye bağlı olarak kenar algılamanın davranışıdır.

bool intpoint_inside_trigon(intPoint s, intPoint a, intPoint b, intPoint c)
{
    int as_x = s.x-a.x;
    int as_y = s.y-a.y;

    bool s_ab = (b.x-a.x)*as_y-(b.y-a.y)*as_x > 0;

    if((c.x-a.x)*as_y-(c.y-a.y)*as_x > 0 == s_ab) return false;

    if((c.x-b.x)*(s.y-b.y)-(c.y-b.y)*(s.x-b.x) > 0 != s_ab) return false;

    return true;
}

Kelime ile fikir şudur: AB ve AC hatlarının her ikisinin solunda mı yoksa sağında mı? Eğer doğruysa, içeride olamaz. Yanlışsa, en azından durumu karşılayan "konilerin" içindedir. Şimdi, bir trigon (üçgen) içindeki bir noktanın AB'nin BC (ve ayrıca CA) ile aynı tarafında olması gerektiğini bildiğimizden, bunların farklı olup olmadığını kontrol ediyoruz. Eğer öyleyse, s muhtemelen içeride olamaz, aksi takdirde s içeride olmalıdır.

Hesaplamalardaki bazı anahtar kelimeler, çizgi yarı düzlemleri ve belirleyicidir (2x2 çapraz ürün). Belki de daha pedagojik bir yol muhtemelen AB, BC ve CA hatlarının her biriyle aynı tarafa (sol veya sağ) doğru olan bir nokta olarak düşünmektir. Ancak yukarıdaki yol bazı optimizasyonlar için daha uygun görünüyordu.


2
Bu test sağlanan ilk testten yaklaşık% 140-180 daha hızlıdır (her ikisi de btw sayesinde) :). : Burada kod koştum paste.ubuntu.com/p/k5w7ywH4p8 aşağıdaki sonuçları özürlü optimizasyonlarla nodejs v8 motoru kullanarak ve var: w düğüm -p --minimal test1:! 114.852ms dnm2: 64.330ms test1: 115.650ms test2: 63.491ms test1: 117.671ms test2: 65.353ms test1: 119.146ms test2: 63.871ms test1: 118.271ms test1: 118.670ms test2: 63.352ms
cerrah 8:19

@surgemcgee Neden optimizasyon olmadan çalıştırdınız? O zaman gerçeklikten daha fazla çıkarılmıyor mu?
xuiqzy

@xuiqzy Pekala, programım iki farklı çözüm içeriyor. Bunu yapmanın en hızlı yöntemini henüz uygulayamadım. Belki de bu yorum kaldırılmalı ve bununla ilgili tamamlanmış
işlerimle

33

Andreasdr ve Perro Azul tarafından yayınlanan barycentric yöntemin C # sürümü. Karşılıklı işaretler varsa sve tvarsa , alan hesaplamasından kaçınılabilir . Oldukça kapsamlı bir birim testi ile doğru davranışı doğruladım.

public static bool PointInTriangle(Point p, Point p0, Point p1, Point p2)
{
    var s = p0.Y * p2.X - p0.X * p2.Y + (p2.Y - p0.Y) * p.X + (p0.X - p2.X) * p.Y;
    var t = p0.X * p1.Y - p0.Y * p1.X + (p0.Y - p1.Y) * p.X + (p1.X - p0.X) * p.Y;

    if ((s < 0) != (t < 0))
        return false;

    var A = -p1.Y * p2.X + p0.Y * (p2.X - p1.X) + p0.X * (p1.Y - p2.Y) + p1.X * p2.Y;

    return A < 0 ?
            (s <= 0 && s + t >= A) :
            (s >= 0 && s + t <= A);
}

[ değiştir ]
@Pierre tarafından önerilen değişikliği kabul etti; Yorumlara bakınız


If ifadesi saat yönünde ve saat yönünün tersine üçgen noktaları için çalışıyorsa, biten çözüm.
Luke Dupin

@LukeDupin Yorumunuzu anladığımdan emin değilim. Bu cevap, verilen 3 noktanın herhangi bir sıralaması için kaydedildiği şekilde çalışır.
Glenn Slayden

12

Barycentric yöntemin Java sürümü:

class Triangle {
    Triangle(double x1, double y1, double x2, double y2, double x3,
            double y3) {
        this.x3 = x3;
        this.y3 = y3;
        y23 = y2 - y3;
        x32 = x3 - x2;
        y31 = y3 - y1;
        x13 = x1 - x3;
        det = y23 * x13 - x32 * y31;
        minD = Math.min(det, 0);
        maxD = Math.max(det, 0);
    }

    boolean contains(double x, double y) {
        double dx = x - x3;
        double dy = y - y3;
        double a = y23 * dx + x32 * dy;
        if (a < minD || a > maxD)
            return false;
        double b = y31 * dx + x13 * dy;
        if (b < minD || b > maxD)
            return false;
        double c = det - a - b;
        if (c < minD || c > maxD)
            return false;
        return true;
    }

    private final double x3, y3;
    private final double y23, x32, y31, x13;
    private final double det, minD, maxD;
}

Yukarıdaki kod, taşma olmadığını varsayarak tamsayılarla doğru şekilde çalışacaktır. Ayrıca saat yönünde ve saat yönünün tersine üçgenlerle çalışır. Doğrusal üçgenlerle çalışmaz (ancak det == 0'ı test ederek bunu kontrol edebilirsiniz).

Aynı üçgenle farklı noktaları test edecekseniz, barycentric versiyon en hızlısıdır.

Barycentric versiyon 3 üçgen noktasında simetrik değildir, bu nedenle kayan nokta yuvarlama hataları nedeniyle Kornel Kisielewicz'in kenar yarım düzlem versiyonundan daha az tutarlı olması muhtemeldir.

Kredi: Vikipedi'nin barycentric koordinatlar hakkındaki yazısından yukarıdaki kodu yaptım.


Güzel ! Veri girişini daha iyi işlemek için javax.vecmath'ın Point3f / Point2f tuples'ını kullanmak için bile geliştirilebilir.
Alex Byrth

10

Basit bir yol:

noktayı üçgenin üç köşesinin her birine bağlayan vektörleri bulun ve bu vektörler arasındaki açıları toplayın. Açıların toplamı 2 * pi ise nokta üçgenin içindedir.

Alternatifleri açıklayan iki iyi site:

blackpawn ve wolfram


3
Um, bu yöntem tam olarak verimli değil ve sayısal hatalara çok yatkın ...
Kornel Kisielewicz

Bu tam tersi, çok verimsiz :-) Bu sadece basit bir yol, bu uygulanması kolaydır. Bunun neden olacağı sayısal bir hata örneği verebilir misiniz?
Simon P Stevens

Bana göre bu sadece bu konu altındaki tüm cevapların en iyisi gibi görünse de, sanırım üçgenin kenarlarındaki noktalar üçgene dahil edilmiş olarak hesaplanır ve bunun üzerinde sağlam bir kontrolünüz yoktur.
Redu

pi'nin irrasyonel olması nedeniyle tam olarak 2pi'nin sayısal olarak imkansız olup olmadığını kontrol etmek. Ancak, açıların pi'den daha büyük bir şeye eklenip eklenmediğini kontrol etmeniz gerekir.
lonewarrior556

10

Barikat merkezli koordinatlara ( Andreas Brinck tarafından işaret edilen ) analitik çözümü kullanarak ve:

  • çarpmayı parantez içinde dağıtılmamış terimler
  • aynı terimleri saklayarak birkaç kez hesaplamaktan kaçının
  • karşılaştırmaları azaltmak ( coproc ve Thomas Eding tarafından belirtildiği gibi )

"Pahalı" işlemlerin sayısı en aza indirilebilir:

function ptInTriangle(p, p0, p1, p2) {
    var dX = p.x-p2.x;
    var dY = p.y-p2.y;
    var dX21 = p2.x-p1.x;
    var dY12 = p1.y-p2.y;
    var D = dY12*(p0.x-p2.x) + dX21*(p0.y-p2.y);
    var s = dY12*dX + dX21*dY;
    var t = (p2.y-p0.y)*dX + (p0.x-p2.x)*dY;
    if (D<0) return s<=0 && t<=0 && s+t>=D;
    return s>=0 && t>=0 && s+t<=D;
}

Kod Perro Azul jsfiddle içine yapıştırılabilir veya aşağıdaki "Kod snippet'ini çalıştır" ı tıklayarak deneyebilirsiniz

var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;

var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();

$("canvas").click(function(evt) {
    point.x = evt.pageX - $(this).offset().left;
    point.y = evt.pageY - $(this).offset().top;
    test();
});

$("canvas").dblclick(function(evt) {
    triangle = randomTriangle();
    test();
});

test();

function test() {
    var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);
    
    var info = "point = (" + point.x + "," + point.y + ")\n";
    info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
    info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
    info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
    info += "result = " + (result ? "true" : "false");

    $("#result").text(info);
    render();
}

function ptInTriangle(p, p0, p1, p2) {
    var A = 1/2 * (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    var sign = A < 0 ? -1 : 1;
    var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y) * sign;
    var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y) * sign;
    
    return s > 0 && t > 0 && (s + t) < 2 * A * sign;
}

function render() {
    ctx.fillStyle = "#CCC";
    ctx.fillRect(0, 0, 500, 500);
    drawTriangle(triangle.a, triangle.b, triangle.c);
    drawPoint(point);
}

function drawTriangle(p0, p1, p2) {
    ctx.fillStyle = "#999";
    ctx.beginPath();
    ctx.moveTo(p0.x, p0.y);
    ctx.lineTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.font = "12px monospace";
    ctx.fillText("1", p0.x, p0.y);
    ctx.fillText("2", p1.x, p1.y);
    ctx.fillText("3", p2.x, p2.y);
}

function drawPoint(p) {
    ctx.fillStyle = "#F00";
    ctx.beginPath();
    ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
    ctx.fill();
}

function rand(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomTriangle() {
    return {
        a: { x: rand(0, W), y: rand(0, H) },
        b: { x: rand(0, W), y: rand(0, H) },
        c: { x: rand(0, W), y: rand(0, H) }
    };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>

Giden:

  • değişken "hatırlama": 30
  • değişken depolama: 7
  • eklemeler: 4
  • çıkarma: 8
  • çarpma: 6
  • bölümler: hiçbiri
  • karşılaştırmalar: 4

Bu, Kornel Kisielewicz çözümüyle (25 hatırlama , 1 depolama, 15 çıkarma, 6 çarpma, 5 karşılaştırma) oldukça iyi bir şekilde karşılaştırılır ve saat yönünde / saat yönünün tersine algılama gerekiyorsa daha iyi olabilir (6 hatırlama alır, 1 toplama, 2 çıkarma) , Rhgb ile işaret edildiği gibi, analitik çözelti belirleyicisini kullanarak kendi içinde 2 çarpma ve 1 karşılaştırma ).


Güzel çözüm. Sanırım burada MSE'deki
Jack D'Aurizio

Kodu olduğu gibi test ettim ve benim için çalışmıyor (örnek p -4.69317198, -6.99191951 p0 -7.05846786 0.596718192 p1 -6.8703599 -2.36565161 p2 -4.69317198, -6.991951)
Giovanni Funchal

@GiovanniFunchal Strange, örneğin hem jsfiddle'da (ilk "nokta" ve "üçgen" tanımlarının yerine) hem de yerel Python uygulamamda benim için çalışıyor. Sayısal kesinlik sorunları (bazı ondalık basamakları çıkarmayı deneyin)?
Cédric Dufour

1
Testimdeki en hızlı gibi görünüyor: jsfiddle.net/eyal/gxw3632c/27 . Bununla birlikte, tüm yöntemler arasındaki fark oldukça küçüktür.
Eyal

Üçgen (-1, -1), (1, -1), (0,1) ve noktayı (0, -1) deneyin. Doğru olması gerektiğinde false değerini döndürür, çünkü s (2) + t (2)> d (2). Üçgenin kenarlarındaki matematik ile ilgili yanlış bir şey var, p noktası p0 ve p1 arasındaki sınırda olduğu için bir <ve <= veya bunun gibi bir şeye dönüştürmek basit bir mesele değil.
devnullicus

5

Yaptığım üç yüz normalini önceden hesaplamak,

  • 3D yan vektörün ve yüz normal vektörünün çapraz ürünü.

  • bileşenleri değiştirerek ve birisini reddederek 2D olarak,

daha sonra herhangi bir taraf için iç / dış taraf normal bir yan ürün ve noktadan noktaya vektör, işareti değiştirmek zaman. Diğer iki (veya daha fazla) taraf için tekrarlayın.

Yararları:

  • aynı üçgen üzerinde çok noktalı test için çok fazla önceden hesaplanmıştır.

  • iç noktadan daha fazla dış vakaların erken reddi. (ayrıca nokta dağılımı bir tarafa ağırlık verilmişse, önce o tarafı test edebilir.)


5

İşte verimli bir Python uygulaması:

def PointInsideTriangle2(pt,tri):
    '''checks if point pt(2) is inside triangle tri(3x2). @Developer'''
    a = 1/(-tri[1,1]*tri[2,0]+tri[0,1]*(-tri[1,0]+tri[2,0])+ \
        tri[0,0]*(tri[1,1]-tri[2,1])+tri[1,0]*tri[2,1])
    s = a*(tri[2,0]*tri[0,1]-tri[0,0]*tri[2,1]+(tri[2,1]-tri[0,1])*pt[0]+ \
        (tri[0,0]-tri[2,0])*pt[1])
    if s<0: return False
    else: t = a*(tri[0,0]*tri[1,1]-tri[1,0]*tri[0,1]+(tri[0,1]-tri[1,1])*pt[0]+ \
              (tri[1,0]-tri[0,0])*pt[1])
    return ((t>0) and (1-s-t>0))

ve bir örnek çıktı:

resim açıklamasını buraya girin


Bu işi yapamadım, örneğin [(0,0), (3,0), (3,4)] üçgenindeki nokta, hiçbiri (1,1) veya (0) , 0) test pozitif. Hem saat yönünde hem de saat yönünün tersine üçgen noktalarla denedim.
ThorSummoner

3

Hız arıyorsanız, size yardımcı olabilecek bir prosedür var.

Üçgen köşeleri koordinatlarına göre sıralayın. Bu en kötü üç karşılaştırmayı gerektirir. Y0, Y1, Y2 üç sıralı değer olsun. İçinden üç ufuk çizgisi çizerek uçağı iki yarım düzleme ve iki plakaya ayırırsınız. Y sorgu noktasının koordinatı olsun.

if Y < Y1
    if Y <= Y0 -> the point lies in the upper half plane, outside the triangle; you are done
    else Y > Y0 -> the point lies in the upper slab
else
    if Y >= Y2 -> the point lies in the lower half plane, outside the triangle; you are done
    else Y < Y2 -> the point lies in the lower slab

İki karşılaştırmaya daha mal olur. Gördüğünüz gibi, "sınırlayıcı levha" dışındaki noktalar için hızlı bir şekilde reddediliyor.

İsteğe bağlı olarak, solda ve sağda ( X <= X0' or X >= X2') hızlı ret için apseler üzerinde bir test yapabilirsiniz . Bu, aynı zamanda hızlı bir sınırlama kutusu testi uygulayacaktır, ancak apseleri de sıralamanız gerekir.

Sonunda, verilen noktanın işaretini, ilgili levhayı (üst veya alt) sınırlayan üçgenin iki tarafına göre hesaplamanız gerekecektir. Test şu şekildedir:

((X - Xi) * (Y - Yj) > (X - Xi) * (Y - Yj)) == ((X - Xi) * (Y - Yk) > (X - Xi) * (Y - Yk))

i, j, kKombinasyonların tam tartışması ( sıralamanın sonucuna bağlı olarak altı tane vardır) bu cevabın kapsamı dışındadır ve "okuyucuya bir egzersiz olarak bırakılmıştır"; verimlilik için bunlar sabit kodlanmış olmalıdır.

Bu çözümün karmaşık olduğunu düşünüyorsanız, sınırlayıcı kutu testinin başarısız olması durumunda temel olarak basit karşılaştırmalar (bazıları önceden hesaplanabilecek) artı 6 çıkarma ve 4 çarpım içerdiğini gözlemleyin. En kötü durumda test sonucunu iki tarafla karşılaştırmaktan kaçınamayacağınız gibi yenmek zordur (diğer cevaplardaki hiçbir yöntemin daha düşük bir maliyeti yoktur, bazıları 15 çıkarma ve 6 çarpma, bazen bölünmeler gibi daha kötü hale getirir).

GÜNCELLEME: Kesme dönüşümü ile daha hızlı

Yukarıda açıklandığı gibi, üç köşe koordinatı tarafından sınırlanan dört yatay banttan birinin içindeki noktayı, iki karşılaştırma kullanarak hızlı bir şekilde bulabilirsiniz.

Sınırlama kutusunun (noktalı çizgiler) durumunu kontrol etmek için isteğe bağlı olarak bir veya iki ekstra X testi yapabilirsiniz.

Sonra verilen "kesme" dönüşümünü göz önünde bulundurun, en yüksek kenarın eğimi X'= X - m Y, Y' = Ynerede . Bu dönüşüm üçgenin bu tarafını dikey yapacaktır. Orta yatayın hangi tarafında olduğunuzu bildiğiniz için, işareti üçgenin tek bir tarafına göre test etmek yeterlidir.mDX/DY

resim açıklamasını buraya girin

Eğimi m, X'makaslanmış üçgen köşeleri ve kenar denklemlerinin katsayıları için önceden hesapladığınızı varsayarak X = m Y + p, en kötü durumda ihtiyacınız olacak

  • dikey sınıflandırma için iki sıra karşılaştırması;
  • sınırlayıcı kutu reddi için isteğe bağlı olarak bir veya iki apsis karşılaştırması;
  • hesaplanması X' = X - m Y;
  • makaslanmış üçgenin apsisleri ile bir veya iki karşılaştırma;
  • X >< m' Y + p'makaslanmış üçgenin ilgili tarafına karşı bir işaret testi .

3

Üç köşenin koordinatlarını ve belirli noktanın koordinatlarını biliyorsanız, tüm üçgenin alanını elde edebilirsiniz. Daha sonra, üç üçgen parçanın alanını hesaplayın (bir nokta verilen nokta ve diğer ikisi üçgenin herhangi iki köşesidir). Böylece, üç üçgen parçasının alanını alacaksınız. Bu alanların toplamı (daha önce aldığınız) toplam alana eşitse, nokta üçgenin içinde olmalıdır. Aksi takdirde, nokta üçgenin içinde değildir. Bu çalışmalı. Herhangi bir sorun varsa bana bildirin. Teşekkür ederim.


3

Python'daki diğer işlev , Geliştirici yönteminden daha hızlı (en azından benim için) ve Cédric Dufour çözümünden ilham aldı :

def ptInTriang(p_test, p0, p1, p2):       
     dX = p_test[0] - p0[0]
     dY = p_test[1] - p0[1]
     dX20 = p2[0] - p0[0]
     dY20 = p2[1] - p0[1]
     dX10 = p1[0] - p0[0]
     dY10 = p1[1] - p0[1]

     s_p = (dY20*dX) - (dX20*dY)
     t_p = (dX10*dY) - (dY10*dX)
     D = (dX10*dY20) - (dY10*dX20)

     if D > 0:
         return (  (s_p >= 0) and (t_p >= 0) and (s_p + t_p) <= D  )
     else:
         return (  (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= D  )

Şunlarla test edebilirsiniz:

X_size = 64
Y_size = 64
ax_x = np.arange(X_size).astype(np.float32)
ax_y = np.arange(Y_size).astype(np.float32)
coords=np.meshgrid(ax_x,ax_y)
points_unif = (coords[0].reshape(X_size*Y_size,),coords[1].reshape(X_size*Y_size,))
p_test = np.array([0 , 0])
p0 = np.array([22 , 8]) 
p1 = np.array([12 , 55]) 
p2 = np.array([7 , 19]) 
fig = plt.figure(dpi=300)
for i in range(0,X_size*Y_size):
    p_test[0] = points_unif[0][i]
    p_test[1] = points_unif[1][i]
    if ptInTriang(p_test, p0, p1, p2):
        plt.plot(p_test[0], p_test[1], '.g')
    else:
        plt.plot(p_test[0], p_test[1], '.r')

Çizilmesi çok zaman alıyor, ancak bu ızgara Geliştirici kodunun 0.0844349861145 saniyesine karşı 0.0195319652557 saniyede test ediliyor .

Son olarak kod yorumu:

# Using barycentric coordintes, any point inside can be described as:
# X = p0.x * r + p1.x * s + p2.x * t
# Y = p0.y * r + p1.y * s + p2.y * t
# with:
# r + s + t = 1  and 0 < r,s,t < 1
# then: r = 1 - s - t
# and then:
# X = p0.x * (1 - s - t) + p1.x * s + p2.x * t
# Y = p0.y * (1 - s - t) + p1.y * s + p2.y * t
#
# X = p0.x + (p1.x-p0.x) * s + (p2.x-p0.x) * t
# Y = p0.y + (p1.y-p0.y) * s + (p2.y-p0.y) * t
#
# X - p0.x = (p1.x-p0.x) * s + (p2.x-p0.x) * t
# Y - p0.y = (p1.y-p0.y) * s + (p2.y-p0.y) * t
#
# we have to solve:
#
# [ X - p0.x ] = [(p1.x-p0.x)   (p2.x-p0.x)] * [ s ]
# [ Y - p0.Y ]   [(p1.y-p0.y)   (p2.y-p0.y)]   [ t ]
#
# ---> b = A*x ; ---> x = A^-1 * b
# 
# [ s ] =   A^-1  * [ X - p0.x ]
# [ t ]             [ Y - p0.Y ]
#
# A^-1 = 1/D * adj(A)
#
# The adjugate of A:
#
# adj(A)   =   [(p2.y-p0.y)   -(p2.x-p0.x)]
#              [-(p1.y-p0.y)   (p1.x-p0.x)]
#
# The determinant of A:
#
# D = (p1.x-p0.x)*(p2.y-p0.y) - (p1.y-p0.y)*(p2.x-p0.x)
#
# Then:
#
# s_p = { (p2.y-p0.y)*(X - p0.x) - (p2.x-p0.x)*(Y - p0.Y) }
# t_p = { (p1.x-p0.x)*(Y - p0.Y) - (p1.y-p0.y)*(X - p0.x) }
#
# s = s_p / D
# t = t_p / D
#
# Recovering r:
#
# r = 1 - (s_p + t_p)/D
#
# Since we only want to know if it is insidem not the barycentric coordinate:
#
# 0 < 1 - (s_p + t_p)/D < 1
# 0 < (s_p + t_p)/D < 1
# 0 < (s_p + t_p) < D
#
# The condition is:
# if D > 0:
#     s_p > 0 and t_p > 0 and (s_p + t_p) < D
# else:
#     s_p < 0 and t_p < 0 and (s_p + t_p) > D
#
# s_p = { dY20*dX - dX20*dY }
# t_p = { dX10*dY - dY10*dX }
# D = dX10*dY20 - dY10*dX20

Bu işlev çalışmıyor. Ver ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])ve trueyanlış olmasına rağmen geri dönecek
Code Pope

3

JS yanıtı olmadığından,
Saat Yönü ve Saatin Tersi çözümü:

function triangleContains(ax, ay, bx, by, cx, cy, x, y) {

    let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)

    return  det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) > 0 &&
            det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) > 0 &&
            det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) > 0 

}

EDIT: det hesaplama ( cy - ayyerine cx - ax) için bir yazım hatası vardı , bu düzeltildi.

https://jsfiddle.net/jniac/rctb3gfL/

function triangleContains(ax, ay, bx, by, cx, cy, x, y) {

    let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
	
    return  det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) > 0 &&
            det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) > 0 &&
            det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) > 0 

}






let width = 500, height = 500

// clockwise
let triangle1 = {

	A : { x: 10, y: -10 },
	C : { x: 20, y: 100 },
	B : { x: -90, y: 10 },
	
	color: '#f00',

}

// counter clockwise
let triangle2 = {

	A : { x: 20, y: -60 },
	B : { x: 90, y: 20 },
	C : { x: 20, y: 60 },

	color: '#00f',
	
}


let scale = 2
let mouse = { x: 0, y: 0 }






// DRAW >

let wrapper = document.querySelector('div.wrapper')

wrapper.onmousemove = ({ layerX:x, layerY:y }) => {
	
	x -= width / 2
	y -= height / 2
	x /= scale
	y /= scale
	
	mouse.x = x
	mouse.y = y
	
	drawInteractive()

}

function drawArrow(ctx, A, B) {

	let v = normalize(sub(B, A), 3)
	let I = center(A, B)
	
	let p
	
	p = add(I, rotate(v, 90), v)
	ctx.moveTo(p.x, p.y)
	ctx.lineTo(I.x, I .y)
	p = add(I, rotate(v, -90), v)
	ctx.lineTo(p.x, p.y)

}

function drawTriangle(ctx, { A, B, C, color }) {

	ctx.beginPath()
	ctx.moveTo(A.x, A.y)
	ctx.lineTo(B.x, B.y)
	ctx.lineTo(C.x, C.y)
	ctx.closePath()
	
	ctx.fillStyle = color + '6'
	ctx.strokeStyle = color
	ctx.fill()
	
	drawArrow(ctx, A, B)
	drawArrow(ctx, B, C)
	drawArrow(ctx, C, A)
	
	ctx.stroke()

}

function contains({ A, B, C }, P) {

	return triangleContains(A.x, A.y, B.x, B.y, C.x, C.y, P.x, P.y)

}

function resetCanvas(canvas) {

	canvas.width = width
	canvas.height = height
	
	let ctx = canvas.getContext('2d')

	ctx.resetTransform()
	ctx.clearRect(0, 0, width, height)
	ctx.setTransform(scale, 0, 0, scale, width/2, height/2)
	
}

function drawDots() {

	let canvas = document.querySelector('canvas#dots')
	let ctx = canvas.getContext('2d')

	resetCanvas(canvas)
	
	let count = 1000

	for (let i = 0; i < count; i++) {

		let x = width * (Math.random() - .5)
		let y = width * (Math.random() - .5)
		
		ctx.beginPath()
		ctx.ellipse(x, y, 1, 1, 0, 0, 2 * Math.PI)
		
		if (contains(triangle1, { x, y })) {
		
			ctx.fillStyle = '#f00'
		
		} else if (contains(triangle2, { x, y })) {
		
			ctx.fillStyle = '#00f'
		
		} else {
		
			ctx.fillStyle = '#0003'
		
		}

		
		ctx.fill()
		
	}
	
}

function drawInteractive() {

	let canvas = document.querySelector('canvas#interactive')
	let ctx = canvas.getContext('2d')

	resetCanvas(canvas)
	
	ctx.beginPath()
	ctx.moveTo(0, -height/2)
	ctx.lineTo(0, height/2)
	ctx.moveTo(-width/2, 0)
	ctx.lineTo(width/2, 0)
	ctx.strokeStyle = '#0003'
	ctx.stroke()
	
	drawTriangle(ctx, triangle1)
	drawTriangle(ctx, triangle2)
	
	ctx.beginPath()
	ctx.ellipse(mouse.x, mouse.y, 4, 4, 0, 0, 2 * Math.PI)
	
	if (contains(triangle1, mouse)) {
	
		ctx.fillStyle = triangle1.color + 'a'
		ctx.fill()
		
	} else if (contains(triangle2, mouse)) {
	
		ctx.fillStyle = triangle2.color + 'a'
		ctx.fill()
		
	} else {
	
		ctx.strokeStyle = 'black'
		ctx.stroke()
		
	}
	
}

drawDots()
drawInteractive()










// trigo

function add(...points) {
	
	let x = 0, y = 0
	
	for (let point of points) {
	
		x += point.x
		y += point.y
	
	}
	
	return { x, y }

}

function center(...points) {
	
	let x = 0, y = 0
	
	for (let point of points) {
	
		x += point.x
		y += point.y
	
	}
	
	x /= points.length
	y /= points.length
	
	return { x, y }

}

function sub(A, B) {

	let x = A.x - B.x
	let y = A.y - B.y
	
	return { x, y }

}

function normalize({ x, y }, length = 10) {

	let r = length / Math.sqrt(x * x + y * y)
	
	x *= r
	y *= r
	
	return { x, y }

}

function rotate({ x, y }, angle = 90) {

	let length = Math.sqrt(x * x + y * y)
	
	angle *= Math.PI / 180
	angle += Math.atan2(y, x)
	
	x = length * Math.cos(angle)
	y = length * Math.sin(angle)
	
	return { x, y }

}
* {
	margin: 0;
}

html {
	font-family: monospace;
}

body {
	padding: 32px;
}

span.red {
	color: #f00;
}

span.blue {
	color: #00f;
}

canvas {
	position: absolute;
	border: solid 1px #ddd;
}
<p><span class="red">red triangle</span> is clockwise</p>
<p><span class="blue">blue triangle</span> is couter clockwise</p>
<br>
<div class="wrapper">
	<canvas id="dots"></canvas>
	<canvas id="interactive"></canvas>
</div>

resim açıklamasını buraya girin

Burada yukarıda açıklananla aynı yöntemi kullanıyorum: sırasıyla AB, BC, CA her satır "aynı" tarafında ise bir nokta ABC içinde.

üçgen içerme örneği


Ben bu kodu yorgun ve çalışmıyor. Her zaman False değerini döndürür.
xApple

hmmm ... muhtemelen bir hata yaptın. İşte bu fonksiyon çalışırken bir keman: jsfiddle.net/jniac/rctb3gfL
Joseph Merdrignac

Python yanıtınızı gördüm, aynı yöntemi kullanıyoruz, eğer bir tane daha satır ( let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)) kullanırsanız, bu üçgen sargı sırasını belirlemek içindir, böylece yöntem CW ve CCW üçgenleriyle çalışır (bkz. jsFiddle).
Joseph Merdrignac

1
hm bir hata yaptım, ben yazdım: let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)bunun yerine let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)bu sabit, raporlama için teşekkürler
Joseph Merdrignac

2

Andreas'ın verdiği barycentric koordinatlar çözümünü açıklamak için basit bir vektör matematiği kullanmak istiyorum, anlamak daha kolay olacak.

  1. Alan A, s * v02 + t * v01 tarafından verilen, s> = 0 ve t> = 0 koşullarıyla herhangi bir vektör olarak tanımlanır. V0, v1, v2 üçgeninin içindeki herhangi bir nokta, A Alanı içinde olmalıdır.

resim açıklamasını buraya girin

  1. Daha fazla kısıtlama varsa s ve t, [0, 1] 'e aitse. S * t, [s, t] 'ye ait olan tüm s * v02 + t * v01 vektörlerini içeren B Alanını elde ederiz. B Alanının düşük kısmının, Üçgen v0, v1, v2'nin aynası olduğunu belirtmek gerekir. Sorun, B Alanının düşük kısmını dışlamak için s ve t'nin belirli koşullarını verebilirsek ortaya çıkar.

resim açıklamasını buraya girin

  1. Varsayalım s değerini verdik ve t [0, 1] 'de değişiyor. Aşağıdaki resimde p noktası v1v2'nin kenarındadır. S * v02 + t * v01'in tüm vektörleri, çizgi vektörü boyunca basit vektör toplamı ile. V1v2 ve kesik çizgi çapraz noktası p'de:

(1-s) | v0v2 | / | v0v2 | = tp | v0v1 | / | v0v1 |

1 - s = tp, sonra 1 = s + tp alırız. Çift çizgi çizgisinde 1 <s + t olan herhangi bir t> tp varsa, vektör üçgenin dışında, herhangi bir t <= tp ise, 1> = s + t ise tek çizgi çizgisinde, vektör üçgen içinde.

Daha sonra [0, 1] 'de herhangi bir s verdiysek, karşılık gelen t, üçgen içindeki vektör için 1> = s + t değerine uymalıdır.

resim açıklamasını buraya girin

Sonunda v = s * v02 + t * v01 olsun, v üçgenin içinde, s, t, s + t koşulu [0, 1] 'e ait. Sonra noktaya çevir, biz var

p - p0 = s * (p1 - p0) + t * (p2 - p0), [0, 1] 'de s, t, s + t ile

Andreas'ın p = p0 + s * (p1 - p0) + t * (p2 - p0) denklem sistemini çözme çözümü ile aynıdır, s, t, s + t [0, 1] 'e aittir.


Kenarların s = 0, t = 0 ve s + t = 1 olması için üç köşe tarafından tanımlanan yerel çerçeveyi kullandığınızı söyleyebilirsiniz. Afin koordinat dönüşümü, doğrusal cebirin iyi bilinen bir işlemidir.
Yves Daoust

2

İşte python'da verimli, belgelenmiş ve üç birim test içeren bir çözüm. Profesyonel kalitede ve projenize olduğu gibi bir modül şeklinde bırakılmaya hazır.

import unittest

###############################################################################
def point_in_triangle(point, triangle):
    """Returns True if the point is inside the triangle
    and returns False if it falls outside.
    - The argument *point* is a tuple with two elements
    containing the X,Y coordinates respectively.
    - The argument *triangle* is a tuple with three elements each
    element consisting of a tuple of X,Y coordinates.

    It works like this:
    Walk clockwise or counterclockwise around the triangle
    and project the point onto the segment we are crossing
    by using the dot product.
    Finally, check that the vector created is on the same side
    for each of the triangle's segments.
    """
    # Unpack arguments
    x, y = point
    ax, ay = triangle[0]
    bx, by = triangle[1]
    cx, cy = triangle[2]
    # Segment A to B
    side_1 = (x - bx) * (ay - by) - (ax - bx) * (y - by)
    # Segment B to C
    side_2 = (x - cx) * (by - cy) - (bx - cx) * (y - cy)
    # Segment C to A
    side_3 = (x - ax) * (cy - ay) - (cx - ax) * (y - ay)
    # All the signs must be positive or all negative
    return (side_1 < 0.0) == (side_2 < 0.0) == (side_3 < 0.0)

###############################################################################
class TestPointInTriangle(unittest.TestCase):

    triangle = ((22 , 8),
                (12 , 55),
                (7 , 19))

    def test_inside(self):
        point = (15, 20)
        self.assertTrue(point_in_triangle(point, self.triangle))

    def test_outside(self):
        point = (1, 7)
        self.assertFalse(point_in_triangle(point, self.triangle))

    def test_border_case(self):
        """If the point is exactly on one of the triangle's edges,
        we consider it is inside."""
        point = (7, 19)
        self.assertTrue(point_in_triangle(point, self.triangle))

###############################################################################
if __name__ == "__main__":
    suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestPointInTriangle)
    unittest.TextTestRunner().run(suite)

Yukarıdaki algoritmanın geçerliliğini doğrulamak için isteğe bağlı ek bir grafik testi vardır:

import random
from matplotlib import pyplot
from triangle_test import point_in_triangle

###############################################################################
# The area #
size_x = 64
size_y = 64

# The triangle #
triangle = ((22 , 8),
            (12 , 55),
            (7 , 19))

# Number of random points #
count_points = 10000

# Prepare the figure #
figure = pyplot.figure()
axes = figure.add_subplot(111, aspect='equal')
axes.set_title("Test the 'point_in_triangle' function")
axes.set_xlim(0, size_x)
axes.set_ylim(0, size_y)

# Plot the triangle #
from matplotlib.patches import Polygon
axes.add_patch(Polygon(triangle, linewidth=1, edgecolor='k', facecolor='none'))

# Plot the points #
for i in range(count_points):
    x = random.uniform(0, size_x)
    y = random.uniform(0, size_y)
    if point_in_triangle((x,y), triangle): pyplot.plot(x, y, '.g')
    else:                                  pyplot.plot(x, y, '.b')

# Save it #
figure.savefig("point_in_triangle.pdf")

Aşağıdaki grafiği üretmek:

Point_in_triangle işlevini test etme


1

Bir noktanın tam olarak bitişik iki üçgenin ortak kenarında olduğu sinir bozucu kenar koşulları vardır. Nokta, her iki üçgende veya üçgenlerin hiçbirinde olamaz. Noktayı atamanın keyfi ancak tutarlı bir yoluna ihtiyacınız vardır. Örneğin, nokta boyunca yatay bir çizgi çizin. Çizgi sağ taraftaki üçgenin diğer tarafıyla kesişirse, nokta üçgenin içindeymiş gibi işlenir. Kavşak solda ise, nokta dışarıdadır.

Noktanın bulunduğu çizgi yataysa, yukarı / aşağı kullanın.

Nokta, birden çok üçgenin ortak tepe noktasındaysa, merkezi noktanın en küçük açıyı oluşturduğu üçgeni kullanın.

Daha eğlenceli: Üç nokta düz bir çizgide olabilir (sıfır derece), örneğin (0,0) - (0,10) - (0,5). Bir üçgenleme algoritmasında, "kulak" (0,10) kapatılmalı, üretilen "üçgen" düz bir çizginin dejenere olgusudur.


1

Bir noktanın üçgenin içinde mi yoksa dışında mı yoksa bir üçgenin kolunda mı olduğunu belirlemek için en basit kavram budur.

Bir noktanın determinantlarla bir üçgen içinde belirlenmesi:

Bir noktanın determinantlarla bir üçgen içinde belirlenmesi

En basit çalışma kodu:

#-*- coding: utf-8 -*-

import numpy as np

tri_points = [(1,1),(2,3),(3,1)]

def pisinTri(point,tri_points):
    Dx , Dy = point

    A,B,C = tri_points
    Ax, Ay = A
    Bx, By = B
    Cx, Cy = C

    M1 = np.array([ [Dx - Bx, Dy - By, 0],
                    [Ax - Bx, Ay - By, 0],
                    [1      , 1      , 1]
                  ])

    M2 = np.array([ [Dx - Ax, Dy - Ay, 0],
                    [Cx - Ax, Cy - Ay, 0],
                    [1      , 1      , 1]
                  ])

    M3 = np.array([ [Dx - Cx, Dy - Cy, 0],
                    [Bx - Cx, By - Cy, 0],
                    [1      , 1      , 1]
                  ])

    M1 = np.linalg.det(M1)
    M2 = np.linalg.det(M2)
    M3 = np.linalg.det(M3)
    print(M1,M2,M3)

    if(M1 == 0 or M2 == 0 or M3 ==0):
            print("Point: ",point," lies on the arms of Triangle")
    elif((M1 > 0 and M2 > 0 and M3 > 0)or(M1 < 0 and M2 < 0 and M3 < 0)):
            #if products is non 0 check if all of their sign is same
            print("Point: ",point," lies inside the Triangle")
    else:
            print("Point: ",point," lies outside the Triangle")

print("Vertices of Triangle: ",tri_points)
points = [(0,0),(1,1),(2,3),(3,1),(2,2),(4,4),(1,0),(0,4)]
for c in points:
    pisinTri(c,tri_points)


0

Dürüst olmak gerekirse, Simon P Steven'ın cevabı kadar basittir, ancak bu yaklaşımla, üçgenin kenarlarındaki noktaların dahil edilmesini isteyip istemediğiniz üzerinde katı bir kontrole sahip değilsiniz.

Benim yaklaşımım biraz farklı ama çok basit. Aşağıdaki üçgeni düşünün;

resim açıklamasını buraya girin

Üçgendeki noktayı elde etmek için 3 koşulu yerine getirmeliyiz

  1. ACE açısı (yeşil) ACB açısından (kırmızı) küçük olmalıdır
  2. ECB açısı (mavi) ACB açısından (kırmızı) küçük olmalıdır
  3. X ve y değerleri | AB | denklemine uygulandığında E Noktası ve C Noktası shoud aynı işarete sahiptir. hat.

Bu yöntemde, kenarlardaki noktayı ayrı ayrı dahil etmek veya hariç tutmak için tam kontrole sahipsiniz. Üçgen içinde sadece | AC | örneğin kenar.

Bu yüzden JavaScript'teki çözümüm şöyle olacak;

function isInTriangle(t,p){

  function isInBorder(a,b,c,p){
    var m = (a.y - b.y) / (a.x - b.x);                     // calculate the slope
    return Math.sign(p.y - m*p.x + m*a.x - a.y) === Math.sign(c.y - m*c.x + m*a.x - a.y);
  }
  
  function findAngle(a,b,c){                               // calculate the C angle from 3 points.
    var ca = Math.hypot(c.x-a.x, c.y-a.y),                 // ca edge length
        cb = Math.hypot(c.x-b.x, c.y-b.y),                 // cb edge length
        ab = Math.hypot(a.x-b.x, a.y-b.y);                 // ab edge length
    return Math.acos((ca*ca + cb*cb - ab*ab) / (2*ca*cb)); // return the C angle
  }

  var pas = t.slice(1)
             .map(tp => findAngle(p,tp,t[0])),             // find the angle between (p,t[0]) with (t[1],t[0]) & (t[2],t[0])
       ta = findAngle(t[1],t[2],t[0]);
  return pas[0] < ta && pas[1] < ta && isInBorder(t[1],t[2],t[0],p);
}

var triangle = [{x:3, y:4},{x:10, y:8},{x:6, y:10}],
      point1 = {x:3, y:9},
      point2 = {x:7, y:9};

console.log(isInTriangle(triangle,point1));
console.log(isInTriangle(triangle,point2));


0
bool isInside( float x, float y, float x1, float y1, float x2, float y2, float x3, float y3 ) {
  float l1 = (x-x1)*(y3-y1) - (x3-x1)*(y-y1), 
    l2 = (x-x2)*(y1-y2) - (x1-x2)*(y-y2), 
    l3 = (x-x3)*(y2-y3) - (x2-x3)*(y-y3);
  return (l1>0 && l2>0  && l3>0) || (l1<0 && l2<0 && l3<0);
}

Bundan daha verimli olamaz! Bir üçgenin her bir tarafı bağımsız pozisyona ve yöne sahip olabilir, bu nedenle üç hesaplama vardır: l1, l2 ve l3, her biri 2 çarpımı içeren kesinlikle gereklidir. L1, l2 ve l3 bilindikten sonra, sonuç sadece birkaç temel karşılaştırma ve boole işlemidir.


0

JavaScript'te uyarladığım yüksek performanslı kod (aşağıdaki makale):

function pointInTriangle (p, p0, p1, p2) {
  return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}
  • pointInTriangle(p, p0, p1, p2) - saat yönünün tersine üçgenler için
  • pointInTriangle(p, p0, p1, p2) - saat yönünde üçgenler için

JSFiddle'a bakın (performans testi dahil), ayrı bir işlevde sargı kontrolü de vardır. Veya aşağıdaki "Kod snippet'ini çalıştır" a basın

var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;

var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();

$("canvas").click(function(evt) {
    point.x = evt.pageX - $(this).offset().left;
    point.y = evt.pageY - $(this).offset().top;
    test();
});

$("canvas").dblclick(function(evt) {
    triangle = randomTriangle();
    test();
});

document.querySelector('#performance').addEventListener('click', _testPerformance);

test();

function test() {
    var result = checkClockwise(triangle.a, triangle.b, triangle.c) ? pointInTriangle(point, triangle.a, triangle.c, triangle.b) : pointInTriangle(point, triangle.a, triangle.b, triangle.c);
    
    var info = "point = (" + point.x + "," + point.y + ")\n";
    info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
    info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
    info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
    info += "result = " + (result ? "true" : "false");

    $("#result").text(info);
    render();
}

function _testPerformance () {
	var px = [], py = [], p0x = [], p0y = [], p1x = [], p1y = [], p2x = [], p2y = [], p = [], p0 = [], p1 = [], p2 = [];
    
	for(var i = 0; i < 1000000; i++) {
    p[i] = {x: Math.random() * 100, y: Math.random() * 100};
    p0[i] = {x: Math.random() * 100, y: Math.random() * 100};
    p1[i] = {x: Math.random() * 100, y: Math.random() * 100};
    p2[i] = {x: Math.random() * 100, y: Math.random() * 100};
  }
  console.time('optimal: pointInTriangle');
  for(var i = 0; i < 1000000; i++) {
    pointInTriangle(p[i], p0[i], p1[i], p2[i]);
  }
  console.timeEnd('optimal: pointInTriangle');

  console.time('original: ptInTriangle');
  for(var i = 0; i < 1000000; i++) {
  	ptInTriangle(p[i], p0[i], p1[i], p2[i]);
  }
  console.timeEnd('original: ptInTriangle');
}

function pointInTriangle (p, p0, p1, p2) {
	return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}

function ptInTriangle(p, p0, p1, p2) {
    var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
    var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);

    if (s <= 0 || t <= 0) return false;

    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    return (s + t) < A;
}

function render() {
    ctx.fillStyle = "#CCC";
    ctx.fillRect(0, 0, 500, 500);
    drawTriangle(triangle.a, triangle.b, triangle.c);
    drawPoint(point);
}

function checkClockwise(p0, p1, p2) {
    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    return A > 0;
}

function drawTriangle(p0, p1, p2) {
    ctx.fillStyle = "#999";
    ctx.beginPath();
    ctx.moveTo(p0.x, p0.y);
    ctx.lineTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.font = "12px monospace";
    ctx.fillText("1", p0.x, p0.y);
    ctx.fillText("2", p1.x, p1.y);
    ctx.fillText("3", p2.x, p2.y);
}

function drawPoint(p) {
    ctx.fillStyle = "#F00";
    ctx.beginPath();
    ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
    ctx.fill();
}

function rand(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomTriangle() {
    return {
        a: { x: rand(0, W), y: rand(0, H) },
        b: { x: rand(0, W), y: rand(0, H) },
        c: { x: rand(0, W), y: rand(0, H) }
    };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="performance">Run performance test (open console)</button>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>

Bundan ilham alan: http://www.phatcode.net/articles.php?id=459


-1
bool point2Dtriangle(double e,double f, double a,double b,double c, double g,double h,double i, double v, double w){
    /* inputs: e=point.x, f=point.y
               a=triangle.Ax, b=triangle.Bx, c=triangle.Cx 
               g=triangle.Ay, h=triangle.By, i=triangle.Cy */
    v = 1 - (f * (b - c) + h * (c - e) + i * (e - b)) / (g * (b - c) + h * (c - a) + i * (a - b));
    w = (f * (a - b) + g * (b - e) + h * (e - a)) / (g * (b - c) + h * (c - a) + i * (a - b));
    if (*v > -0.0 && *v < 1.0000001 && *w > -0.0 && *w < *v) return true;//is inside
    else return false;//is outside
    return 0;
} 

Bariyer merkezden dönüştürülen neredeyse mükemmel Kartezyen koordinatlar * v (x) ve * w (y) iki katına çıkarılır. Her iki dışa aktarma iki katının her durumda önceden * karakteri olmalıdır, büyük olasılıkla: * v ve * w Kod, bir dörtgenin diğer üçgeni için de kullanılabilir. Burada imzalı saat yönünde abcd dörtlü sadece abc üçgeni yazdı.

A---B
|..\\.o|  
|....\\.| 
D---C 

o noktası, ikinci üçgenle test için ABC üçgeninin içindedir bu işlevi CDA yönüne çağırır ve sonuçlar dörtgenden sonra *v=1-*v;ve *w=1-*w;dörtgen için doğru olmalıdır


-1

Üçgenlerin saat yönünde olacağından kesinlikle emin olduğunuzda "kontrol edilebilir ortamda" üçgen kontrolünde noktaya ihtiyacım vardı. Böylece, Perro Azul'un jsfiddle'ını aldım ve bu gibi durumlar için coproc tarafından önerildiği gibi değiştirdim ; Ayrıca, birbirlerini iptal ettikleri için fazladan 0,5 ve 2 çarpımı da kaldırmışlardır.

http://jsfiddle.net/dog_funtom/H7D7g/

var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;

var point = {
    x: W / 2,
    y: H / 2
};
var triangle = randomTriangle();

$("canvas").click(function (evt) {
    point.x = evt.pageX - $(this).offset().left;
    point.y = evt.pageY - $(this).offset().top;
    test();
});

$("canvas").dblclick(function (evt) {
    triangle = randomTriangle();
    test();
});

test();

function test() {
    var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);

    var info = "point = (" + point.x + "," + point.y + ")\n";
    info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
    info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
    info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
    info += "result = " + (result ? "true" : "false");

    $("#result").text(info);
    render();
}

function ptInTriangle(p, p0, p1, p2) {
    var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
    var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);

    if (s <= 0 || t <= 0) return false;

    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);

    return (s + t) < A;
}

function checkClockwise(p0, p1, p2) {
    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    return A > 0;
}

function render() {
    ctx.fillStyle = "#CCC";
    ctx.fillRect(0, 0, 500, 500);
    drawTriangle(triangle.a, triangle.b, triangle.c);
    drawPoint(point);
}

function drawTriangle(p0, p1, p2) {
    ctx.fillStyle = "#999";
    ctx.beginPath();
    ctx.moveTo(p0.x, p0.y);
    ctx.lineTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.font = "12px monospace";
    ctx.fillText("1", p0.x, p0.y);
    ctx.fillText("2", p1.x, p1.y);
    ctx.fillText("3", p2.x, p2.y);
}

function drawPoint(p) {
    ctx.fillStyle = "#F00";
    ctx.beginPath();
    ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
    ctx.fill();
}

function rand(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomTriangle() {
    while (true) {
        var result = {
            a: {
                x: rand(0, W),
                y: rand(0, H)
            },
            b: {
                x: rand(0, W),
                y: rand(0, H)
            },
            c: {
                x: rand(0, W),
                y: rand(0, H)
            }
        };
        if (checkClockwise(result.a, result.b, result.c)) return result;
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>

<pre id="result"></pre>

<canvas width="500" height="500"></canvas>

İşte Unity için eşdeğer C # kodu:

public static bool IsPointInClockwiseTriangle(Vector2 p, Vector2 p0, Vector2 p1, Vector2 p2)
{
    var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
    var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);

    if (s <= 0 || t <= 0)
        return false;

    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);

    return (s + t) < A;
}

-3

Üçgenin (x1, y1), (x2, y2), (x3, y3) köşeleri tarafından oluşturulan alanın pozitif olup olmadığını kontrol etmenin en kolay yollarından biri.

Alan formülle hesaplanabilir:

1/2 [x1 (y2 – y3) + x2 (y3 – y1) + x3 (y1 – y2)]

veya python kodu şu şekilde yazılabilir:

def triangleornot(p1,p2,p3):
    return (1/ 2) [p1[0](p2[1]–p3[1]) + p2[0] (p3[1]–p1[1]) + p3[0] (p1[0]–p2[0])]
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.