점이 2D 삼각형인지 확인하는 방법? [닫은]


258

점이 삼각형 안에 있는지 쉽게 확인할 수 있습니까? 3D가 아니라 2D입니다.


15
삼각형 테스트 포인트에 관한 완전한 기사를 썼습니다. 무게 중심, 파라 메트릭 및 내적 제품 기반 방법을 보여줍니다. 그런 다음 점이 정확히 한 가장자리에있을 때 발생하는 정확도 문제를 처리합니다 (예제 포함). 마지막으로 지점 간 거리에 따라 완전히 새로운 방법을 노출합니다. totologic.blogspot.fr/2014/01/… 즐기십시오!
논리


1
여기서 논의 된 방법은 3D 공간에서도 유효하다는 점에 주목할 가치가 있습니다. 좌표 변환 (및 삼각형 평면의 점의 적절한 투영)이 선행되어야합니다. 삼각형은 2 차원 개체입니다.
andreasdr 2016 년

권선 순서와 무관 한 솔루션. 다음은 작동하는 바이올린입니다. jsfiddle.net/ibowankenobi/oex3pzq2
ibrahim 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에서 일반적으로 사용됩니다. 무게 중심 좌표는 사람들을 혼란스럽게하는 경향이 있습니다. 또한 삼각형의 좌표와 점 좌표를 고려할 때 barycentrics의 효율성에 대해 확신이 없습니다.
Kornel Kisielewicz

7
@Kornel Barycentric 버전은 2D에서도 더 효율적입니다. 또한 솔루션에 삼각형이 시계 방향 또는 반 시계 방향으로 지정되어 있는지에 따라 삼각형의 가장자리에있는 점에 대해 다른 결과를보고한다는 문제가 있습니다.
Andreas Brinck

9
Kornel Kisielewicz가 제안한 원래의 대답은 내 목적 (이 사이트를 찾은 이유)이 훨씬 더 효율적입니다. BYTE 크기 좌표가있는 LCD 디스플레이와 정수 곱셈이 매우 빠른 명령어이며 나눗셈이 훨씬 느리고 매우 일반적인 마이크로 프로세서로 작업하고 있습니다. 나눗셈이 없기 때문에 숫자 문제도 훨씬 작습니다! 모든 계산이 정확합니다. 고마워, Rick

4
따라서 sign () 함수는 하프 플레인의 어느 쪽 (p2와 p3 사이의 선으로 형성됨)을 알려줍니다 p1?
David Doria

1
정점의 순서를 정한다고 가정하면 (반 시계 방향) 항상 모든 결정자를 계산할 필요는 없습니다. 실제로 가장 좋은 경우, 1 결정자는 점이 삼각형 안에 있지 않다는 것을 알기에 충분합니다.
Thash

176

다음 방정식 시스템을 풉니 다.

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

p0 <= s <= 1and 0 <= t <= 1및 안에 삼각형 안에 s + t <= 1있습니다.

s, t1 - s - t호출되는 무게 중심 좌표 점의를 p.


1
이것은 반평면 검사보다 빠르지 만, 무게 중심 좌표를 처음 사용하는 경우 파악하기가 약간 더 어렵습니다.
Daniel Rikowski

8
Kornel의 방법으로 사소한 출구 (구현되지 않음)를 사용하면 실제로는 당신보다 훨씬 효율적입니다. 실제로 s와 t를 계산하려고하면 내가 무슨 뜻인지 알 수 있습니다.

85
나는 이것을 테스트하고 싶었고 @andreasdr 솔루션과 coproc 주석에 의존하여 jsfiddle을 만들었습니다 : jsfiddle.net/PerroAZUL/zdaY8/1
urraka

5
최적화 : 및 if 및을 s + t <= 1의미 합니다 . s <= 1t <= 1s >= 0t >= 0
토마스 에딩

7
이 기사의 totologic.blogspot.fr/2014/01/... @Logic 포스트에 의해 제안은 내가 더 나은이 솔루션을 이해하는 데 도움이
Flayn

112

Andreas Brinck에 동의 하는데 ,이 작업에는 무게 중심 좌표가 매우 편리합니다. 매번 방정식 시스템을 풀 필요가 없습니다. 분석 솔루션 만 평가하면됩니다. 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, t1-s-t. 요점p 모두 양수인 경우에만 삼각형 안에 있습니다.

