วิธีการตรวจสอบว่าจุดอยู่ในรูปสามเหลี่ยม 2D? [ปิด]


258

มีวิธีง่าย ๆ ในการตรวจสอบว่าจุดที่อยู่ภายในรูปสามเหลี่ยม? มันเป็น 2D ไม่ใช่ 3D


15
ฉันเขียนบทความฉบับสมบูรณ์เกี่ยวกับประเด็นในการทดสอบรูปสามเหลี่ยม มันแสดงให้เห็นถึงวิธีการตาม barycentric, พารามิเตอร์และจุดผลิตภัณฑ์ จากนั้นมันจะจัดการกับปัญหาความแม่นยำที่เกิดขึ้นเมื่อมีจุดอยู่ตรงขอบหนึ่ง (พร้อมตัวอย่าง) ในที่สุดมันก็เปิดเผยวิธีการใหม่ที่สมบูรณ์ขึ้นอยู่กับระยะห่างจากจุดหนึ่งไปยังอีกขอบหนึ่ง totologic.blogspot.fr/2014/01/…สนุกได้เลย!
ลอจิก


1
เป็นที่น่าสังเกตว่าวิธีการใด ๆ ที่กล่าวถึงที่นี่ใช้ได้ในพื้นที่ 3D เช่นกัน พวกเขาเพียงแค่ต้องนำหน้าด้วยการแปลงพิกัด (และการฉายภาพที่เหมาะสมของจุดบนระนาบของรูปสามเหลี่ยม) รูปสามเหลี่ยมเป็นวัตถุ 2 มิติ
andreasdr

สำหรับวิธีการแก้ปัญหาที่เป็นอิสระจากการไขลาน นี่คือซอที่ทำงาน: jsfiddle.net/ibowankenobi/oex3pzq2
อิบราฮิม tanyalcin

2
ฉันลงคะแนนให้ปิดคำถามนี้เพราะมันเกี่ยวกับคณิตศาสตร์มากกว่าการเขียนโปรแกรมและอิงตามความคิดเห็น ("ง่าย" สำหรับคุณคืออะไร)
TylerH

คำตอบ:


264

โดยทั่วไปอัลกอริทึมที่ง่ายที่สุด (และเหมาะสมที่สุด) คือการตรวจสอบว่าด้านใดของระนาบครึ่งที่สร้างโดยขอบของจุดนั้น

นี่คือข้อมูลคุณภาพสูงในหัวข้อนี้ใน GameDevรวมถึงปัญหาด้านประสิทธิภาพ

และนี่คือรหัสเพื่อให้คุณเริ่มต้น:

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
มันใช้กันทั่วไปในแบบ 2D พิกัด Barycentric มักทำให้ผู้คนสับสน ยิ่งไปกว่านั้นเมื่อได้รับรูปสามเหลี่ยมและจุดรองฉันไม่แน่ใจเกี่ยวกับประสิทธิภาพของการใช้ barycentrics
Kornel Kisielewicz

7
@Kornel เวอร์ชัน barycentric มีประสิทธิภาพมากขึ้นในแบบ 2D เช่นกัน วิธีการแก้ปัญหาของคุณยังมีปัญหาที่จะรายงานผลที่แตกต่างกันสำหรับจุดที่ขอบของรูปสามเหลี่ยมขึ้นอยู่กับว่าสามเหลี่ยมระบุตามเข็มนาฬิกาหรือตามลำดับทวนเข็มนาฬิกา
Andreas Brinck

9
สำหรับวัตถุประสงค์ของฉัน (เหตุผลที่ฉันพบเว็บไซต์นี้) คำตอบเดิมที่เสนอโดย Kornel Kisielewicz นั้นมีประสิทธิภาพมากกว่ามาก ฉันกำลังทำงานกับจอ LCD ที่มีพิกัดขนาด BYTE และไมโครโพรเซสเซอร์ทั่วไปซึ่งจำนวนเต็มคูณเป็นคำสั่งที่รวดเร็วมากและการหารนั้นช้ากว่ามาก ปัญหาตัวเลขยังเล็กกว่ามากเนื่องจากไม่มีการแบ่ง! การคำนวณทั้งหมดนั้นแน่นอน ขอบคุณ Rick

4
ดังนั้นฟังก์ชั่น sign () จะบอกคุณว่าด้านใดของ halfplane (เกิดจากเส้นแบ่งระหว่าง p2 และ p3) p1 คืออะไร?
David Doria

1
โปรดทราบว่าถ้าคุณสมมติลำดับของจุดยอด (พูดทวนเข็มนาฬิกา) คุณไม่จำเป็นต้องคำนวณดีเทอร์มิแนนต์ทั้งหมดตลอดเวลา ในความเป็นจริงในกรณีที่ดีที่สุด 1 ดีเทอร์มิแนนต์เพียงพอที่จะพบว่าจุดนั้นไม่ได้อยู่ในรูปสามเหลี่ยม
Thash

176

แก้ระบบสมการต่อไปนี้:

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

จุดpอยู่ภายในรูปสามเหลี่ยมถ้า0 <= s <= 1และและ0 <= t <= 1s + t <= 1

s, tและ1 - s - tจะเรียกว่าพิกัด Barycentricpของจุด


1
นี่เร็วกว่าการตรวจสอบแบบครึ่งระนาบ แต่อาจจะยากกว่านี้หากคุณยังใหม่กับพิกัดแบบไบริเซนทริก
Daniel Rikowski

