如何确定一个点是不是在二维三角形中? [关闭]

Posted

技术标签:

【中文标题】如何确定一个点是不是在二维三角形中? [关闭]【英文标题】:How to determine if a point is in a 2D triangle? [closed]如何确定一个点是否在二维三角形中? [关闭] 【发布时间】:2011-01-04 05:01:16 【问题描述】:

有没有一种简单的方法可以确定一个点是否在三角形内?这是 2D,不是 3D。

【问题讨论】:

我写了一篇关于三角点测试的完整文章。它显示了基于重心、参数和点积的方法。然后它处理当一个点恰好位于一个边缘时发生的准确性问题(带有示例)。最后,它公开了一种基于点到边距离的全新方法。 totologic.blogspot.fr/2014/01/…享受! Similar question for 3D 值得注意的是,这里讨论的任何方法在 3D 空间中也是有效的。它们只需要在坐标变换之前(以及点在三角形平面上的适当投影)。三角形是二维对象。 我投票结束这个问题,因为它是关于数学而不是编程,并且是基于意见的(什么对你来说“容易”?)。 这个问题被关闭的事实表明 SO 是有缺陷的。测试多边形(三角形)中的点是一个常见的编程问题。 【参考方案1】:

一般来说,最简单(也是相当优化)的算法是检查点在由边缘创建的半平面的哪一侧。

这是topic on 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);

【讨论】:

常用于2D。重心坐标往往会使人感到困惑。此外,鉴于三角形的坐标和点坐标,我不确定使用重心的效率。 @Kornel 重心版本在 2D 中也更有效。您的解决方案还有一个问题,即它会根据三角形是按顺时针还是逆时针顺序指定三角形边缘上的点而报告不同的结果。 就我的目的(我发现这个网站的原因)而言,Kornel Kisielewicz 提出的原始答案要高效得多。我正在使用具有 BYTE 大小坐标的 LCD 显示器和一个非常典型的微处理器,其中整数乘法是一个非常快的指令,而除法要慢得多。由于没有除法,数字问题也小得多!所有计算都是准确的。谢谢,里克 那么 sign() 函数告诉你半平面的哪一侧(由 p2 和 p3 之间的线形成)p1 是? 请注意,如果您假设顶点的某种顺序(例如逆时针),则无需一直计算所有这些行列式。事实上,在最好的情况下,1 个行列式就足以发现该点不在三角形内。【参考方案2】:

一个简单的方法是:

找到连接的向量 指向三角形的三个中的每一个 顶点和总和之间的角度 那些向量。如果总和 角度是 2*pi 然后点是 在三角形内。

解释替代方案的两个好网站是:

blackpawn 和 wolfram

【讨论】:

嗯,那个方法效率不高,而且很容易出现数值错误... 恰恰相反,它非常低效:-) 这只是一种简单的方法,很容易实现。你能举一个这会导致数字错误的例子吗? 虽然对我来说这似乎是本主题下所有答案中最好的,但我猜三角形边缘上的点被计算为包含在三角形中,而你没有对此进行可靠的控制。 检查它是否正好是 2pi 在数字上是不可能的,因为 pi 是不合理的。但是,您只需要检查角度加起来是否大于 pi。【参考方案3】:

求解以下方程组:

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

如果0 &lt;= s &lt;= 10 &lt;= t &lt;= 1s + t &lt;= 1,点p 位于三角形内。

s,t1 - s - t 被称为点barycentric coordinates 的p

【讨论】:

这比半平面检查要快,但如果您不熟悉重心坐标,可能会有点难以掌握。 在 Kornel 的方法中使用微不足道的退出(未实现),他的方法实际上比你的效率更高。如果您真的尝试计算 s 和 t,您就会明白我的意思。 我想对此进行测试,所以我做了一个 jsfiddle,依靠 @andreasdr 解决方案和 coproc 评论:jsfiddle.net/PerroAZUL/zdaY8/1 优化:s + t &lt;= 1 意味着 s &lt;= 1t &lt;= 1 如果 s &gt;= 0t &gt;= 0 @Logic post 提出的文章totologic.blogspot.fr/2014/01/… 帮助我更好地理解了这个解决方案【参考方案4】:

我在与 Google 进行最后一次尝试并找到此页面之前编写了此代码,所以我想我会分享它。它基本上是 Kisielewicz 答案的优化版本。我也研究了重心方法,但从***的文章来看,我很难看出它是如何更有效的(我猜有一些更深层次的等价性)。反正这个算法的好处是不用除法;一个潜在的问题是边缘检测的行为取决于方向。

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 线的左边还是右边?如果是真的,它不可能在里面。如果为假,则至少在满足条件的“锥体”内。现在,既然我们知道三角形(三角形)内的一个点必须与 BC(以及 CA)在 AB 的同一侧,我们检查它们是否不同。如果有,s 不可能在里面,否则 s 必须在里面。

计算中的一些关键字是线半平面和行列式(2x2 叉积)。也许一种更具教学性的方式可能是将其视为位于内部的一个点,当且仅当它与 AB、BC 和 CA 行的同一侧(左侧或右侧)。然而,上述方式似乎更适合一些优化。

【讨论】:

这个测试比提供的第一个测试快大约 140-180%(谢谢你们两个顺便说一句:)。我在这里运行代码:paste.ubuntu.com/p/k5w7ywH4p8 使用禁用优化的 nodejs v8 引擎并得到以下结果: :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 @surgemcgee 你为什么要在没有优化的情况下运行它?那不是更远离现实吗? @xuiqzy 好吧,我的程序包含两种不同的解决方案。我还没有管理最快的方法。也许该评论应该被删除并替换为我已完成的关于此的作品..【参考方案5】:

我所做的是预先计算三个面部法线,

在 3D 中通过侧向量和面法向量的叉积。

在 2D 中通过简单地交换组件并取反一个,

然后任何一侧的内部/外部是当侧面法线和顶点到点向量的点积改变符号时。对其他两个(或更多)面重复此操作。

好处:

很多都是预先计算的,非常适合在同一个三角形上进行多点测试。

早期拒绝外多于内点的常见情况。 (如果点分布加权到一侧,可以先测试那一侧。)

【讨论】:

【参考方案6】:

我同意 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);

只需评估st1-s-tp 点在三角形内当且仅当它们都是正数。

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

编辑 2:为了获得更好的计算效率,请参阅下面 coproc 的评论(这表明如果事先知道三角形节点的方向(顺时针或逆时针),可以避免在st 的表达式中除以2*Area)。另请参阅 Andreas Brinck 答案下 cmets 中 Perro Azul 的 jsfiddle-code。

【讨论】:

正在解方程组:) 是的,我的观点是,基于求解方程组的计算成本对您的方法的任何批评都是没有根据的,因为这不必作为算法的一部分进行。 不除以2*Area,即通过计算s´=2*|Area|*st´=2*|Area|*t可以提高效率(如果点的方向 - 顺时针或逆时针 - 未知,则当然,必须检查Area 的符号,否则它可能甚至不需要计算),因为检查s&gt;0 就足以检查s´&gt;0。而不是检查1-s-t&gt;0,检查s´+t´&lt;2*|Area|就足够了。 我可以补充一点,如果p0-&gt;p1-&gt;p2笛卡尔中是逆时针(通常是顺时针在屏幕坐标),这种方法计算出来的Area为正数。 @user2600366 当您沿三角形边界沿 p0 -> p1 -> p2 -> p0 方向移动时,以此类推,三角形的内部将始终位于您的右侧或者总是在你的左边。前一种情况是顺时针编号,后一种情况是逆时针编号。【参考方案7】:

C# 版本的重心方法,由 andreasdr 和 Perro Azul 发布。当st 有相反的符号(并且两者都不是零)时,我添加了一个检查以放弃面积计算,因为可能避免三分之一的乘法成本似乎是合理的。

public static bool PointInTriangle(Point p, Point p0, Point p1, Point p2)

    var s = (p0.X - p2.X) * (p.Y - p2.Y) - (p0.Y - p2.Y) * (p.X - p2.X);
    var t = (p1.X - p0.X) * (p.Y - p0.Y) - (p1.Y - p0.Y) * (p.X - p0.X);

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

    var d = (p2.X - p1.X) * (p.Y - p1.Y) - (p2.Y - p1.Y) * (p.X - p1.X);
    return d == 0 || (d < 0) == (s + t <= 0);

