Làm thế nào để xác định nếu một điểm nằm trong tam giác 2D? [đóng cửa]


258

Có một cách dễ dàng để xác định nếu một điểm nằm trong một tam giác? Đó là 2D, không phải 3D.


15
Tôi đã viết một bài viết đầy đủ về điểm trong bài kiểm tra tam giác. Nó cho thấy các phương pháp dựa trên sản phẩm barycentric, parametric và dot. Sau đó, nó giải quyết vấn đề chính xác xảy ra khi một điểm nằm chính xác trên một cạnh (có ví dụ). Cuối cùng, nó cho thấy một phương pháp hoàn toàn mới dựa trên khoảng cách từ điểm đến cạnh. totologic.blogspot.fr/2014/01/ Hãy tận hưởng!
Logic


1
Điều đáng chú ý là bất kỳ phương pháp nào được thảo luận ở đây cũng hợp lệ trong không gian 3D. Chúng chỉ cần được đi trước bởi một phép biến đổi tọa độ (và một hình chiếu thích hợp của điểm trên mặt phẳng của tam giác). Một tam giác là một đối tượng 2 chiều.
andreasdr

Đối với một giải pháp độc lập với trật tự quanh co. Đây là một fiddle làm việc: jsfiddle.net/ibowankenobi/oex3pzq2
ibrahim tanyalcin

2
Tôi đang bỏ phiếu để đóng câu hỏi này vì nó liên quan đến toán học hơn là lập trình và dựa trên quan điểm (điều gì là "dễ dàng" với bạn?).
TylerH

Câu trả lời:


264

Nói chung, thuật toán đơn giản nhất (và khá tối ưu) là kiểm tra phía nào của nửa mặt phẳng được tạo bởi các cạnh của điểm.

Đây là một số thông tin chất lượng cao trong chủ đề này trên GameDev , bao gồm các vấn đề về hiệu suất.

Và đây là một số mã để giúp bạn bắt đầu:

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
Nó thường được sử dụng trong 2D. Tọa độ barycentric có xu hướng gây nhầm lẫn cho mọi người. Ngoài ra, với các phép cộng của tam giác và điểm nối, tôi không chắc về hiệu quả của việc sử dụng phép đo barycentrics.
Kornel Kisielewicz

7
@Kornel Phiên bản barycentric cũng hiệu quả hơn trong 2D. Giải pháp của bạn cũng có vấn đề là nó sẽ báo cáo một kết quả khác cho các điểm chính xác trên các cạnh của tam giác tùy thuộc vào thời tiết tam giác được chỉ định theo chiều kim đồng hồ hoặc ngược chiều kim đồng hồ.
Andreas Brinck

9
Đối với mục đích của tôi (lý do tôi tìm thấy trang web này), câu trả lời ban đầu được đề xuất bởi Kornel Kisielewicz hiệu quả hơn nhiều. Tôi đang làm việc với màn hình LCD với tọa độ kích thước BYTE và bộ vi xử lý rất điển hình trong đó nhân số nguyên là một hướng dẫn rất nhanh và phân chia thì nhiều, nhiều, chậm hơn. Các vấn đề về số cũng nhỏ hơn nhiều, do không có sự phân chia! tất cả các tính toán là chính xác. Cảm ơn, Rick

4
Vậy hàm dấu () cho bạn biết phía nào của nửa mặt phẳng (được hình thành bởi đường thẳng giữa p2 và p3) p1 là?
David Doria

1
Lưu ý rằng nếu bạn giả sử một số thứ tự của các đỉnh (nói ngược chiều kim đồng hồ), bạn không cần phải tính toán tất cả các yếu tố quyết định đó mọi lúc. Trong thực tế trong trường hợp tốt nhất, 1 định thức là đủ để thấy rằng điểm không nằm trong tam giác.
Thash

176

Giải hệ phương trình sau:

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

Vấn đề plà bên trong tam giác nếu 0 <= s <= 10 <= t <= 1s + t <= 1.

s, t1 - s - tđược gọi là tọa độ barycentric của điểm p.


1
Điều này nhanh hơn kiểm tra nửa mặt phẳng, nhưng có lẽ khó nắm bắt hơn một chút nếu bạn chưa quen với tọa độ barycentric.
Daniel Rikowski

