如何确定一个点是否在2D三角形中?[关闭]


258

有没有一种简单的方法来确定点是否在三角形内?它是2D,而不是3D。


15
我写了一篇有关三角测试中点的完整文章。它显示了基于重心,参数和点积的方法。然后,它处理了一个点恰好位于一条边上时发生的精度问题(带有示例)。最后,它公开了一种基于点到边距离的全新方法。totologic.blogspot.fr/2014/01/…享受!
逻辑2014年


1
值得注意的是,这里讨论的任何方法在3D空间中也是有效的。它们只需要进行坐标转换(以及该点在三角形平面上的适当投影)即可。三角形是二维对象。
andreasdr

对于不依赖绕组顺序的解决方案。这是一个有效的小提琴:jsfiddle.net/ibowankenobi/oex3pzq2
易卜拉欣tanyalcin

2
我投票结束这个问题,因为它是关于数学而不是编程,并且是基于观点的(对您来说“容易”吗?)。
TylerH

Answers:


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中使用。重心坐标往往会使人困惑。另外,考虑到三角形的坐标和点坐标,我不确定使用重心的效率。
Kornel Kisielewicz

7
@Kornel重心版本在2D中也更有效。您的解决方案还存在一个问题,即根据以顺时针方向还是逆时针方向指定三角形,对于三角形边缘上的精确点将报告不同的结果。
Andreas Brinck 2010年

9
出于我的目的(我找到此网站的原因),Kornel Kisielewicz提出的原始答案要有效得多。我正在使用带有BYTE大小坐标的LCD显示器和一个非常典型的微处理器,其中整数乘法是非常快的指令,而除法则非常慢。由于没有除法,数字问题也小得多!所有计算都是精确的。谢谢,瑞克

4
那么sign()函数告诉您p1是半平面的哪一侧(由p2和p3之间的线形成)?
David Doria 2013年

1
请注意,如果您假设某个顶点的顺序(逆时针表示),则无需一直计算所有这些行列式。实际上,在最佳情况下,行列式1足以确定该点不在三角形内。
Thash 2016年

176

解决以下方程组:

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

p如果0 <= s <= 10 <= t <= 1和,则该点位于三角形内部s + t <= 1

st以及1 - s - t被称为重心坐标点的p


1
这比半平面检查要快,但是如果您不熟悉重心坐标,则可能会更难掌握。
Daniel Rikowski 2010年

8
使用Kornel方法中的琐碎出口(未实现),他的效率实际上比您高得多。如果您实际上尝试计算s和t,您就会明白我的意思。

85
我想测试一下,所以我做了一个jsfiddle,依靠@andreasdr解决方案和coproc注释:jsfiddle.net/PerroAZUL/zdaY8/1
urraka 2013年

5
优化:s + t <= 1隐含s <= 1以及t <= 1if s >= 0t >= 0
托马斯·爱丁

7
@Logic发布的文章totologic.blogspot.fr/2014/01/…有助于我更好地理解此解决方案
Flayn 2015年

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

只是评估st然后1-s-tp当且仅当它们都为正时,点才在三角形内。

编辑:请注意,该区域的上述表达式假定三角形节点编号为逆时针方向。如果编号为顺时针,则此表达式将返回负数区域(但幅度正确)。测试本身(s>0 && t>0 && 1-s-t>0)不依赖于编号的方向,因为1/(2*Area)如果三角形节点方向发生变化,则上面乘以的表达式也将更改符号。

编辑2:为获得更好的计算效率,请参阅下面的coproc注释(这是要点,如果事先知道三角形节点的方向(顺时针或逆时针),则2*Areas和中的除法t可以是避免)。在Andreas Brinck的回答下的注释中,也请参阅Perro Azul的jsfiddle-code 。


6
就是在求解方程组:)
Andreas Brinck 2013年

1
是的,我的观点是,基于求解方程组的计算成本对您的方法的任何批评都是没有根据的,因为这不必作为算法的一部分进行。
andreasdr 2013年

13
可以通过不进行除法来提高效率2*Area,即通过计算s´=2*|Area|*st´=2*|Area|*t(如果不知道点的方向(顺时针或逆时针)Area,则当然必须检查的符号,但否则甚至不进行检查。需要计算),因为检查s>0就足够了s´>0。而不是检查1-s-t>0就足够了s´+t´<2*|Area|
coproc

1
我可以补充一点,如果p0->p1->p2逆时针方向笛卡尔(通常是顺时针屏幕坐标),则Area通过该方法计算出的将是正的。
rhgb

1
@ user2600366当您沿三角形的边界沿p0-> p1-> p2-> p0等方向旅行时,依此类推,三角形的内部将始终位于您的右侧或左侧。在前一种情况下,编号是顺时针,在后一种情况下,编号是逆时针。
andreasdr