8
ด้วยทางออกเล็กน้อย (ไม่ได้ใช้) ในวิธีการของ Kornel เขาสามารถมีประสิทธิภาพมากกว่าของคุณ หากคุณพยายามคำนวณและคุณจะรู้ว่าฉันหมายถึงอะไร

85
ฉันต้องการทดสอบสิ่งนี้ดังนั้นฉันจึงสร้าง jsfiddle โดยใช้ @andreasdr solution และ coproc ความคิดเห็น: jsfiddle.net/PerroAZUL/zdaY8/1
urraka

5
การเพิ่มประสิทธิภาพ: s + t <= 1นัยs <= 1และt <= 1ถ้าและs >= 0 t >= 0
Thomas Eding

7
บทความtotologic.blogspot.fr/2014/01/…ที่เสนอโดย @Logic โพสต์ช่วยให้ฉันเข้าใจวิธีแก้ปัญหานี้ได้ดีขึ้น
Flayn

112

ฉันเห็นด้วยกับAndreas Brinckพิกัด barycentric นั้นสะดวกมากสำหรับงานนี้ โปรดทราบว่าไม่จำเป็นต้องแก้ระบบสมการทุกครั้ง: เพียงแค่ประเมินโซลูชันการวิเคราะห์ ใช้สัญกรณ์ของAndreasวิธีแก้คือ:

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เป็นพื้นที่ (ลงนาม) ของรูปสามเหลี่ยม:

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

เพียงแค่การประเมินs, และt 1-s-tจุดpนั้นอยู่ในรูปสามเหลี่ยมถ้าหากมันเป็นบวกทั้งหมด

แก้ไข: โปรดทราบว่าการแสดงออกด้านบนสำหรับพื้นที่ถือว่าหมายเลขโหนดสามเหลี่ยมเป็นทวนเข็มนาฬิกา หากตัวเลขเป็นตามเข็มนาฬิกานิพจน์นี้จะส่งคืนพื้นที่ลบ (แต่มีขนาดที่ถูกต้อง) การทดสอบตัวเอง ( s>0 && t>0 && 1-s-t>0) ไม่ได้ขึ้นอยู่กับทิศทางของการกำหนดหมายเลขอย่างไรก็ตามเนื่องจากการแสดงออกข้างต้นที่ถูกคูณด้วย1/(2*Area)การเปลี่ยนเครื่องหมายหากการวางแนวโหนดสามเหลี่ยมเปลี่ยนไป

แก้ไข 2: เพื่อประสิทธิภาพการคำนวณที่ดียิ่งขึ้นโปรดดูความคิดเห็นของcoprocด้านล่าง (ซึ่งทำให้ประเด็นที่ว่าหากการวางแนวของโหนดสามเหลี่ยม (ตามเข็มนาฬิกาหรือทวนเข็มนาฬิกา) เป็นที่ทราบกันล่วงหน้าการหารด้วย2*Areaนิพจน์สำหรับsและtสามารถ หลีกเลี่ยง) ดูรหัส jsfiddle ของPerro Azulในข้อคิดเห็นภายใต้คำตอบของAndreas Brinck


6
นั่นคือการแก้ระบบสมการ :)
22460 Andreas Brinck

1
ใช่ประเด็นของฉันคือการวิจารณ์วิธีการของคุณตามต้นทุนการคำนวณในการแก้ระบบสมการนั้นไม่มีมูลความจริงเนื่องจากมันไม่จำเป็นต้องทำในฐานะเป็นส่วนหนึ่งของอัลกอริธึม
andreasdr

13
ประสิทธิภาพสามารถปรับปรุงได้โดยไม่หารผ่าน2*Areaเช่นการคำนวณs´=2*|Area|*sและt´=2*|Area|*t(หากการวางแนวของคะแนน - ตามเข็มนาฬิกาหรือทวนเข็มนาฬิกา - ไม่เป็นที่รู้จักAreaต้องมีการตรวจสอบเครื่องหมายของแน่นอน แต่ไม่เช่นนั้นอาจไม่ได้ จะต้องมีการคำนวณ) ตั้งแต่การตรวจสอบก็พอเพียงที่จะตรวจสอบs>0 s´>0และแทนการตรวจสอบก็พอเพียงที่จะตรวจสอบ1-s-t>0 s´+t´<2*|Area|
coproc

1
ฉันอาจเพิ่มว่าถ้าp0->p1->p2เป็นทวนเข็มนาฬิกาในคาร์ทีเซียน (ซึ่งมักจะเป็นตามเข็มนาฬิกาในพิกัดหน้าจอ ) การAreaคำนวณโดยวิธีนี้จะเป็นบวก
rhgb

1
@ user2600366 เมื่อคุณเดินทางไปตามขอบเขตของรูปสามเหลี่ยมในทิศทาง p0 -> p1 -> p2 -> p0 เป็นต้นคุณจะมีการตกแต่งภายในของรูปสามเหลี่ยมไปทางขวาหรือทางซ้ายเสมอ ในกรณีก่อนหน้าการนับหมายเลขตามเข็มนาฬิกาในกรณีหลังจะเป็นทวนเข็มนาฬิกา
andreasdr

47

ฉันเขียนรหัสนี้ก่อนที่จะพยายามครั้งสุดท้ายกับ Google และค้นหาหน้านี้ดังนั้นฉันคิดว่าฉันต้องการแชร์ มันเป็นรุ่นที่ดีที่สุดของคำตอบ Kisielewicz ฉันมองไปที่วิธี Barycentric เช่นกัน แต่การตัดสินจากบทความ Wikipedia ฉันมีเวลายากที่จะเห็นว่ามันมีประสิทธิภาพมากขึ้นได้อย่างไร อย่างไรก็ตามอัลกอริทึมนี้มีข้อดีของการไม่ใช้การแบ่ง ปัญหาที่อาจเกิดขึ้นคือพฤติกรรมของการตรวจจับขอบขึ้นอยู่กับทิศทาง

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