8
Với các lối thoát tầm thường (không được triển khai) trong phương pháp của Kornel, anh ta thực sự có thể hiệu quả hơn nhiều so với phương pháp của bạn. Nếu bạn thực sự cố gắng tính toán và bạn sẽ hiểu ý tôi là gì.

85
Tôi muốn thử nghiệm điều này vì vậy tôi đã tạo ra một jsfiddle, dựa vào giải pháp @andreasdr và nhận xét về coproc
urraka

5
Tối ưu hóa: s + t <= 1ngụ ý s <= 1t <= 1nếu s >= 0t >= 0.
Thomas Eding

7
Bài viết totologic.blogspot.fr/2014/01/ được đề xuất bởi bài đăng @Logic đã giúp tôi hiểu rõ hơn về giải pháp này
Flayn

112

Tôi đồng ý với Andreas Brinck , tọa độ barycentric rất thuận tiện cho nhiệm vụ này. Lưu ý rằng không cần phải giải hệ phương trình mỗi lần: chỉ cần đánh giá giải pháp phân tích. Sử dụng ký hiệu của Andreas , giải pháp là:

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

nơi Arealà khu vực (đã ký) của tam giác:

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

Chỉ cần đánh giá s, t1-s-t. Điểm pnằm trong tam giác khi và chỉ khi tất cả chúng đều dương.

EDIT: Lưu ý rằng biểu thức trên cho khu vực giả định rằng việc đánh số nút tam giác là ngược chiều kim đồng hồ. Nếu đánh số theo chiều kim đồng hồ, biểu thức này sẽ trả về một vùng âm (nhưng với cường độ chính xác). s>0 && t>0 && 1-s-t>0Tuy nhiên, bản thân phép thử ( ) không phụ thuộc vào hướng đánh số, vì các biểu thức ở trên được nhân với 1/(2*Area)dấu hiệu cũng thay đổi nếu hướng của nút tam giác thay đổi.

EDIT 2: Để có hiệu quả tính toán tốt hơn, hãy xem bình luận của coproc bên dưới (điều này cho thấy rằng nếu định hướng của các nút tam giác (theo chiều kim đồng hồ hoặc ngược chiều kim đồng hồ), thì việc phân chia theo 2*Areacác biểu thức cho stcó thể được tránh). Xem thêm mã jsfiddle của Perro Azul trong các bình luận dưới câu trả lời của Andreas Brinck .


6
Đó giải hệ phương trình :)
Andreas Brinck

1
Vâng, quan điểm của tôi là bất kỳ sự chỉ trích nào về phương pháp của bạn dựa trên chi phí tính toán để giải hệ phương trình là không có cơ sở, vì điều đó không phải được thực hiện như một phần của thuật toán.
andreasdr

13
Hiệu quả có thể được cải thiện bằng cách không phân chia 2*Area, tức là bằng cách tính toán s´=2*|Area|*st´=2*|Area|*t(nếu định hướng của các điểm - theo chiều kim đồng hồ hoặc ngược chiều kim đồng hồ - không được biết, dấu hiệu của tất nhiên Areaphải được kiểm tra, nhưng nếu không thì có thể thậm chí không cần phải được tính toán), vì để kiểm tra s>0nó đủ để kiểm tra s´>0. Và thay vì kiểm tra 1-s-t>0nó đủ để kiểm tra s´+t´<2*|Area|.
coproc

1
Tôi có thể thêm rằng nếu p0->p1->p2truy cập chiều kim đồng hồ trong Descartes (mà thường là chiều kim đồng hồ trong tọa độ màn hình ), các Areatính toán bằng phương pháp này sẽ được tích cực.
rhgb

1
@ user2600366 Khi bạn đi dọc theo đường biên của tam giác theo hướng p0 -> p1 -> p2 -> p0, v.v., bạn sẽ có phần bên trong của tam giác luôn ở bên phải hoặc luôn ở bên trái của bạn. Trong trường hợp trước, việc đánh số theo chiều kim đồng hồ, trong trường hợp sau, nó ngược chiều kim đồng hồ.
andreasdr

47