47

我在与Google进行最终尝试并找到此页面之前编写了此代码,所以我想与大家分享。它基本上是Kisielewicz答案的优化版本。我也研究了重心方法,但是从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;
}

换句话说,想法是:点s是AB和AC线的左边还是右边?如果为true,则不能在其中。如果为假,则至少在满足条件的“圆锥”内部。现在,由于我们知道三角形(三角形)内的点必须与BC(以及CA)在AB的同一侧,因此我们检查它们是否不同。如果它们这样做,则s不可能在内部,否则s必须在内部。

计算中的一些关键字是线半平面和行列式(2x2叉积)。也许更教学的方式可能是将其视为点,如果它在AB,BC和CA线的同一侧(左或右)。上面的方法似乎更适合某些优化。


2
此测试比提供的第一个测试快大约140-180%(感谢你们俩:)。我在这里运行代码:paste.ubuntu.com/p/k5w7ywH4p8使用了禁用优化功能的nodejs v8引擎,并得到了以下结果::w!node -p --minimum 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
–urgemcgee

@surgemcgee为什么不进行优化就运行它?难道这还不就是现实吗?
xuiqzy

@xuiqzy好,我的程序包含两个不同的解决方案。我还没有执行最快的方法。也许这评论应该被删除,并与我完成了这方面的作品..更换
surgemcgee

33

andreasdr和Perro Azul发布的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语句结尾的解决方案适用于顺时针和逆时针三角形的点。
路加·杜平

@LukeDupin不确定我是否理解您的评论。对于提供的3点订购,此答案的发布均有效。
Glenn Slayden '19年

12

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进行检查)。

如果您要使用相同的三角形测试不同的点,则重心版本最快。

重心形式在3个三角形点中不对称,因此由于浮点舍入误差,它的一致性可能不如Kornel Kisielewicz的边缘半平面形式。

信用:我是根据维基百科有关重心坐标的文章编写上述代码的。


不错!为了更好地处理数据输入,甚至可以改进使用javax.vecmath的Point3f / Point2f元组。
亚历克斯·伯斯

10

一种简单的方法是:

找到将点连接到三角形的三个顶点中的每个顶点的向量,并对这些向量之间的角度求和。如果角度之和为2 * pi,则该点位于三角形内部。

可以解释替代方案的两个不错的网站是:

blackpawn


3
嗯,这种方法并不十分有效,而且很容易出现数值错误……
Kornel Kisielewicz 2010年

相反,效率很低:-)不过,这只是一种简单的方法,很容易实现。您能举一个可能引起数值误差的例子吗?
西蒙·史蒂文斯

虽然对我而言,这似乎是本主题下所有答案中最好的,但我猜想三角形的边缘上的点已计算为包含在三角形中,而您对此没有可靠的控制。
Redu

考虑到pi的不合理性,在数字上不可能检查它是否恰好是2pi。但是,您只需要检查角度加起来是否大于pi。
lonewarrior556

10