ในคำพูดความคิดคือ: เป็นจุดด้านซ้ายหรือด้านขวาของทั้งสาย AB และ AC? ถ้าเป็นจริงมันไม่สามารถอยู่ข้างในได้ หากเป็นเท็จมันเป็นอย่างน้อยภายใน "กรวย" ที่ตรงตามเงื่อนไข ตอนนี้เนื่องจากเรารู้ว่าจุดภายในตรีโกณมิติ (สามเหลี่ยม) ต้องอยู่ในด้านเดียวกันของ AB เป็น BC (และ CA) เราจึงตรวจสอบว่ามันแตกต่างกันหรือไม่ หากพวกเขาทำสิ่งนั้นจะไม่สามารถอยู่ภายในมิฉะนั้นจะต้องอยู่ภายใน

คำหลักบางคำในการคำนวณนั้นเป็นระนาบครึ่งบรรทัดและตัวกำหนด (2x2 cross product) บางทีวิธีการสอนมากขึ้นอาจจะคิดว่ามันเป็นจุดที่อยู่ภายใน iff มันอยู่ในด้านเดียวกัน (ซ้ายหรือขวา) ไปยังแต่ละบรรทัด AB, BC และ CA วิธีข้างต้นดูเหมือนจะเหมาะสมกว่าสำหรับการเพิ่มประสิทธิภาพบางอย่าง


2
การทดสอบนี้เร็วกว่าการทดสอบครั้งแรกประมาณ 140-180% (ขอบคุณทั้งคุณ btw :) ฉันรันโค้ดที่นี่: paste.ubuntu.com/p/k5w7ywH4p8โดยใช้เครื่องมือ nodejs v8 ที่ปิดการใช้งานการปรับให้เหมาะสมและได้ผลลัพธ์ต่อไปนี้:: w! node -p - การทดสอบขั้นต่ำ 1: 114.852ms test2: 64.330ms test1: 115.350ms test2: 63.491ms test1: 117.671ms test2: 65.353ms test1: 119.146ms test2: 63.871ms test1: 118.271ms test1: 118.671ms test2: 63.352ms
ผ่าตัด

@surgemcgee ทำไมคุณถึงเรียกใช้โดยไม่มีการเพิ่มประสิทธิภาพ นั่นไม่ได้ถูกลบออกไปจากความเป็นจริงอีกแล้วใช่ไหม
xuiqzy

@xuiqzy ดีโปรแกรมของฉันมีโซลูชั่นที่แตกต่างกันสองแบบ ฉันยังไม่ได้จัดการวิธีที่เร็วที่สุดในการทำมัน บางทีนั่นอาจจะแสดงความคิดเห็นจะถูกลบออกไปและแทนที่ด้วยผลงานที่เสร็จสมบูรณ์ของฉันเกี่ยวกับเรื่องนี้ ..
surgemcgee

33

วิธีการ barycentric รุ่นC #ที่โพสต์โดย andreasdr และ Perro Azul โปรดทราบว่าการคำนวณพื้นที่สามารถหลีกเลี่ยงได้หากsและtมีสัญญาณตรงข้าม ฉันตรวจสอบพฤติกรรมที่ถูกต้องด้วยการทดสอบหน่วยอย่างละเอียด

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

[ แก้ไข ]
ยอมรับการแก้ไขที่แนะนำโดย @Pierre; ดูความคิดเห็น


การแก้ปัญหาด้วยการสิ้นสุดถ้าคำสั่งทำงานสำหรับจุดสามเหลี่ยมตามเข็มนาฬิกาและทวนเข็มนาฬิกา
ลุคดูปิน

@LukeDupin ไม่แน่ใจว่าฉันเข้าใจความคิดเห็นของคุณ คำตอบนี้ทำงานเป็นโพสต์สำหรับการสั่งซื้อใด ๆ ของ 3 คะแนน
Glenn Slayden

12

เวอร์ชัน Java ของวิธีการ barycentric:

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

รหัสข้างต้นจะทำงานอย่างถูกต้องกับจำนวนเต็มสมมติว่าไม่มีการล้น มันจะทำงานร่วมกับสามเหลี่ยมตามเข็มนาฬิกาและทวนเข็มนาฬิกา มันจะไม่ทำงานกับสามเหลี่ยม collinear (แต่คุณสามารถตรวจสอบได้โดยการทดสอบ det == 0)

เวอร์ชัน barycentric นั้นเร็วที่สุดถ้าคุณจะทดสอบจุดต่าง ๆ ด้วยสามเหลี่ยมเดียวกัน

รุ่น barycentric ไม่ได้เป็นแบบสมมาตรในจุดสามเหลี่ยม 3 จุดดังนั้นจึงมีความสอดคล้องกันน้อยกว่ารุ่นครึ่งระนาบของ Kornel Kisielewicz เนื่องจากข้อผิดพลาดในการปัดเศษทศนิยม

เครดิต: ฉันทำโค้ดด้านบนจากบทความของ Wikipedia เกี่ยวกับพิกัด barycentric


เยี่ยมมาก! นอกจากนี้ยังสามารถปรับปรุงให้ใช้ tuples Point3f / Point2f ของ javax.vecmath เพื่อให้จัดการกับอินพุตข้อมูลได้ดียิ่งขึ้น
Alex Byrth