Tôi đã viết mã này trước một nỗ lực cuối cùng với Google và tìm thấy trang này, vì vậy tôi nghĩ tôi sẽ chia sẻ nó. Nó về cơ bản là một phiên bản tối ưu của câu trả lời Kisielewicz. Tôi cũng đã xem xét phương pháp Barycentric nhưng đánh giá từ bài viết trên Wikipedia tôi có một thời gian khó khăn để xem nó hiệu quả hơn như thế nào (tôi đoán có một số tương đương sâu hơn). Dù sao, thuật toán này có lợi thế là không sử dụng phép chia; một vấn đề tiềm năng là hành vi phát hiện cạnh tùy theo định hướng.

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

Nói cách khác, ý tưởng là thế này: Điểm s ở bên trái hay bên phải của cả hai đường thẳng AB và AC? Nếu đúng, nó không thể ở bên trong. Nếu sai, ít nhất là bên trong "hình nón" thỏa mãn điều kiện. Bây giờ vì chúng ta biết rằng một điểm bên trong một tam giác (tam giác) phải cùng phía với AB với BC (và cả CA), chúng ta kiểm tra xem chúng có khác nhau không. Nếu họ làm, họ không thể ở bên trong, nếu không thì phải ở trong.

Một số từ khóa trong tính toán là nửa mặt phẳng dòng và định thức (sản phẩm chéo 2x2). Có lẽ một cách sư phạm hơn có lẽ là nghĩ về nó như là một điểm bên trong nếu nó ở cùng một phía (trái hoặc phải) cho mỗi dòng AB, BC và CA. Cách trên có vẻ phù hợp hơn cho một số tối ưu hóa tuy nhiên.


2
Bài kiểm tra này nhanh hơn khoảng 140-180% so với bài kiểm tra đầu tiên được cung cấp (nhờ cả hai bạn btw :). Tôi đã chạy mã ở đây: paste.ubfox.com/p/k5w7ywH4p8 bằng cách sử dụng công cụ nodejs v8 với tối ưu hóa bị vô hiệu hóa và nhận được các kết quả sau :: w! Node -p --minimal test1: 114.852ms test2: 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
phẫu thuật

@surgemcgee tại sao bạn lại chạy nó mà không tối ưu hóa? Chẳng phải điều đó đã bị loại bỏ khỏi thực tế rồi sao?
xuiqzy

@xuiqzy Vâng, chương trình của tôi chứa hai giải pháp khác nhau. Tôi vẫn chưa quản lý phương pháp nhanh nhất để làm điều đó. Có lẽ bình luận đó nên được gỡ bỏ và thay thế bằng những tác phẩm đã hoàn thành của tôi liên quan đến vấn đề này ..
phẫu thuật

33

Phiên bản C # của phương pháp barycentric được đăng bởi andreasdr và Perro Azul. Lưu ý rằng tính toán diện tích có thể tránh được nếu stcó dấu hiệu ngược lại. Tôi xác minh hành vi đúng với một bài kiểm tra đơn vị khá kỹ lưỡng.

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

[ sửa ]
chấp nhận sửa đổi đề xuất bởi @Pierre; Xem ý kiến


Giải pháp với câu lệnh if kết thúc hoạt động cho các điểm tam giác theo chiều kim đồng hồ và ngược chiều kim đồng hồ.
Luke Dupin

@LukeDupin Không chắc tôi hiểu bình luận của bạn. Câu trả lời này hoạt động như được đăng cho bất kỳ thứ tự được cung cấp trong 3 điểm.
Glenn Slayden

12

Phiên bản Java của phương pháp 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;
}

Đoạn mã trên sẽ hoạt động chính xác với các số nguyên, giả sử không có tràn. Nó cũng sẽ làm việc với các hình tam giác theo chiều kim đồng hồ và ngược chiều kim đồng hồ. Nó sẽ không hoạt động với các tam giác thẳng hàng (nhưng bạn có thể kiểm tra điều đó bằng cách kiểm tra det == 0).

Phiên bản barycentric là nhanh nhất nếu bạn sẽ kiểm tra các điểm khác nhau với cùng một tam giác.

Phiên bản barycentric không đối xứng trong 3 điểm tam giác, do đó, nó có thể ít nhất quán hơn phiên bản nửa mặt phẳng cạnh của Kornel Kisielewicz, do lỗi làm tròn điểm nổi.

Tín dụng: Tôi đã thực hiện mã trên từ bài viết của Wikipedia về tọa độ barycentric.