通过使用重心坐标的解析解(由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
  • 部门:无
  • 比较:4

这与Kornel Kisielewicz解决方案(25个召回,1个存储,15个减法,6个乘法,5个比较)相比相当好,如果需要顺时针/逆时针检测(需要6个召回,1个加法,2个减法)可能会更好。 ,使用rhgb指出的解析解行列式本身进行2次乘法和1次比较。


不错的解决方案。我认为这是相当相当于我在这里最后一种方法在MSE:math.stackexchange.com/questions/51326/...
杰克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奇怪,您的示例对我有用,无论是在jsfiddle中(替换初始的“点”和“三角形”定义)还是在我的本地Python实现中。数值精度问题(尝试剥离一些小数)?
塞德里克·杜福尔

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

这是一个有效的Python实现:

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

和示例输出:

在此处输入图片说明


我无法进行此工作,例如对于三角形[[0,0),(3,0),(3,4)]中的点,既没有点(1,1)也没有(0 ,0)测试呈阳性。我尝试了顺时针和逆时针三角形的点。
ThorSummoner

3

如果您正在寻找速度,那么以下步骤可能会为您提供帮助。

在其纵坐标上对三角形顶点进行排序。这至少需要进行三个比较。令Y0,Y1,Y2为三个排序值。通过在它们上绘制三个水平线,可以将平面划分为两个半平面和两个平板。令Y为查询点的纵坐标。

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

还要进行两次比较。如您所见,对“边界板”之外的点实现了快速剔除。

(可选)您可以在横坐标上提供测试,以在左侧和右侧(X <= X0' or X >= X2')快速拒绝。这将同时执行快速边界框测试,但您也需要对横坐标进行排序。

最终,您需要相对于界定相关平板(上部或下部)的三角形的两侧计算给定点的符号。测试具有以下形式:

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

i, j, k组合的完整讨论(根据排序的结果,其中有六个)超出了此答案的范围,并且“留给读者练习”;为了提高效率,应该对它们进行硬编码。

如果您认为此解决方案很复杂,请注意,它主要涉及简单的比较(其中一些可以预先计算),加上6个减法和4个乘法,以防边界框测试失败。后者的成本很难克服,因为在最坏的情况下,您无法避免将测试点与两侧进行比较(其他答案中的任何方法都不具有较低的成本,有些方法会使成本降低,例如15次减法和6次乘法,有时是除法)。

更新:使用剪切变换更快

如上文所述,您可以使用两次比较,快速定位由三个顶点坐标所界定的四个水平带之一内的点。

您可以选择执行一个或两个额外的X测试来检查边界框的内部(虚线)。

然后考虑由给出的“剪切”变换X'= X - m Y, Y' = Y,其中mDX/DY最高边缘的斜率。此变换将使三角形的这一侧垂直。而且,由于您知道自己在中间水平线的哪一侧,因此就该三角形的单侧而言测试该符号就足够了。

在此处输入图片说明

假设您预先计算了斜率m,以及X'剪切后的三角形顶点的以及边的方程式的系数as X = m Y + p,则在最坏的情况下您将需要

  • 垂直分类的两个纵坐标比较;
  • 可选的一两个横坐标比较,用于边界框剔除;
  • 计算X' = X - m Y;
  • 与剪切三角形的横坐标进行一两个比较;
  • X >< m' Y + p'对剪切的三角形的相关边进行一次符号测试。

3

如果知道三个顶点的坐标和特定点的坐标,则可以获得完整三角形的面积。然后,计算三个三角形线段的面积(一个点是给定的点,另外两个点是三角形的任意两个顶点)。这样,您将获得三个三角形线段的面积。如果这些面积的总和等于总面积(您之前获得的面积),则该点应在三角形内。否则,该点将不在三角形内。这应该工作。如果有任何问题,请通知我。谢谢。


3

python中的其他功能,比Developer的方法快(至少对我而言),并受CédricDufour解决方案的启发:

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

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

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

您可以使用以下方法进行测试:

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

绘制需要花费很多时间,但是该网格在0.0195319652557秒内对开发人员代码 0.0844349861145秒进行了测试。

最后,代码注释:

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

该功能不起作用。给予ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])并返回,true尽管它是错误的
教宗

3

由于没有JS答案,因此
按顺时针和逆时针解决方案:

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

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

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

}

编辑:有用于det计算的错字(cy - ay而不是cx - ax),这是固定的。

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

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

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

}






let width = 500, height = 500

// clockwise
let triangle1 = {

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

}

// counter clockwise
let triangle2 = {

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

	color: '#00f',
	
}


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






// DRAW >

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

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

}

function drawArrow(ctx, A, B) {

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

}

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

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

}

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

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

}

function resetCanvas(canvas) {

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

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

function drawDots() {

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

	resetCanvas(canvas)
	
	let count = 1000

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

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

		
		ctx.fill()
		
	}
	
}

function drawInteractive() {

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

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

drawDots()
drawInteractive()










// trigo

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

}

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

}

function sub(A, B) {

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

}

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

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

}

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

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

}
* {
	margin: 0;
}

html {
	font-family: monospace;
}

body {
	padding: 32px;
}

span.red {
	color: #f00;
}

span.blue {
	color: #00f;
}

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

在此处输入图片说明

我在这里使用与上述相同的方法:如果一个点分别位于AB,BC,CA每条线的“相同”侧,则该点位于ABC内。

三角形夹杂物示例


我厌倦了这段代码,它不起作用。它总是返回False。
xApple

嗯...你可能弄错了。这是一个正在运行该功能的小提琴:jsfiddle.net/jniac/rctb3gfL
Joseph Merdrignac

我已经看到了您的Python响应,我们使用的是相同的方法,如果我再使用一行(let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)),这是为了确定三角形的缠绕顺序,因此该方法将适用于CW和CCW三角形(请参阅jsFiddle)。
约瑟夫·梅德里格纳克

1
我写错了:我写错了: let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)并非 let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)如此,这是固定的,感谢您的举报
Joseph Merdrignac

2

我只想使用一些简单的矢量数学来解释Andreas给出的重心坐标解,这将更容易理解。

  1. 区域A定义为s * v02 + t * v01给定的任何向量,条件s> = 0且t> =0。如果三角形v0,v1,v2内的任何点必须在区域A内。