10

วิธีง่ายๆคือ:

หาเวกเตอร์ที่เชื่อมโยงจุดกับจุดยอดสามจุดของสามเหลี่ยมแต่ละอันแล้วหาผลรวมมุมระหว่างเวกเตอร์เหล่านั้น หากผลรวมของมุมเป็น 2 * pi จุดนั้นจะอยู่ภายในรูปสามเหลี่ยม

สองเว็บไซต์ที่ดีที่อธิบายทางเลือกคือ:

blackpawnและวุลแฟรม


3
อืมวิธีการนั้นไม่ได้มีประสิทธิภาพอย่างแน่นอนและมีแนวโน้มที่จะเกิดข้อผิดพลาดเชิงตัวเลขมาก ...
Kornel Kisielewicz

มันค่อนข้างตรงกันข้ามมันไม่มีประสิทธิภาพมาก :-) เป็นเพียงวิธีง่ายๆวิธีหนึ่งที่ใช้งานง่าย คุณสามารถยกตัวอย่างข้อผิดพลาดเกี่ยวกับตัวเลขที่จะทำให้เกิดปัญหานี้ได้หรือไม่?
Simon P Stevens

ในขณะที่ฉันนี่ดูเหมือนจะเป็นคำตอบที่ดีที่สุดในหัวข้อนี้ฉันเดาว่าจุดที่ขอบของสามเหลี่ยมถูกคำนวณเพื่อรวมไว้ในรูปสามเหลี่ยมและคุณไม่มีการควบคุมที่ดี
Redu

ตรวจสอบว่ามันเป็น 2pi เป็นไปไม่ได้ตัวเลขที่ได้รับเพราะเหตุผลของ อย่างไรก็ตามคุณเพียงแค่ต้องตรวจสอบว่ามุมรวมถึงบางสิ่งที่ยิ่งใหญ่กว่าปี่
lonewarrior556

10

โดยใช้โซลูชันการวิเคราะห์เพื่อพิกัด barycentric (ชี้ให้เห็นโดยAndreas Brinck ) และ:

  • ไม่กระจายการคูณในเทอมวงเล็บ
  • หลีกเลี่ยงการคำนวณคำเดียวกันหลายครั้งด้วยการเก็บไว้
  • การลดการเปรียบเทียบ (ตามที่อธิบายโดยcoprocและThomas Eding )

หนึ่งสามารถลดจำนวนการดำเนินการ "แพง":

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

รหัสสามารถวางในPerro Azul jsfiddleหรือลองโดยคลิก "เรียกใช้ข้อมูลโค้ด" ด้านล่าง

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>

นำไปสู่:

  • ตัวแปร "เรียกคืน": 30
  • ที่เก็บตัวแปร: 7
  • เพิ่มเติม: 4
  • การลบ: 8
  • การคูณ: 6
  • ดิวิชั่น: ไม่มี
  • การเปรียบเทียบ: 4

สิ่งนี้เปรียบเทียบได้ค่อนข้างดีกับKornel Kisielewicz solution (เรียกคืน 25 ครั้ง, ที่เก็บ 1 ครั้ง, การลบ 15 ครั้ง, การคูณ 6 ครั้ง, การเปรียบเทียบ 5 ครั้ง) และอาจดีกว่านี้หากต้องการการตรวจจับตามเข็มนาฬิกา / ทวนเข็มนาฬิกา , 2 การคูณและ 1 การเปรียบเทียบในตัวมันเองโดยใช้ดีเทอร์มิแนนต์โซลูชันการวิเคราะห์ตามที่ชี้โดยrhgb )


ทางออกที่ดี ฉันคิดว่ามันค่อนข้างเทียบเท่ากับวิธีการสุดท้ายของฉันที่นี่ใน MSE: math.stackexchange.com/questions/51326/ …
Jack D'Aurizio

ฉันเพิ่งทดสอบโค้ดตามที่เป็นอยู่และมันไม่ทำงานสำหรับฉัน (ตัวอย่าง p -4.69317198, -6.99191951 p0 -7.05846786 0.596718192 p1 -6.8703599 -2.36565161 p2 -4.69317198, -6.99191951)
Giovanni Funchal

@GiovanniFunchal Strange ตัวอย่างของคุณใช้ได้กับฉันทั้งใน jsfiddle (แทนที่คำว่า "จุด" และ "สามเหลี่ยม" เริ่มต้น) และการใช้ Python ในพื้นที่ของฉัน ปัญหาความแม่นยำเป็นตัวเลข (ลองลอกทศนิยม) หรือไม่
Cédric Dufour

1
ดูเหมือนว่าคุณเร็วที่สุดในการทดสอบของฉัน: jsfiddle.net/eyal/gxw3632c/27 ความแตกต่างระหว่างวิธีการทั้งหมดนั้นค่อนข้างเล็ก
Eyal

ลองสามเหลี่ยม (-1, -1), (1, -1), (0,1) และจุด (0, -1) ส่งกลับค่าเท็จเมื่อมันควรจะกลับเป็นจริงเพราะ s (2) + t (2)> d (2) มีบางอย่างผิดปกติกับคณิตศาสตร์บนขอบของสามเหลี่ยมดูเหมือนว่าจุด p อยู่ตรงขอบระหว่าง p0 และ p1 และไม่ใช่เรื่องง่าย ๆ ในการแปลง <เป็น <= หรือบางอย่างเช่นนั้น
devnullicus

5