편집 : 영역에 대한 위의 표현은 삼각형 노드 번호가 시계 반대 방향이라고 가정합니다. 번호가 시계 방향 인 경우이 표현식은 음수 영역을 반환하지만 정확한 크기를 갖습니다. 테스트 자체 ( s>0 && t>0 && 1-s-t>0)는 숫자의 방향에 의존하지 않지만, 위의 표현식은1/(2*Area) 에 삼각형 노드 방향이 변경되는 경우 변경 부호도 입니다.

편집 2 : 계산 효율을 높이 려면 아래의 coproc 주석을 참조하십시오 (이는 삼각형 노드의 방향 (시계 방향 또는 시계 반대 방향)을 사전에 알고 있다면 2*Area식에서 st피하십시오). Andreas Brinck 의 답변 아래 주석에서 Perro Azul 의 jsfiddle-code를 참조하십시오 .


6
되는 방정식 시스템 : 해결
안드레아스 Brinck

1
그렇습니다. 제 포인트는 방정식 시스템을 풀기위한 계산 비용을 기반으로 한 방법에 대한 비판은 근거가 없다는 것입니다. 왜냐하면 알고리즘의 일부로 수행 할 필요가 없기 때문입니다.
andreasdr

13
를 통해 나누지 않음 2*Area, 즉 계산 s´=2*|Area|*st´=2*|Area|*t(시계 방향 또는 시계 반대 방향으로 포인트의 방향을 알 Area수없는 경우 물론 부호를 확인해야하지만 그렇지 않은 경우에도 효율을 향상시킬 수 있음) 확인해야하기 때문에 check s>0가 충분 하므로 계산해야합니다 s´>0. 그리고 확인 1-s-t>0하는 대신 확인 하면 충분합니다 s´+t´<2*|Area|.
coproc

1
I는 것을 추가 할 수있는 경우 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 둘 다의 왼쪽 또는 오른쪽에 있습니까? 사실이면 안에있을 수 없습니다. 거짓 인 경우, 조건을 만족시키는 것은 "콘"안에 있습니다. 이제 trigon (삼각형) 내부의 점이 BC (및 CA)와 AB의 같은면에 있어야한다는 것을 알았으므로 차이점이 있는지 확인합니다. 그렇다면 내부에있을 수 없으며, 그렇지 않으면 내부에 있어야합니다.

계산에 사용 된 일부 키워드는 반쪽 평면과 결정 요인 (2x2 교차 곱)입니다. 아마도 더 교육적인 방법은 아마도 각 선 AB, BC 및 CA와 같은 쪽 (왼쪽 또는 오른쪽)에있는 경우 내부 점으로 생각하는 것입니다. 그러나 위의 방법은 일부 최적화에 더 적합한 것으로 보입니다.


2
이 테스트는 처음 제공된 것보다 약 140-180 % 빠릅니다 (둘 다 btw :). : 여기의 코드를 실행 paste.ubuntu.com/p/k5w7ywH4p8은 다음과 같은 결과를 비활성화 최적화와 nodejs V8 엔진을 사용하고 있어요 : w 노드 -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
surgemcgee

@surgemcgee 왜 최적화하지 않고 실행하겠습니까? 그렇다면 현실에서 더 많이 제거되지 않습니까?
xuiqzy

@xuiqzy 글쎄, 내 프로그램에는 두 가지 다른 솔루션이 포함되어 있습니다. 아직 가장 빠른 방법을 관리하지 않았습니다. 아마도 그 의견은 제거되고 이것에 관한 완성 된 작품으로 대체되어야합니다 ..
surgemcgee

33

andreasdr 및 Perro Azul이 게시 한 barycentric 방법의 C # 버전입니다. 면적 계산하면 피할 수 있습니다 st반대 표시를해야합니다. 꽤 철저한 단위 테스트로 올바른 동작을 확인했습니다.

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

[ edit ]
@Pierre의 제안 된 수정을 수락했습니다. 의견보기


종료 if 문이있는 솔루션은 시계 방향 및 시계 반대 방향 삼각형 포인트에 적용됩니다.
Luke Dupin

@LukeDupin 귀하의 의견을 잘 모르겠습니다. 이 답변은 3 포인트의 공급 주문에 대해 게시 된대로 작동합니다.
Glenn Slayden 1