在此处输入图片说明

  1. 如果进一步限制s,则t属于[0,1]。我们得到区域B,其中包含s * v02 + t * v01的所有向量,条件为s,t属于[0,1]。值得注意的是,区域B的下部是三角形v0,v1,v2的镜像。问题在于是否可以给定s和t的某些条件以进一步排除区域B的低部分。

在此处输入图片说明

  1. 假设我们给定值s,而t在[0,1]中变化。在下面的图片中,点p在v1v2的边缘。s * v02 + t * v01的所有向量都沿着虚线通过简单向量求和。在v1v2和虚线交叉点p,我们有:

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

我们得到1-s = tp,那么1 = s + tp。如果任何t> tp,即在双点划线上的1 <s + t,则向量在三角形之外,任何t <= tp,其中1> = s + t,在单点划线上,则向量为在三角形内。

然后,如果我们在[0,1]中给定任何s,则对于三角形内的矢量,对应的t必须满足1> = s + t。

在此处输入图片说明

所以最终我们得到v = s * v02 + t * v01,v在条件s为t的三角形内部,t,s + t属于[0,1]。然后翻译成点,我们有

p-p0 = s *(p1- p0)+ t *(p2-p0),其中s,t,s + t在[0,1]中

这与Andreas求解方程组p = p0 + s *(p1-p0)+ t *(p2-p0)的方法相同,其中s,t,s + t属于[0,1]。


您可以说您使用了由三个顶点定义的局部框架,因此边变为s = 0,t = 0和s + t = 1。仿射坐标变换是线性代数的众所周知的运算。
伊夫·达乌斯特

2

这是一个高效的python解决方案,已有文档证明,其中包含三个单元测试。它具有专业级的质量,可以按模块原样放入您的项目中。

import unittest

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

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

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

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

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

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

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

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

上述算法还有一个可选的图形测试,以确认其有效性:

import random
from matplotlib import pyplot
from triangle_test import point_in_triangle

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

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

# Number of random points #
count_points = 10000

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

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

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

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

产生以下图形:

测试point_in_triangle函数


1

在令人讨厌的边缘条件下,点恰好位于两个相邻三角形的公共边缘上。该点不能同时位于两个三角形中,也不能位于两个三角形中。您需要一种任意但一致的分配点的方法。例如,画一条穿过该点的水平线。如果该线与右侧三角形的另一侧相交,则将该点视为在三角形内部。如果相交在左边,则该点在外面。

如果该点所在的线是水平的,则在上方/下方使用。

如果该点位于多个三角形的公共顶点上,请使用该点的中心形成最小角度的三角形。

更有趣:三个点可以成一条直线(零度),例如(0,0)-(0,10)-(0,5)。在三角剖分算法中,必须放弃“耳朵”(0,10),生成的“三角”是直线的退化情况。


1

这是确定点是在三角形的内部还是外部还是在三角形的手臂上的最简单概念。

通过行列式确定点在三角形内:

行列式确定点在三角形内

最简单的工作代码:

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

import numpy as np

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

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

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

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

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

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

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

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

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


0

坦率地说,这就像西蒙·史蒂文Simon P Steven)的回答一样简单,但是采用这种方法,您无法确定是否要包含三角形边缘上的点。

我的方法有些不同,但非常基础。考虑下面的三角形;

在此处输入图片说明

为了使该点在三角形中,我们必须满足3个条件

  1. ACE角(绿色)应小于ACB角(红色)
  2. ECB角(蓝色)应小于ACB角(红色)
  3. 当将它们的x和y值应用于| AB |的方程时,E点和C点应该具有相同的符号。线。

在这种方法中,您可以完全控制单独包括或排除边缘上的点。因此,您可以检查点是否在仅包含| AC |的三角形中 例如边缘。

因此,我在JavaScript中的解决方案如下:

function isInTriangle(t,p){

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

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

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

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


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

没有比这更有效的了!三角形的每一边都可以具有独立的位置和方向,因此肯定需要三个计算:l1,l2和l3,每个计算涉及2个乘法。一旦知道了l1,l2和l3,结果就是一些基本比较和布尔运算。


0

据说我在JavaScript中改编了高性能代码(以下文章):

function pointInTriangle (p, p0, p1, p2) {
  return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}
  • pointInTriangle(p, p0, p1, p2) -用于逆时针三角形
  • pointInTriangle(p, p0, p1, p2) -用于顺时针三角形

一下jsFiddle(包括性能测试),在另一个函数中也进行了绕组检查。或按下面的“运行代码段”

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

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

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

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

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

test();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

受此启发:http : //www.phatcode.net/articles.php?id=459


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

从重心转换的几乎完美的笛卡尔坐标在* v(x)和* w(y)的倍数内导出。在每种情况下,两个出口双打都应在前面加上*字符,可能是:* 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)]

或python代码可以写成:

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])]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.