สิ่งที่ฉันทำก็คือการคำนวณพื้นฐานของใบหน้าทั้งสาม

  • ใน 3D โดย cross product ของเวกเตอร์ด้านข้างและเวกเตอร์ปกติของใบหน้า

  • ใน 2D โดยเพียงแค่สลับองค์ประกอบและลบหนึ่ง

จากนั้นภายใน / ภายนอกสำหรับด้านใดด้านหนึ่งคือเมื่อผลคูณของจุดของด้านปกติและจุดยอดเป็นจุดเวกเตอร์เปลี่ยนเครื่องหมาย ทำซ้ำสำหรับอีกสองด้าน (หรือมากกว่า)

ประโยชน์ที่ได้รับ:

  • มีการคำนวณล่วงหน้าจำนวนมากดังนั้นยอดเยี่ยมสำหรับการทดสอบหลายจุดในรูปสามเหลี่ยมเดียวกัน

  • การปฏิเสธต้นของกรณีทั่วไปของนอกมากกว่าภายในจุด (เช่นถ้าการกระจายจุดถ่วงน้ำหนักด้านหนึ่งสามารถทดสอบด้านนั้นก่อน)


5

นี่คือการใช้Python ที่มีประสิทธิภาพ:

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

และตัวอย่างผลลัพธ์:

ป้อนคำอธิบายรูปภาพที่นี่


ฉันไม่สามารถทำงานนี้ได้ตัวอย่างเช่นสำหรับจุดในรูปสามเหลี่ยม [(0,0), (3,0), (3,4)], ไม่ได้คะแนน (1,1) หรือ (0 , 0) การทดสอบเป็นบวก ฉันลองด้วยจุดสามเหลี่ยมทั้งตามเข็มนาฬิกาและทวนเข็มนาฬิกา
ThorSummoner

3

หากคุณกำลังมองหาความเร็วนี่คือขั้นตอนที่อาจช่วยคุณได้

เรียงลำดับจุดยอดสามเหลี่ยมบนตัวชี้วัด สิ่งนี้ใช้การเปรียบเทียบสามครั้งที่แย่ที่สุด ให้ Y0, Y1, Y2 เป็นค่าเรียงสามค่า โดยการวาดสามแนวนอนให้คุณแบ่งระนาบออกเป็นสองระนาบครึ่งและสองแผ่น ให้ Y เป็นค่าพิกัดของจุดสอบถาม

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

ค่าใช้จ่ายการเปรียบเทียบอีกสองรายการ ตามที่คุณเห็นการปฏิเสธอย่างรวดเร็วนั้นเกิดขึ้นได้จากจุดนอกแผ่น "ขอบเขต"

คุณสามารถจัดหาการทดสอบเกี่ยวกับ abscissas สำหรับการปฏิเสธอย่างรวดเร็วทางด้านซ้ายและด้านขวา ( X <= X0' or X >= X2') การดำเนินการนี้จะใช้การทดสอบขอบเขตแบบรวดเร็วในเวลาเดียวกัน แต่คุณจะต้องเรียงลำดับตัวอักษรด้วย

ในที่สุดคุณจะต้องคำนวณเครื่องหมายของจุดที่กำหนดด้วยความเคารพต่อทั้งสองด้านของรูปสามเหลี่ยมที่คั่นแผ่นพื้นที่เกี่ยวข้อง (บนหรือล่าง) แบบทดสอบมีรูปแบบ:

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

การอภิปรายที่สมบูรณ์ของการi, j, kรวมกัน (มีหกของพวกเขาขึ้นอยู่กับผลของการเรียงลำดับ) อยู่นอกขอบเขตของคำตอบนี้และ "ซ้ายเป็นแบบฝึกหัดให้ผู้อ่าน"; เพื่อประสิทธิภาพพวกเขาควรได้รับการเข้ารหัสยาก