Đẹp ! Nó thậm chí có thể được cải thiện để sử dụng các bộ dữ liệu Point3f / Point2f của javax.vecmath, để xử lý dữ liệu đầu vào tốt hơn.
Alex Byrth

10

Một cách đơn giản là:

tìm các vectơ nối điểm với mỗi ba đỉnh của tam giác và tính tổng các góc giữa các vectơ đó. Nếu tổng các góc là 2 * pi thì điểm nằm trong tam giác.

Hai trang web tốt giải thích các lựa chọn thay thế là:

blackpawnwolfram


3
Ừm, phương pháp đó không chính xác hiệu quả và rất dễ bị lỗi số ...
Kornel Kisielewicz

Hoàn toàn ngược lại, nó rất kém hiệu quả :-) Tuy nhiên, đó chỉ là một cách đơn giản, dễ thực hiện. Bạn có thể đưa ra một ví dụ về một lỗi số này sẽ gây ra?
Simon P Stevens

Mặc dù với tôi đây đơn giản là câu trả lời hay nhất trong tất cả các câu trả lời trong chủ đề này, tôi đoán các điểm trên các cạnh của tam giác được tính để đưa vào tam giác và bạn chưa kiểm soát được điều đó.
Redu

kiểm tra xem chính xác 2pi là không thể đưa ra số không hợp lý của pi. Tuy nhiên, bạn chỉ cần kiểm tra nếu các góc cộng với một cái gì đó lớn hơn pi.
lonewar Warrior556

10

Bằng cách sử dụng giải pháp phân tích cho tọa độ barycentric (được chỉ ra bởi Andreas Brinck ) và:

  • không phân phối phép nhân trên các điều khoản được ngoặc
  • tránh tính toán nhiều lần cùng một thuật ngữ bằng cách lưu trữ chúng
  • giảm so sánh (như được chỉ ra bởi coprocThomas Eding )

Người ta có thể giảm thiểu số lượng các hoạt động "tốn kém":

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

Mã có thể được dán trong Perro Azul jsfiddle hoặc thử nó bằng cách nhấp vào "Chạy đoạn mã" bên dưới

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>

Dẫn tới:

  • biến "thu hồi": 30
  • lưu trữ biến: 7
  • bổ sung: 4
  • phép trừ: 8
  • phép nhân: 6
  • các bộ phận: không có
  • so sánh: 4

Điều này so sánh khá tốt với giải pháp Kornel Kisielewicz (25 hồi tưởng, 1 lưu trữ, 15 phép trừ, 6 phép nhân, 5 phép so sánh) và thậm chí có thể tốt hơn nếu cần phát hiện theo chiều kim đồng hồ / ngược chiều kim đồng hồ (mất 6 lần thu hồi, 1 phép cộng, 2 phép trừ , 2 phép nhân và 1 phép so sánh trong chính nó, sử dụng định thức giải pháp phân tích, như được chỉ ra bởi rhgb ).


Giải pháp tốt đẹp. Tôi nghĩ là khá tương đương với cách tiếp cận cuối cùng của tôi ở đây trên MSE: math.stackexchange.com/questions/51326/ Kẻ
Jack D'Aurizio

Tôi chỉ kiểm tra các mã như nguyên trạng và nó không làm việc cho tôi (ví dụ p -4,69317198, -6,99191951 p0 -7,05846786 0,596718192 p1 -6,8703599 -2,36565161 p2 -4,69317198, -6,99191951)
Giovanni Funchal

@GiovanniFunchal Strange, ví dụ của bạn hoạt động với tôi, cả trong jsfiddle (thay thế các định nghĩa "điểm" và "tam giác" ban đầu) và triển khai Python cục bộ của tôi. Các vấn đề chính xác về số (thử tước một số thập phân)?
Cédric Dufour

1
Bạn dường như nhanh nhất trong bài kiểm tra của tôi: jsfiddle.net/eyal/gxw3632c/27 . Sự khác biệt giữa tất cả các phương pháp là khá nhỏ, mặc dù.
Mắt

Hãy thử tam giác (-1, -1), (1, -1), (0,1) và điểm (0, -1). Trả về false khi trả về true vì s (2) + t (2)> d (2). Có vẻ như có gì đó sai với toán học trên các cạnh của tam giác, dường như, điểm p nằm ngay trên đường viền giữa p0 và p1 và việc chuyển đổi <thành <= hoặc một cái gì đó tương tự không phải là vấn đề đơn giản.
devnullicus