12

barycentric 메소드의 Java 버전 :

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

위의 코드는 오버플로가 없다고 가정하면 정수로 정확하게 작동합니다. 또한 시계 방향 및 반 시계 방향 삼각형에서도 작동합니다. 동일 선형 삼각형에서는 작동하지 않지만 det == 0을 테스트하여 확인할 수 있습니다.

같은 삼각형으로 다른 점을 테스트하려는 경우 barycentric 버전이 가장 빠릅니다.

Barycentric 버전은 3 개의 삼각형 포인트에서 대칭이 아니므로 부동 소수점 반올림 오류로 인해 Kornel Kisielewicz의 Edge Half-plane 버전보다 일관성이 떨어질 수 있습니다.

크레딧 : 나는 Wikipedia의 기사에서 barycentric 좌표에 위의 코드를 만들었습니다.


좋아요! 데이터 입력을 더 잘 처리하기 위해 javax.vecmath의 Point3f / Point2f 튜플을 사용하도록 향상시킬 수도 있습니다.
Alex Byrth

10

간단한 방법은 다음과 같습니다.

점을 삼각형의 세 꼭짓점 각각에 연결하는 벡터를 찾고 해당 벡터 사이의 각도를 합산합니다. 각도의 합이 2 * pi 인 경우 점은 삼각형 안에 있습니다.

대안을 설명하는 두 가지 좋은 사이트는 다음과 같습니다.

블랙 포른볼프람


3
음, 그 방법은 정확하지 않고 수치 오류가 발생하기 쉽습니다 ...
Kornel Kisielewicz

그것은 매우 반대입니다, 그것은 매우 비효율적입니다 :-) 그것은 하나의 간단한 방법이지만, 구현하기 쉽습니다. 이것이 발생할 수있는 수치 오류의 예를들 수 있습니까?
Simon P Stevens

나에게 이것은이 주제 아래의 모든 대답 중 가장 좋은 것 같습니다. 삼각형의 가장자리에있는 점이 삼각형에 포함되도록 계산되어 있고 견고한 제어권을 얻지 못했습니다.
Redu

pi의 비이성적 인 경우 정확히 2pi인지 확인하는 것은 수치 적으로 불가능합니다. 그러나 각도가 파이보다 큰지 확인해야합니다.
lonewarrior556

10

Barrycentric 좌표에 분석 솔루션을 사용하여 ( Andreas Brinck가 지적함 )

  • 괄호로 묶은 용어에 곱셈을 배포하지 않음
  • 같은 용어를 여러 번 저장하여 계산하지 않기
  • 비교 감소 ( coprocThomas 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
  • 구분 : none
  • 비교 : 4

이것은 Kornel Kisielewicz 솔루션 (25 회상, 1 회 저장, 15 차감, 6 곱셈, 5 회 비교) 과 상당히 잘 비교되며 시계 방향 / 시계 반대 방향 감지가 필요한 경우에 더 좋습니다 (6 회 호출, 1 더하기, 2 빼기 필요) , rhgb에 의해 지적 된 바와 같이 , 분석 용액 결정기를 사용하여 그 자체로 2 개의 곱셈 및 1 개의 비교 .


좋은 해결책. 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)이므로 true를 반환해야 할 경우 false를 반환합니다. 삼각형의 가장자리에있는 수학에 문제가있는 것은 점 p가 p0과 p1 사이의 경계에 있고 <를 <= 또는 이와 비슷한 것으로 변환하는 단순한 문제가 아닌 것 같습니다.
devnullicus

5

제가하는 것은 세면 법선을 미리 계산하는 것입니다.

  • 측면 벡터와면 법선 벡터의 교차 곱에 의해 3D로

  • 단순히 구성 요소를 교체하고 무시함으로써 2D로

그런 다음 한면의 내부 / 외부에서 측면 법선의 점 곱과 정점이 벡터를 가리키는 점인 경우 부호를 변경합니다. 다른 두면 이상 반복하십시오.

혜택:

  • 동일한 삼각형에서 여러 점을 테스트하기 위해 많은 것이 미리 계산되어 있습니다.

  • 내부 지점보다 더 많은 외부 사례가 조기에 거부되었습니다. (또한 점 분포가 한쪽에 가중되면 해당 쪽을 먼저 테스트 할 수 있습니다.)