หากคุณคิดว่าวิธีนี้มีความซับซ้อนให้สังเกตว่าส่วนใหญ่เกี่ยวข้องกับการเปรียบเทียบแบบง่าย ๆ (ซึ่งสามารถคำนวณได้ล่วงหน้า) บวก 6 การลบและ 4 คูณในกรณีที่การทดสอบขอบเขตกล่องล้มเหลว ค่าใช้จ่ายหลังเป็นเรื่องยากที่จะเอาชนะได้ในกรณีที่เลวร้ายที่สุดที่คุณไม่สามารถหลีกเลี่ยงการเปรียบเทียบจุดทดสอบกับทั้งสองฝ่าย (ไม่มีวิธีการในคำตอบอื่น ๆ ที่มีราคาถูกกว่าบางคนทำให้แย่ลงเช่น 15

อัปเดต: เร็วขึ้นด้วยการแปลงแรงเฉือน

ตามที่อธิบายไว้ด้านบนคุณสามารถค้นหาจุดภายในหนึ่งในสี่แถบแนวนอนที่คั่นด้วยสามจุดยอดโดยใช้การเปรียบเทียบสองแบบ

คุณสามารถเลือกทำการทดสอบ X พิเศษหนึ่งหรือสองแบบเพื่อตรวจสอบ insideness ไปยังกล่อง bounding (เส้นประ)

แล้วพิจารณา "เฉือน" แปลงที่กำหนดโดยX'= X - m Y, Y' = Yที่mเป็นทางลาดDX/DYสำหรับขอบสูงสุด การแปลงนี้จะทำให้ด้านนี้ของแนวตั้งเป็นรูปสามเหลี่ยม และเมื่อคุณรู้ว่าคุณอยู่ตรงไหนของแนวนอนตรงกลางมันก็เพียงพอแล้วที่จะทดสอบเครื่องหมายด้วยความเคารพด้านเดียวของสามเหลี่ยม

ป้อนคำอธิบายรูปภาพที่นี่

สมมติว่าคุณคำนวณค่าความชันล่วงหน้าmเช่นเดียวกับX'จุดยอดสามเหลี่ยมที่ถูกตัดและค่าสัมประสิทธิ์ของสมการด้านข้างX = m Y + pคุณจะต้องใช้ในกรณีที่เลวร้ายที่สุด

  • เปรียบเทียบการจัดเรียงสองแบบสำหรับการจำแนกตามแนวตั้ง
  • เลือกหนึ่งหรือสองการเปรียบเทียบ abscissa สำหรับการปฏิเสธกล่องขอบเขต;
  • การคำนวณของX' = X - m Y;
  • เปรียบเทียบหนึ่งหรือสองกับ abscissas ของรูปสามเหลี่ยมที่ถูกเฉือน
  • การทดสอบสัญญาณเดียวX >< m' Y + p'กับด้านที่เกี่ยวข้องของสามเหลี่ยมที่ถูกเฉือน

3

หากคุณรู้พิกัดของสามจุดยอดและพิกัดของจุดเฉพาะคุณจะได้พื้นที่ของสามเหลี่ยมสมบูรณ์ หลังจากนั้นให้คำนวณพื้นที่ของส่วนสามเหลี่ยมทั้งสาม (จุดหนึ่งคือจุดที่กำหนดและอีกสองจุดเป็นจุดยอดสองจุดใด ๆ ของสามเหลี่ยม) ดังนั้นคุณจะได้พื้นที่ของสามส่วนสามเหลี่ยม หากผลรวมของพื้นที่เหล่านี้เท่ากับพื้นที่ทั้งหมด (ที่คุณได้ก่อนหน้านี้) ดังนั้นจุดควรอยู่ภายในรูปสามเหลี่ยม มิฉะนั้นจุดไม่อยู่ภายในรูปสามเหลี่ยม สิ่งนี้น่าจะใช้ได้ หากมีปัญหาใด ๆ แจ้งให้เราทราบ ขอบคุณ.


3

ฟังก์ชั่นอื่น ๆ ในpythonเร็วกว่าวิธีผู้พัฒนา (สำหรับฉันอย่างน้อย) และได้รับแรงบันดาลใจจากโซลูชันCédric Dufour :

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  )

คุณสามารถทดสอบด้วย:

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

มันใช้เวลามากในการพล็อต แต่ตารางที่มีการทดสอบใน 0.0195319652557 วินาทีกับ 0.0844349861145 วินาทีรหัสของผู้พัฒนา

ในที่สุดความคิดเห็นรหัส:

# 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

ฟังก์ชั่นนี้ไม่ทำงาน ให้ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])และมันจะกลับมาtrueแม้ว่าจะเป็นเท็จ
รหัสสมเด็จพระสันตะปาปา

3

เนื่องจากไม่มีคำตอบ JS
โซลูชันตามเข็มนาฬิกาและทวนเข็มนาฬิกา:

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 

}

แก้ไข: มีการพิมพ์ผิดสำหรับการคำนวณ det ( cy - ayแทนcx - ax) สิ่งนี้ได้รับการแก้ไข

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>

ป้อนคำอธิบายรูปภาพที่นี่

ฉันใช้วิธีเดียวกันกับที่อธิบายไว้ข้างต้น: จุดหนึ่งอยู่ด้านใน ABC ถ้าเป็นตามลำดับที่ด้าน "เดียวกัน" ของแต่ละบรรทัด AB, BC, CA

ตัวอย่างการรวมรูปสามเหลี่ยม


ฉันเบื่อรหัสนี้และมันไม่ทำงาน มันจะคืนค่าเป็นเท็จเสมอ
xApple

อืม ... คุณอาจจะทำผิดพลาด นี่คือซอที่ใช้ฟังก์ชันนั้น: jsfiddle.net/jniac/rctb3gfL
Joseph Merdrignac

ฉันเห็นการตอบสนองของ Python เรากำลังใช้วิธีการเดียวกันถ้าฉันใช้อีกหนึ่งบรรทัด ( let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)) นี่คือการกำหนดลำดับการพันรูปสามเหลี่ยมดังนั้นวิธีการนี้จะทำงานกับสามเหลี่ยม CW & CCW (ดู jsFiddle)
Joseph Merdrignac

1
hm ฉันทำผิดฉันเขียน: let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)แทนที่จะเป็น let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)เช่นนี้ได้รับการแก้ไขขอบคุณสำหรับการรายงาน
Joseph Merdrignac

2

ฉันแค่ต้องการใช้เวกเตอร์คณิตศาสตร์ง่าย ๆ เพื่ออธิบายวิธีแก้ปัญหาพิกัด barycentric ที่ Andreas ให้มามันจะเข้าใจง่ายขึ้น

  1. Area A หมายถึงเวกเตอร์ใด ๆ ที่กำหนดโดย s * v02 + t * v01 โดยมีเงื่อนไข s> = 0 และ t> = 0 หากจุดใด ๆ ภายในสามเหลี่ยม v0, v1, v2 จะต้องอยู่ภายใน Area A