5

Những gì tôi làm là tính toán trước ba quy tắc,

  • trong 3D bằng sản phẩm chéo của vectơ bên và vectơ bình thường.

  • trong 2D bằng cách hoán đổi các thành phần và phủ định một,

sau đó bên trong / bên ngoài cho bất kỳ một bên là khi một sản phẩm chấm của bên bình thường và vectơ từ đỉnh đến điểm, thay đổi dấu hiệu. Lặp lại cho hai bên khác (hoặc nhiều hơn).

Những lợi ích:

  • rất nhiều được tính toán trước rất tuyệt vời cho thử nghiệm nhiều điểm trên cùng một tam giác.

  • từ chối sớm của trường hợp phổ biến bên ngoài nhiều hơn bên trong điểm. (cũng nếu phân phối điểm có trọng số sang một bên, có thể kiểm tra bên đó trước.)


5

Đây là một triển khai Python hiệu quả :

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

và một ví dụ đầu ra:

nhập mô tả hình ảnh ở đây


Tôi chưa thể thực hiện công việc này, ví dụ cho điểm trong tam giác [(0,0), (3,0), (3,4)], không phải điểm (1,1) hoặc (0 , 0) xét nghiệm dương tính. Tôi đã thử với cả hai điểm tam giác theo chiều kim đồng hồ và ngược chiều kim đồng hồ.
ThorSummoner

3

Nếu bạn đang tìm kiếm tốc độ, đây là một thủ tục có thể giúp bạn.

Sắp xếp các đỉnh tam giác trên tọa độ của chúng. Điều này mất ở ba so sánh tồi tệ nhất. Đặt Y0, Y1, Y2 là ba giá trị được sắp xếp. Bằng cách vẽ ba chiều ngang qua chúng, bạn phân vùng mặt phẳng thành hai nửa mặt phẳng và hai tấm. Gọi Y là tọa độ của điểm truy vấn.

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

Chi phí so sánh nhiều hơn. Như bạn thấy, từ chối nhanh chóng đạt được cho các điểm bên ngoài "phiến giới hạn".

Tùy chọn, bạn có thể cung cấp một bài kiểm tra trên các abscissas để từ chối nhanh ở bên trái và bên phải (X <= X0' or X >= X2' ). Điều này sẽ thực hiện kiểm tra hộp giới hạn nhanh cùng một lúc, nhưng bạn cũng cần phải sắp xếp trên các abscissas.

Cuối cùng, bạn sẽ cần tính toán dấu của điểm đã cho đối với hai cạnh của tam giác phân định phiến liên quan (trên hoặc dưới). Bài kiểm tra có dạng:

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

Các cuộc thảo luận đầy đủ về i, j, k kết hợp (có sáu trong số chúng, dựa trên kết quả của loại) nằm ngoài phạm vi của câu trả lời này và "để lại như một bài tập cho người đọc"; để có hiệu quả, chúng nên được mã hóa cứng.

Nếu bạn nghĩ rằng giải pháp này phức tạp, hãy quan sát rằng nó chủ yếu liên quan đến các phép so sánh đơn giản (một số trong đó có thể được tính toán trước), cộng với 6 phép trừ và 4 phép nhân trong trường hợp kiểm tra hộp giới hạn không thành công. Chi phí thứ hai khó vượt qua vì trong trường hợp xấu nhất bạn không thể tránh so sánh điểm kiểm tra với hai bên (không có phương pháp nào trong các câu trả lời khác có chi phí thấp hơn, một số làm cho nó tồi tệ hơn, như 15 phép trừ và 6 phép nhân, đôi khi là chia).

CẬP NHẬT: Nhanh hơn với một biến đổi cắt

Như đã giải thích ở trên, bạn có thể nhanh chóng xác định vị trí điểm bên trong một trong bốn dải nằm ngang được phân định bởi ba tọa độ đỉnh, sử dụng hai phép so sánh.

Bạn có thể tùy ý thực hiện một hoặc hai thử nghiệm X bổ sung để kiểm tra độ không chắc chắn đối với hộp giới hạn (đường chấm chấm).