5

효율적인 파이썬 구현 은 다음과 같습니다 .

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를 세 가지 정렬 값으로 설정하십시오. 3 개의 수평을 그려 평면을 2 개의 반 평면과 2 개의 슬래브로 분할합니다. 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

두 번의 비교가 더 필요합니다. 보시다시피 "경계 슬래브"외부의 포인트에 대해서는 빠른 거부가 이루어집니다.

선택적으로 가로 좌표에 테스트를 제공하여 왼쪽과 오른쪽에서 빠르게 거부 할 수 있습니다 (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 가지가 있음)는이 답변의 범위를 벗어 났으며 "독자에게 연습으로 남겨 두었습니다"; 효율성을 위해서는 하드 코딩해야합니다.

이 솔루션이 복잡하다고 생각되는 경우, 경계 상자 테스트에 실패한 경우 간단한 비교 (일부 사전 계산 가능)와 6 개의 뺄셈 및 4 개의 곱이 주로 포함됩니다. 후자의 비용은 최악의 경우와 같이 테스트 포인트를 양측과 비교하는 것을 피할 수 없으므로 극복하기가 어렵습니다 (다른 답변의 방법은 비용이 낮지 않으며 일부는 15 뺄셈과 6 곱하기, 때로는 나눗셈과 같이 악화시킵니다).

업데이트 : 전단 변형으로 더 빠름

바로 위에서 설명한 것처럼 두 개의 비교를 사용하여 세 개의 정점 좌표로 구분 된 네 개의 수평 밴드 중 하나의 내부에서 점을 빠르게 찾을 수 있습니다.

선택적으로 하나 또는 두 개의 추가 X 테스트를 수행하여 경계 상자의 내부를 확인할 수 있습니다 (점선).

그런 다음로 주어진 "전단"변환을 고려하십시오 X'= X - m Y, Y' = Y. 여기서 가장 높은 모서리 m의 기울기 DX/DY가 있습니다. 이 변환은 삼각형의이면을 수직으로 만듭니다. 그리고 당신은 당신이 중간 수평의 어느쪽에 있는지 알고 있기 때문에 삼각형의 한면에 대해 부호를 테스트하면 충분합니다.

여기에 이미지 설명을 입력하십시오

기울어 진 삼각형 정점과 측면 방정식의 계수 m뿐만 아니라 기울기를 미리 계산했다고 가정하면 최악의 경우가 필요합니다.X'X = m Y + p

  • 수직 분류에 대한 두 개의 세로 비교;
  • 선택적으로 경계 박스 거부에 대한 하나 또는 두 개의 가로 좌표 비교;
  • 계산 X' = X - m Y;
  • 전단 된 삼각형의 가로 좌표와의 하나 또는 두 개의 비교;
  • X >< m' Y + p'전단 된 삼각형의 관련면에 대한 1 개의 부호 테스트 .

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.0844349861145 초에 대해 0.0195319652557 초에서 테스트됩니다 .

마지막으로 코드 주석 :

# 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
Code Pope

3

JS 답변이 없으므로
Clockwise & Counter-Clockwise 솔루션 :

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>

여기에 이미지 설명을 입력하십시오

여기에 위에서 설명한 것과 동일한 방법을 사용하고 있습니다. 점이 각각 AB, BC, CA의 "동일한"면에있는 경우 ABC 안에 있습니다.

삼각형 포함 예


이 코드를 지 쳤는데 작동하지 않습니다. 항상 False를 반환합니다.
xApple

흠 ... 당신은 아마 실수했을 것입니다. 다음은 그 함수의 실행과 바이올린는 다음과 같습니다 jsfiddle.net/jniac/rctb3gfL
조셉 Merdrignac

파이썬 응답을 보았습니다. 우리는 동일한 방법을 사용하고 있습니다. 하나 이상의 선 ( let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay))을 사용하면 삼각형 감기 순서를 결정하는 것이므로이 방법은 CW 및 CCW 삼각형과 함께 작동합니다 (jsFiddle 참조).
Joseph Merdrignac

1
흠, 내가 쓴 실수 : 내가 let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)대신 let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)수정 : 보고 주셔서 감사합니다
조셉 Merdrignac