ป้อนคำอธิบายรูปภาพที่นี่

  1. หาก จำกัด s เพิ่มเติมและ t เป็นของ [0, 1] เราได้พื้นที่ B ซึ่งมีเวกเตอร์ทั้งหมดของ s * v02 + t * v01 โดยมีเงื่อนไข s, t เป็นของ [0, 1] เป็นที่น่าสังเกตว่าส่วนล่างของพื้นที่ B คือกระจกของ Triangle v0, v1, v2 ปัญหาเกิดขึ้นถ้าเราสามารถกำหนดเงื่อนไขบางอย่างของ s และ t เพื่อยกเว้นส่วนต่ำสุดของพื้นที่ B

ป้อนคำอธิบายรูปภาพที่นี่

  1. สมมติว่าเราให้ค่า s และ t เปลี่ยนใน [0, 1] ในรูปต่อไปนี้จุด p อยู่บนขอบของ v1v2 เวกเตอร์ทั้งหมดของ s * v02 + t * v01 ซึ่งอยู่ตามแนวเส้นประด้วยผลรวมเวกเตอร์อย่างง่าย ที่ v1v2 และเส้นประจุดตัดกัน p

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

เราได้ 1 - s = tp แล้ว 1 = s + tp หากใดก็ตาม t> tp ซึ่ง 1 <s + t ที่อยู่บนเส้นประสองเส้นเวกเตอร์อยู่นอกรูปสามเหลี่ยมใด ๆ t <= tp ใด ๆ ที่ 1> = s + t ที่อยู่บนเส้นประเดียวเวกเตอร์คือ ภายในสามเหลี่ยม

จากนั้นถ้าเราให้ s ใด ๆ ใน [0, 1], t ที่สอดคล้องกันจะต้องตรงกับ 1> = s + t, สำหรับเวกเตอร์ที่อยู่ในรูปสามเหลี่ยม

ป้อนคำอธิบายรูปภาพที่นี่

ดังนั้นในที่สุดเราก็ได้ v = s * v02 + t * v01, v อยู่ภายในสามเหลี่ยมที่มีเงื่อนไข s, t, s + t เป็นของ [0, 1] จากนั้นแปลให้ชี้เรามี

p - p0 = s * (p1 - p0) + t * (p2 - p0), ด้วย s, t, s + t ใน [0, 1]

ซึ่งเหมือนกับโซลูชันของ Andreas ในการแก้ระบบสมการ p = p0 + s * (p1 - p0) + t * (p2 - p0) โดยมี s, t, s + t เป็นของ [0, 1]


คุณสามารถบอกได้ว่าคุณใช้เฟรมภายในที่กำหนดโดยจุดยอดสามจุดเพื่อให้ด้านกลายเป็น s = 0, t = 0 และ s + t = 1 การแปลงพิกัดเลียนแบบเป็นการดำเนินการที่รู้จักกันดีของพีชคณิตเชิงเส้น
Yves Daoust

2

นี่คือวิธีการแก้ปัญหาในงูหลามที่มีประสิทธิภาพเอกสารและมีสาม unittests คุณภาพระดับมืออาชีพและพร้อมที่จะนำไปวางในโครงการของคุณในรูปแบบของโมดูลตามที่เป็นอยู่

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)

มีการทดสอบกราฟิกเพิ่มเติมเพิ่มเติมสำหรับอัลกอริทึมด้านบนเพื่อยืนยันความถูกต้อง:

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

การผลิตกราฟิกต่อไปนี้:

ทดสอบฟังก์ชั่น point_in_triangle


1

มีเงื่อนไขที่น่ารำคาญที่มีจุดอยู่ตรงขอบสามัญของสามเหลี่ยมสองรูปที่อยู่ติดกัน จุดไม่สามารถอยู่ในทั้งสองหรือสามเหลี่ยมใด ๆ คุณต้องการวิธีกำหนดจุดโดยพลการ ตัวอย่างเช่นวาดเส้นแนวนอนผ่านจุด หากเส้นตัดกับด้านอื่น ๆ ของรูปสามเหลี่ยมทางด้านขวาจุดนั้นจะถือว่าราวกับว่ามันอยู่ภายในรูปสามเหลี่ยม หากสี่แยกอยู่ด้านซ้ายจุดนั้นจะอยู่ด้านนอก

หากบรรทัดที่จุดนั้นอยู่ในแนวนอนให้ใช้ด้านบน / ล่าง

หากจุดนั้นอยู่บนจุดยอดทั่วไปของสามเหลี่ยมหลายอันให้ใช้สามเหลี่ยมที่มีจุดศูนย์กลางอยู่ที่จุดเล็กที่สุด

สนุกยิ่งขึ้น: สามคะแนนสามารถเป็นเส้นตรง (ศูนย์องศา) ตัวอย่างเช่น (0,0) - (0,10) - (0,5) ในอัลกอริทึมการวิเคราะห์ตำแหน่ง "หู" (0,10) จะต้องถูกตัดออก "สามเหลี่ยม" ที่สร้างขึ้นเป็นกรณีความเสื่อมของเส้นตรง


1

นี่เป็นแนวคิดที่ง่ายที่สุดในการพิจารณาว่าจุดนั้นอยู่ด้านในหรือด้านนอกรูปสามเหลี่ยมหรือที่แขนของรูปสามเหลี่ยม

การหาจุดอยู่ภายในรูปสามเหลี่ยมโดยดีเทอร์มิแนนต์:

การหาจุดอยู่ภายในรูปสามเหลี่ยมโดยดีเทอร์มิแนนต์

รหัสการทำงานที่ง่ายที่สุด:

#-*- 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