Sau đó xem xét biến đổi "cắt" được đưa ra bởi X'= X - m Y, Y' = Y, đâu mlà độ dốc DX/DYcho cạnh cao nhất. Biến đổi này sẽ làm cho cạnh này của tam giác thẳng đứng. Và vì bạn biết bạn đang ở phía bên nào của đường ngang ở giữa, nên đủ để kiểm tra dấu hiệu liên quan đến một phía của tam giác.

nhập mô tả hình ảnh ở đây

Giả sử bạn đã tính trước độ dốc m, cũng như X'cho các đỉnh tam giác bị cắt và các hệ số của phương trình của các cạnh như X = m Y + p, bạn sẽ cần trong trường hợp xấu nhất

  • hai so sánh thứ tự để phân loại dọc;
  • tùy chọn một hoặc hai so sánh abscissa để từ chối hộp giới hạn;
  • tính toán của X' = X - m Y;
  • một hoặc hai so sánh với các abscissas của tam giác bị cắt;
  • kiểm tra một dấu hiệu X >< m' Y + p'đối với phía có liên quan của tam giác bị cắt.

3

Nếu bạn biết tọa độ của ba đỉnh và tọa độ của điểm cụ thể, thì bạn có thể có được diện tích của tam giác hoàn chỉnh. Sau đó, tính diện tích của ba đoạn tam giác (một điểm là điểm đã cho và hai điểm còn lại là hai đỉnh của tam giác). Như vậy, bạn sẽ có được diện tích của ba đoạn tam giác. Nếu tổng của các khu vực này bằng tổng diện tích (mà bạn đã có trước đó), thì điểm phải nằm trong tam giác. Mặt khác, điểm không nằm trong tam giác. Điều này nên làm việc. Nếu có bất kỳ vấn đề, cho tôi biết. Cảm ơn bạn.


3

Các chức năng khác trong python , nhanh hơn phương thức của Nhà phát triển (ít nhất là đối với tôi) và được truyền cảm hứng từ giải pháp 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  )

Bạn có thể kiểm tra nó với:

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

Phải mất rất nhiều âm mưu, nhưng lưới đó được kiểm tra trong 0,0195319652557 giây so với 0,0844349861145 giây mã của Nhà phát triển .

Cuối cùng là mã nhận xét:

# 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

Chức năng này không hoạt động. Cho ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])và nó sẽ trả lại truemặc dù đó là sai
Mã Giáo hoàng

3

Vì không có câu trả lời JS,
giải pháp theo chiều kim đồng hồ và ngược chiều kim đồng hồ:

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: đã có một lỗi đánh máy cho tính toán det ( cy - aythay vì cx - ax), điều này đã được sửa.

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>

nhập mô tả hình ảnh ở đây

Tôi đang sử dụng ở đây cùng một phương pháp như được mô tả ở trên: một điểm nằm trong ABC nếu nó nằm tương ứng ở phía "cùng" của mỗi dòng AB, BC, CA.

ví dụ bao gồm tam giác


Tôi mệt mỏi mã này và nó không hoạt động. Nó luôn trả về Sai.
xApple

hmmm ... bạn có thể đã làm sai Đây là một câu đố với chức năng đó đang chạy: jsfiddle.net/jniac/rctb3gfL
Joseph Merdrignac

Tôi đã thấy phản hồi Python của bạn, chúng tôi đang sử dụng cùng một phương thức, nếu tôi sử dụng thêm một dòng ( let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)), đây là để xác định thứ tự cuộn dây tam giác, vì vậy phương thức này sẽ hoạt động với các tam giác CW & CCW (xem jsFiddle).
Joseph Merdrignac

1
hm tôi đã làm sai, tôi đã viết: let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)thay vì let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)vậy điều này đã được sửa, cảm ơn vì đã báo cáo
Joseph Merdrignac

2

Tôi chỉ muốn sử dụng một số phép toán vectơ đơn giản để giải thích giải pháp tọa độ barycentric mà Andreas đã đưa ra, nó sẽ dễ hiểu hơn.

  1. Vùng A được định nghĩa là bất kỳ vectơ nào được cho bởi s * v02 + t * v01, với điều kiện s> = 0 và t> = 0. Nếu bất kỳ điểm nào bên trong tam giác v0, v1, v2, thì nó phải nằm trong Vùng A.