2021 年更新:此版本可正确处理任意缠绕方向(顺时针与逆时针)指定的三角形。请注意,对于恰好位于三角形边缘的点,此页面上的其他一些答案会根据列出三角形三个点的顺序给出不一致的结果。这些点被认为是“在”三角形中,无论缠绕方向如何,此代码都会正确返回 true

【讨论】:

if 语句结尾的解决方案适用于顺时针和逆时针三角形点。 @LukeDupin 不确定我是否理解您的评论。此答案适用于任何提供的 3 点订购。 @GlennSlayden 哪些点是三角形,哪些是我们要寻找的点? @USer22999299 第一个参数p 是您要查找的点。其余 3 个 Point 参数 p0p1p2 建立了一个您希望在其中搜索的三角形。 感谢您在这里发布。就一件事。您的额外检查打破了该算法对缠绕顺序的冷漠。三角形 (1,1;1,2;2,2) 和点 (1,1.5) 被认为不匹配,尽管它位于边缘。删除你的两条线虽然可以解决问题。但是,再次感谢您发布它。这是一个很大的帮助。【参考方案8】:

这是一个高效的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) 测试阳性。我尝试了顺时针和逆时针三角形点。【参考方案9】:

如果您正在寻找速度,这里有一个可能对您有所帮助的过程。

按其纵坐标对三角形顶点进行排序。这需要进行最坏的三个比较。令 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 &lt;= X0' or X &gt;= 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 给出的“剪切”变换,其中m 是最高边缘的斜率DX/DY。这种变换将使三角形的这一边垂直。而且由于您知道自己在中间水平线的哪一边,因此只需针对三角形的单边测试符号即可。

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

垂直分类的两个纵坐标比较; 可选的一个或两个横坐标比较来拒绝边界框; X' = X - m Y的计算; 与剪切三角形的横坐标一两比较; 一个符号测试X &gt;&lt; m' Y + p' 对剪切三角形的相关边。

【讨论】:

这很聪明!在最后一种情况下应用第二种不同的剪切变换是否可能且有益?【参考方案10】:

当您完全确定三角形是顺时针方向时,我需要在“可控环境”中检查三角形点。因此,我采用了 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  ></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;

【讨论】:

【参考方案11】:

如果你知道三个顶点的坐标和特定点的坐标,那么你就可以得到整个三角形的面积。然后,计算三个三角形段的面积(一个点是给定的点,另外两个是三角形的任意两个顶点)。因此,您将获得三个三角形段的面积。如果这些面积的总和等于总面积(您之前得到的),那么该点应该在三角形内部。否则,该点不在三角形内。这应该有效。如果有任何问题,请告诉我。谢谢。

【讨论】:

【参考方案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 的边缘半平面版本一致,因为浮点舍入误差。

Credit:我从***关于重心坐标的文章中制作了上述代码。

【讨论】:

不错!甚至可以改进使用 javax.vecmath 的 Point3f / Point2f 元组,以便更好地处理数据输入。 只是好奇:为什么那个包缺少 Point2i 类?【参考方案13】:

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

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

如果该点在多个三角形的公共顶点上,则使用该点与该点形成最小角度的三角形。

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

【讨论】:

【参考方案14】:

通过使用barycentric coordinates 的解析解(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 或点击下方“运行代码 sn-p”试试

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  ></canvas>

导致:

变量“召回”:30 变量存储:7 补充:4 减法:8 乘法:6 部门:无 比较:4

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

【讨论】:

不错的解决方案。我认为这与我在 MSE 上的最后一种方法相当:math.stackexchange.com/questions/51326/… 我只是按原样测试了代码,但它对我不起作用(例如 p -4.69317198、-6.99191951 p0 -7.05846786 0.596718192 p1 -6.8703599 -2.36565161 p2 -4.69317198、-6.99191)跨度> @GiovanniFunchal 奇怪,您的示例在 jsfiddle(替换初始的“点”和“三角形”定义)和我的本地 Python 实现中都适用于我。数值精度问题(尝试去除一些小数)? 你似乎是我测试中最快的:jsfiddle.net/eyal/gxw3632c/27。不过,所有方法之间的差异都很小。 实际上,经过进一步研究,它似乎可以轻松修复。将 ptInTriangle 的最后一行更改为 "return s >= 0.0 && t >= 0.0 && (s + t) 【参考方案15】:

据说是我在 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(包括性能测试),还有一个单独的函数中的绕组检查。或者按下面的“运行代码sn-p”

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  ></canvas>

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

【讨论】:

【参考方案16】:

我只是想用一些简单的向量数学来解释安德烈亚斯给出的重心坐标解,这样会更容易理解。

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

    如果进一步限制s,并且t属于[0, 1]。我们得到区域 B,其中包含 s * v02 + t * v01 的所有向量,条件为 s,t 属于 [0, 1]。值得注意的是,B区的低部分是三角形v0、v1、v2的镜像。问题来了,如果我们能给出s和t的一定条件,进一步排除B区的低部分。

    假设我们给出一个值 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 在单虚线上,向量是三角形内。

那么如果我们给定 [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),其中 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。仿射坐标变换是众所周知的线性代数运算。【参考方案17】:

适用于所有类型三角形的最简单方法是简单地确定 P 点 A、B、C 点的角度。任何一个角度大于180.0就是在外,180.0就是在圆周上,如果acos欺骗你,小于180.0就是在里面。望理解http://math-physics-psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html

【讨论】:

【参考方案18】:

老实说,它就像 Simon P Steven's answer 一样简单,但是使用这种方法,您无法完全控制是否要包含三角形边缘上的点。

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

为了得到三角形中的点,我们必须满足 3 个条件

    ACE 角度(绿色)应小于 ACB 角度(红色) ECB 角度(蓝色)应小于 ACB 角度(红色) 当点 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));

【讨论】:

【参考方案19】:
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,只需进行一些基本的比较和布尔运算即可得到结果。

【讨论】:

【参考方案20】:

python 中的其他函数,比 Developer's method(至少对我而言)更快,并受到 Cédric Dufour 解决方案的启发:

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

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

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

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

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

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

最后是代码注释:

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

【讨论】:

此功能无效。给ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])它会返回true虽然它是假的【参考方案21】:

既然没有 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    


编辑:修正了两个错别字(关于符号和比较)。

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。 嗯......你可能犯了一个错误。这是运行该功能的小提琴:jsfiddle.net/jniac/rctb3gfL 我看到了你的 Python 响应,我们使用相同的方法,如果我多使用一行 (let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)),这是确定三角形缠绕顺序,因此该方法适用于CW 和 CCW 三角形(参见 jsFiddle)。 hm 我犯了一个错误,我写的是:let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay) 而不是let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax) 所以这个问题已经解决了,感谢报告 你的triangleContains函数不正确;对于(1, 1.5),它错误地返回false——对于两个替代绕组(1, 1) (1, 2) (2, 2)(1, 1) (2, 2) (1, 2)——即使该点显然位于三角形的边缘。我相信您编写的函数中的所有三个不等式都应该是 … &gt;= 0 而不是 … &gt; 0【参考方案22】:
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; 之后结果应该是正确的

【讨论】:

【参考方案23】:

这是一个高效、有文档且包含三个单元测试的 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")

制作如下图:

【讨论】:

【参考方案24】:

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

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

最简单的工作代码:

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

【讨论】:

【参考方案25】:

检查三角形顶点是否形成的区域的最简单方法之一 (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])]

【讨论】:

以上是关于如何确定一个点是不是在二维三角形中? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

确定一个点是不是在由具有给定纬度/经度的 3 个点组成的三角形内

如何确定 3-D 射线穿过的第一个三角形(一组三角形中)?

网格二维三角剖分

如何检查圆是否与二维空间中的三角形相交?

二维图像的三角形变换算法解释

给定一个三角形和一个线段,找到第三个点,它会创建一个相似的三角形[关闭]