2

Andreas가 제공 한 barycentric 좌표 솔루션을 설명하기 위해 간단한 벡터 수학을 사용하고 싶습니다. 이해하기가 더 쉬울 것입니다.

  1. 영역 A는 조건 s> = 0 및 t> = 0 인 s * v02 + t * v01에 의해 주어진 벡터로 정의됩니다. 삼각형 v0, v1, v2 안의 점이 있으면 A 영역 안에 있어야합니다.

여기에 이미지 설명을 입력하십시오

  1. s를 더 제한하면 t는 [0, 1]에 속합니다. s * v02 + t * v01의 모든 벡터를 포함하는 영역 B를 얻습니다. 조건은 s이고 t는 [0, 1]에 속합니다. B 영역의 하단은 Triangle v0, v1, v2의 거울입니다. 영역 B의 낮은 부분을 더 배제하기 위해 s와 t의 특정 조건을 줄 수 있다면 문제가 발생합니다.

여기에 이미지 설명을 입력하십시오

  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를 얻습니다. 이중 대시 라인에있는 1 <s + t 인 t> tp 인 경우, 벡터는 삼각형 바깥에 있고, t <= tp, 단일 대시 라인에있는 1> = s + t이면 벡터는 삼각형 안에.

그런 다음 [0, 1]에 s를 주면 삼각형 내부의 벡터에 대해 해당 t가 1> = s + t를 충족해야합니다.

여기에 이미지 설명을 입력하십시오

결국 우리는 v = s * v02 + t * v01을 얻습니다. v는 s, t, s + t가 [0, 1]에 속하는 삼각형 안에 있습니다. 그런 다음 포인트로 번역하면

p-p0 = s * (p1-p0) + t * (p2-p0), [0, 1]에서 s, t, s + t 포함

방정식 시스템 p = p0 + s * (p1-p0) + t * (p2-p0)를 해결하는 Andreas의 솔루션과 동일하며, s, t, s + t는 [0, 1]에 속합니다.


면이 s = 0, t = 0 및 s + t = 1이되도록 3 개의 꼭짓점으로 정의 된 로컬 프레임을 사용한다고 말할 수 있습니다. 아핀 좌표 변환은 선형 대수학의 잘 알려진 연산입니다.
이브 다우 스트

2

다음은 효율적이고 문서화되었으며 세 가지 단위 테스트를 포함하는 파이썬의 솔루션입니다. 전문가 수준의 품질로 모듈 형태로 프로젝트에 바로 넣을 수 있습니다.

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

솔직히 Simon P Steven의 대답 만큼 간단 하지만 그 방법을 사용하면 삼각형 가장자리의 점을 포함할지 여부를 확실하게 제어 할 수 없습니다.

내 접근 방식은 조금 다르지만 매우 기본적입니다. 다음 삼각형을 고려하십시오.

여기에 이미지 설명을 입력하십시오

삼각형으로 포인트를 가지려면 3 가지 조건을 만족해야합니다

  1. ACE 각도 (녹색)는 ACB 각도 (빨간색)보다 작아야합니다.
  2. ECB 각도 (파란색)는 ACB 각도 (빨간색)보다 작아야합니다
  3. 점 E와 점 C는 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은 (성능 시험 포함), 별도의 기능에 체크가 감기입니다. 또는 아래의 "Run code snippet"을 누르십시오

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) doubles로 내보내집니다. 두 가지 내보내기 복식에는 모든 경우에 * 문자가 있어야합니다. * v 및 * w 코드는 사각형의 다른 삼각형에도 사용할 수 있습니다. 이에 의해 부호있는 abcd 쿼드로부터 삼각형 abc만이 기록되었다.

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

o 점은 ABC 삼각형 안에 있으며 두 번째 삼각형으로 테스트하려면이 기능을 CDA 방향으로 호출하십시오. 결과는 사각형 이후 *v=1-*v;*w=1-*w;사각형에 대해 정확해야합니다


-1

삼각형이 시계 방향이 될 것임을 확신 할 때 "제어 가능한 환경"에서 삼각형 확인의 포인트가 필요했습니다. 그래서 Perro Azul 의 jsfiddle을 가져 와서 그러한 경우 에 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>

Unity의 C # 코드는 다음과 같습니다.

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.