nhập mô tả hình ảnh ở đây

  1. Nếu tiếp tục hạn chế s và t thuộc về [0, 1]. Chúng ta có Vùng B chứa tất cả các vectơ của s * v02 + t * v01, với điều kiện s, t thuộc về [0, 1]. Điều đáng lưu ý là phần thấp của Khu vực B là gương của Tam giác v0, v1, v2. Vấn đề xảy ra nếu chúng ta có thể đưa ra một số điều kiện nhất định của s và t, để loại trừ thêm phần thấp của Khu vực B.

nhập mô tả hình ảnh ở đây

  1. Giả sử chúng ta đưa ra một giá trị s và t đang thay đổi trong [0, 1]. Trong ảnh sau, điểm p nằm trên cạnh của v1v2. Tất cả các vectơ của s * v02 + t * v01 nằm dọc theo đường gạch ngang bằng tổng vectơ đơn giản. Tại v1v2 và đường chéo điểm p, chúng ta có:

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

ta được 1 - s = tp, sau đó 1 = s + tp. Nếu bất kỳ t> tp nào, trong đó 1 <s + t nằm trên đường gạch ngang kép, thì vectơ nằm ngoài tam giác, bất kỳ t <= tp nào, trong đó 1> = s + t nằm trên đường gạch ngang đơn, vectơ là bên trong tam giác.

Sau đó, nếu chúng ta cho bất kỳ s nào trong [0, 1], t tương ứng phải đáp ứng 1> = s + t, cho vectơ bên trong tam giác.

nhập mô tả hình ảnh ở đây

Vậy cuối cùng ta được v = s * v02 + t * v01, v nằm trong tam giác với điều kiện s, t, s + t thuộc về [0, 1]. Sau đó dịch sang điểm, chúng tôi có

p - p0 = s * (p1 - p0) + t * (p2 - p0), với s, t, s + t trong [0, 1]

tương tự như giải pháp của Andreas để giải hệ phương trình p = p0 + s * (p1 - p0) + t * (p2 - p0), với s, t, s + t thuộc về [0, 1].


Bạn chỉ có thể nói rằng bạn sử dụng khung cục bộ được xác định bởi ba đỉnh để các cạnh trở thành s = 0, t = 0 và s + t = 1. Phép biến đổi tọa độ affine là một phép toán nổi tiếng của đại số tuyến tính.
Yves Daoust

2

Đây là một giải pháp trong python có hiệu quả, được ghi lại và chứa ba điều không mong muốn. Đó là chất lượng cấp chuyên nghiệp và sẵn sàng để được đưa vào dự án của bạn dưới dạng một mô-đun.

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)

Có một thử nghiệm đồ họa tùy chọn bổ sung cho thuật toán ở trên để xác nhận tính hợp lệ của nó:

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

Sản xuất đồ họa sau:

Kiểm tra hàm point_in_trigin


1

Có các điều kiện cạnh pesky trong đó một điểm chính xác trên cạnh chung của hai hình tam giác liền kề. Điểm không thể ở cả hai hoặc cả hai hình tam giác. Bạn cần một cách tùy ý nhưng nhất quán để gán điểm. Ví dụ, vẽ một đường ngang qua điểm. Nếu đường thẳng giao với phía bên kia của tam giác bên phải, điểm được xử lý như thể nó nằm trong tam giác. Nếu giao điểm ở bên trái, điểm nằm bên ngoài.

Nếu đường thẳng nằm trên điểm nằm ngang, hãy sử dụng phía trên / bên dưới.

Nếu điểm nằm trên đỉnh chung của nhiều tam giác, hãy sử dụng tam giác có tâm là điểm tạo thành góc nhỏ nhất.

Thú vị hơn: ba điểm có thể nằm trên một đường thẳng (không độ), ví dụ (0,0) - (0,10) - (0,5). Trong thuật toán tam giác, "tai" (0,10) phải được loại bỏ, "tam giác" được tạo ra là trường hợp suy biến của một đường thẳng.


1

Đây là khái niệm đơn giản nhất để xác định xem một điểm nằm trong hay ngoài tam giác hay trên một nhánh của tam giác.

Xác định một điểm nằm trong tam giác bằng các định thức:

Xác định một điểm nằm trong tam giác bằng các định thức