วิธีที่ง่ายที่สุดและใช้งานได้กับสามเหลี่ยมทุกประเภทเพียงกำหนดมุมของจุด P A, B, C หากมุมใดมุมหนึ่งมีขนาดใหญ่กว่า 180.0 องศามันก็จะอยู่ข้างนอกถ้ามุม 180.0 นั้นอยู่ในเส้นรอบวงและหากเอซีสโกงคุณและน้อยกว่า 180.0 จากนั้นก็อยู่ข้างในลองมองหาการทำความเข้าใจhttp: // คณิตศาสตร์ฟิสิกส์ -psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html


0

จริงๆแล้วมันง่ายเหมือนคำตอบของ Simon P Stevenแต่ด้วยวิธีการดังกล่าวคุณไม่สามารถควบคุมได้อย่างแน่นหนาว่าคุณต้องการให้คะแนนบนขอบของรูปสามเหลี่ยมรวมหรือไม่

วิธีการของฉันแตกต่างกันเล็กน้อย แต่ธรรมดามาก พิจารณาสามเหลี่ยมต่อไปนี้

ป้อนคำอธิบายรูปภาพที่นี่

เพื่อให้มีจุดในรูปสามเหลี่ยมเราต้องตอบสนอง 3 เงื่อนไข

  1. มุม ACE (สีเขียว) ควรเล็กกว่ามุม ACB (สีแดง)
  2. มุม ECB (สีน้ำเงิน) ควรเล็กกว่ามุม ACB (สีแดง)
  3. จุด E และจุด C shoud มีเครื่องหมายเหมือนกันเมื่อค่า x และ y ของพวกเขาถูกนำไปใช้กับสมการของ | AB | ไลน์.

ในวิธีนี้คุณสามารถควบคุมได้อย่างสมบูรณ์เพื่อรวมหรือแยกจุดบนขอบแต่ละรายการ ดังนั้นคุณอาจตรวจสอบว่าจุดหนึ่งอยู่ในรูปสามเหลี่ยมรวมถึงเฉพาะ | AC | ขอบเช่น

ดังนั้นทางออกของฉันใน JavaScript จะเป็นดังนี้;

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

มันไม่สามารถมีประสิทธิภาพมากกว่านี้! แต่ละด้านของรูปสามเหลี่ยมสามารถมีตำแหน่งและการวางแนวที่เป็นอิสระดังนั้นจึงจำเป็นต้องใช้การคำนวณสามอย่าง: l1, l2 และ l3 ซึ่งเกี่ยวข้องกับการคูณ 2 ครั้ง เมื่อทราบค่า l1, l2 และ l3 แล้วผลลัพธ์ก็คือการเปรียบเทียบแบบพื้นฐานเล็กน้อยและการดำเนินการบูลีน


0

ควรมีรหัสประสิทธิภาพสูงซึ่งฉันปรับใช้ใน JavaScript (บทความด้านล่าง):

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) - สำหรับรูปสามเหลี่ยมทวนเข็มนาฬิกา
  • pointInTriangle(p, p0, p1, p2) - สำหรับรูปสามเหลี่ยมตามเข็มนาฬิกา

ดูในjsFiddle (รวมการทดสอบประสิทธิภาพ) นอกจากนี้ยังมีการตรวจสอบที่คดเคี้ยวในฟังก์ชั่นแยกต่างหาก หรือกด "เรียกใช้ข้อมูลโค้ด" ด้านล่าง

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>

แรงบันดาลใจจากสิ่งนี้: 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;
} 

พิกัดคาร์ทีเซียนที่เกือบสมบูรณ์แบบที่ถูกแปลงจาก barycentric จะถูกส่งออกภายใน * v (x) และ * w (y) เป็นสองเท่า การเอ็กซ์พอร์ตทั้งคู่ควรมี * char มาก่อนในทุกกรณีซึ่งอาจเป็นไปได้: * v และ * w Code สามารถใช้สำหรับสามเหลี่ยมอื่น ๆ ของจตุรัสได้เช่นกัน ขอลงนามเขียนสามเหลี่ยมเพียง abc จากรูปสี่เหลี่ยม abcd ตามเข็มนาฬิกา

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

จุด o อยู่ภายในรูปสามเหลี่ยม ABC สำหรับการทดสอบด้วยรูปสามเหลี่ยมที่สองเรียกฟังก์ชันนี้ทิศทาง CDA และผลลัพธ์ควรถูกต้องหลัง*v=1-*v;และ*w=1-*w;สำหรับรูปสี่เหลี่ยม


-1

ฉันต้องการจุดในการตรวจสอบรูปสามเหลี่ยมใน "สภาพแวดล้อมที่สามารถควบคุมได้" เมื่อคุณแน่ใจว่าสามเหลี่ยมจะเป็นตามเข็มนาฬิกา ดังนั้นฉันจึงใช้jsfiddle ของPerro Azulและแก้ไขตามคำแนะนำของcoprocสำหรับกรณีดังกล่าว ยังลบการซ้ำซ้อน 0.5 และ 2 ซ้ำซ้อนเพราะพวกเขาเพิ่งยกเลิกซึ่งกันและกัน

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>

นี่คือรหัสเทียบเท่า C # สำหรับ Unity:

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

หนึ่งในวิธีที่ง่ายที่สุดในการตรวจสอบว่าพื้นที่ที่เกิดขึ้นจากจุดยอดของรูปสามเหลี่ยม (x1, y1), (x2, y2), (x3, y3) เป็นค่าบวกหรือไม่

พื้นที่สามารถคำนวณโดยสูตร:

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

หรือรหัสหลามสามารถเขียนเป็น:

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])]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.