Mã làm việc đơn giản nhất:

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

Cách dễ nhất và nó hoạt động với tất cả các loại hình tam giác chỉ đơn giản là xác định các góc của các điểm P điểm A, B, C. Nếu bất kỳ góc nào lớn hơn 180.0 độ thì nó ở bên ngoài, nếu 180.0 thì nó nằm trên chu vi và nếu acos lừa dối bạn và dưới 180.0 thì nó ở bên trong. Hãy tìm hiểu về http: // math-vật lý -psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html


0

Thành thật mà nói, nó đơn giản như câu trả lời của Simon P Steven tuy nhiên với cách tiếp cận đó, bạn không có quyền kiểm soát chặt chẽ về việc bạn có muốn bao gồm các điểm trên các cạnh của tam giác hay không.

Cách tiếp cận của tôi là một chút khác nhau nhưng rất cơ bản. Xét tam giác sau;

nhập mô tả hình ảnh ở đây

Để có điểm trong tam giác ta phải thỏa mãn 3 điều kiện

  1. Góc ACE (màu xanh lá cây) phải nhỏ hơn góc ACB (màu đỏ)
  2. Góc ECB (màu xanh) phải nhỏ hơn góc ACB (màu đỏ)
  3. Điểm E và Điểm C shoud có cùng dấu khi giá trị x và y của chúng được áp dụng cho phương trình của | AB | hàng.

Trong phương pháp này, bạn có toàn quyền kiểm soát để bao gồm hoặc loại trừ điểm trên các cạnh riêng lẻ. Vì vậy, bạn có thể kiểm tra nếu một điểm nằm trong tam giác chỉ bao gồm | AC | cạnh chẳng hạn.

Vì vậy, giải pháp của tôi trong JavaScript sẽ như sau;

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

Nó không thể hiệu quả hơn thế này! Mỗi cạnh của một tam giác có thể có vị trí và hướng độc lập, do đó ba phép tính: l1, l2 và l3 chắc chắn là cần thiết liên quan đến 2 phép nhân mỗi lần. Khi đã biết l1, l2 và l3, kết quả chỉ là một vài so sánh cơ bản và các phép toán boolean.


0

Mã hiệu suất cao được cho là tôi đã điều chỉnh bằng JavaScript (bài viết dưới đây):

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) - cho hình tam giác ngược chiều kim đồng hồ
  • pointInTriangle(p, p0, p1, p2) - cho hình tam giác theo chiều kim đồng hồ

Hãy xem trong jsFiddle (bao gồm kiểm tra hiệu năng), cũng có kiểm tra quanh co trong một chức năng riêng biệt. Hoặc nhấn "Chạy đoạn mã" bên dưới

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>

Lấy cảm hứng từ điều này: 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;
} 

Các tọa độ Cartesian gần như hoàn hảo được chuyển đổi từ barycentric được xuất trong phạm vi * v (x) và * w (y). Cả hai nhân đôi xuất khẩu nên có một * char trước trong mọi trường hợp, có khả năng: * v và * w Mã cũng có thể được sử dụng cho tam giác khác của một tứ giác. Dưới đây đã ký chỉ viết tam giác abc từ tứ giác abcd theo chiều kim đồng hồ.

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

điểm o nằm trong tam giác ABC để kiểm tra với tam giác thứ hai gọi hàm CDA này và kết quả phải chính xác sau *v=1-*v;*w=1-*w;cho tứ giác


-1

Tôi cần điểm trong kiểm tra tam giác trong "môi trường có thể kiểm soát" khi bạn hoàn toàn chắc chắn rằng các tam giác sẽ theo chiều kim đồng hồ. Vì vậy, tôi đã lấy jsfiddle của Perro Azul và sửa đổi nó theo đề xuất của coproc cho những trường hợp như vậy; cũng loại bỏ 0,5 và 2 phép nhân dư thừa vì chúng chỉ hủy nhau.

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>

Đây là mã C # tương đương cho 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

Một trong những cách dễ nhất để kiểm tra xem diện tích hình thành bởi các đỉnh của tam giác (x1, y1), (x2, y2), (x3, y3) có dương hay không.

Diện tích có thể bằng cách tính theo công thức:

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

hoặc mã python có thể được viết là:

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])]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.