【问题标题】:How to check if a point lies on a line between 2 other points如何检查一个点是否位于另外两个点之间的线上
【发布时间】:2012-08-08 03:02:18
【问题描述】:

我将如何编写这个函数?任何例子表示赞赏

function isPointBetweenPoints(currPoint, point1, point2):Boolean {

    var currX = currPoint.x;
    var currY = currPoint.y;

    var p1X = point1.x;
    var p1y = point1.y;

    var p2X = point2.x;
    var p2y = point2.y;

    //here I'm stuck
}

【问题讨论】:

  • 下面有一些很好的答案,但我想我会指出您应该注意浮点精度问题。无论您使用哪种方法,在测试两个不同的斜率是否相同时,您都可能需要允许少量错误。
  • @Adrian McCarthy:这是基于斜率的方法的主要问题。斜率随角度变化不均匀:线越接近垂直,斜率增长越快(更不用说垂直和几乎垂直线的特殊情况)。根本没有好的基于斜率的策略。我会不惜一切代价避免使用基于斜率的方法。

标签: algorithm geometry


【解决方案1】:

这种方法类似于Steve's approach,只是更短并且经过改进以使用尽可能少的内存和处理能力。但首先是数学思想:

a, b 为行尾,ab 为它们之间的差异,p 为要检查的点。那么 p 正好是 then 就行了,如果

a + i * ab = p

其中 i 是区间 [0;1] 中的数字,表示行上的索引。我们可以把它写成两个独立的方程(对于 2D):

a.x + i * ab.x = p.x
a.y + i * ab.y = p.y

i = (p.x - a.x) / ab.x
i = (p.y - a.y) / ab.y

这给了我们要求 pab 的线路:

(p.x - a.x) / ab.x = (p.y - a.y) / ab.y

0≤i≤1

在代码中:

function onLine(a, b, p) {
    var i1 = (p.x - a.x) / (b.x - a.x), i2 = (p.y - a.y) / (b.y - a.y);
    return i1 == i2 && i1 <= 0 && i1 >= 1;
}

从技术上讲,您甚至可以内联 i2,但这会使其更难阅读。

【讨论】:

  • 我希望这对于 ab 定义的水平和垂直线会失败。
【解决方案2】:
   Distance(point1, currPoint)
 + Distance(currPoint, point2)
== Distance(point1, point2)

但要小心,如果你有浮点值,它们的情况会有所不同......

当担心计算“平方根”的计算成本时,不要:
只需比较“正方形”即可。

【讨论】:

  • 非常好,使用浮点值(比如在 JavaScript 中)你可以做类似return distanceAC + distanceBC - distanceAB &lt; THRESHOLD;
  • 这种方法比上面AnT的答案更稳定。
  • 但它需要更多的计算能力(3 倍平方根)。
  • @Chrisstar,如果平方根是一个瓶颈,可以重写方程来避免它 - 单独平方两个部分只留下一个平方根,更复杂的是我们也可以摆脱它。
  • 用于提高速度比较 squareDistances : abs(a * a + b * b - c * c)
【解决方案3】:

准备好接受似乎比其他一些解决方案更简单的方法了吗?

您将三个点传递给它(三个具有 x 和 y 属性的对象)。第 1 点和第 2 点定义您的线,第 3 点是您要测试的点。

function pointOnLine(pt1, pt2, pt3) {
    const dx = (pt3.x - pt1.x) / (pt2.x - pt1.x);
    const dy = (pt3.y - pt1.y) / (pt2.y - pt1.y);
    const onLine = dx === dy

    // Check on or within x and y bounds
    const betweenX = 0 <= dx && dx <= 1;
    const betweenY = 0 <= dy && dy <= 1;

    return onLine && betweenX && betweenY;
}

console.log('pointOnLine({x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2})');
console.log(pointOnLine({ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }));

console.log('pointOnLine({x: 0, y: 0}, {x: 1, y: 1}, {x: 0.5, y: 0.5})');
console.log(pointOnLine({ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 0.5, y: 0.5 }));

编辑:根据 RBarryYoung 的观察进一步简化。

【讨论】:

  • 你真的需要变量之间的变量吗?检查dxdy 是否在0.0 和1.0 之间还不够吗?既然dx === dy你只需要检查其中一个?
  • 不错@RBarryYoung,又简单了!我已经更新了答案。为了便于阅读和解释,我保留了变量。
  • 我希望这对于 p1 和 p2 定义的水平和垂直线会失败。
【解决方案4】:

我将使用三角形方法:

首先,我会检查Area,如果Area接近0,那么Point就在Line上。

但是想想AC的长度这么大的情况,那么Area从0增加了很多,但是视觉上我们仍然看到B在AC上:当我们需要检查三角形的高度时。

为此,我们需要记住我们从一年级学到的公式:Area = Base * Height / 2

代码如下:

    bool Is3PointOn1Line(IList<Vector2> arrVert, int idx1, int idx2, int idx3)
    {
        //check if the area of the ABC triangle is 0:
        float fArea = arrVert[idx1].x * (arrVert[idx2].y - arrVert[idx3].y) +
            arrVert[idx2].x * (arrVert[idx3].y - arrVert[idx1].y) +
            arrVert[idx3].x * (arrVert[idx1].y - arrVert[idx2].y);
        fArea = Mathf.Abs(fArea);
        if (fArea < SS.EPSILON)
        {
            //Area is zero then it's the line
            return true;
        }
        else
        {
            //Check the height, in case the triangle has long base
            float fBase = Vector2.Distance(arrVert[idx1], arrVert[idx3]);
            float height = 2.0f * fArea / fBase;
            return height < SS.EPSILON;
        }
    }

用法:

Vector2[] arrVert = new Vector2[3];

arrVert[0] = //...
arrVert[1] = //...
arrVert[2] = //...

if(Is3PointOn1Line(arrVert, 0, 1, 2))
{
    //Ta-da, they're on same line
}

PS:SS.EPSILON = 0.01f,我使用了 Unity 的一些功能(例如:Vector2.Distance),但你明白了。

【讨论】:

  • 非常有趣!但是我想知道如果我们尝试检查 C 右侧的点会发生什么。是否有可能我们在这一点上也得到 0?
  • @LucasSousa :你的意思是问“如果 B 在 C 的右边而不是在 A 和 C 的中间,那么函数仍然正确吗?”?如果是这样,该函数仍然是正确的,不管 B 是否在 A 和 C 之间。
【解决方案5】:

假设point1point2 不同,首先检查点是否在线。为此,您只需要向量 point1 -&gt; currPointpoint1 -&gt; point2 的“叉积”。

dxc = currPoint.x - point1.x;
dyc = currPoint.y - point1.y;

dxl = point2.x - point1.x;
dyl = point2.y - point1.y;

cross = dxc * dyl - dyc * dxl;

当且仅当cross 等于零时,您的观点才成立。

if (cross != 0)
  return false;

现在,您知道该点确实位于线上,是时候检查它是否位于原始点之间。这可以通过比较 x 坐标来轻松完成,如果线条“比垂直更水平”,或者y 坐标,否则

if (abs(dxl) >= abs(dyl))
  return dxl > 0 ? 
    point1.x <= currPoint.x && currPoint.x <= point2.x :
    point2.x <= currPoint.x && currPoint.x <= point1.x;
else
  return dyl > 0 ? 
    point1.y <= currPoint.y && currPoint.y <= point2.y :
    point2.y <= currPoint.y && currPoint.y <= point1.y;

请注意,如果输入数据是整数,则上述算法如果完全是整数,即整数输入不需要浮点计算。不过在计算 cross 时要小心潜在的溢出。

附:该算法是绝对精确的,这意味着它将拒绝非常靠近直线但不精确在线的点。有时这不是我们所需要的。但那是另一回事。

【讨论】:

  • 您可以通过在叉积验证中实现阈值来降低算法精度所以如果叉积几乎为零,那么该点几乎在线上threshold = 0.1; if (abs(cross) &gt; threshold) return false;
  • 我们可以简化一下吗?既然我们知道它在线,我们为什么要关心它是否比垂直更水平?线上任何给定的 x 都只能有一个 y 值,所以如果 currPoint.x 介于 point1.xpoint2.x 之间,那么它怎么可能是线上以外的任何地方?
  • @mkirk:“线上任何给定的 x 都只能有一个 y 值” - 对于垂直线而言并非如此。如果该段是严格垂直的,则对x 的范围检查不会产生有意义的答案。是的,总是可以检查x 范围,除了对于严格垂直的段,必须检查y 范围。我的“更水平”/“更垂直”的方法只是对此的平衡概括。
  • 感谢@AnT 的澄清,你是对的! if dxl != 0 可能更中肯,而且速度稍快。
  • @Romel Pérez:不确定你的意思。上面的答案清楚地显示了如何检查该点是否位于两个原始点之间。
【解决方案6】:

您要检查从point1currPoint 的斜率是否与从currPointpoint2 的斜率相同,所以:

m1 = (currY - p1Y) / (currX - p1X);
m2 = (p2Y - currY) / (p2X - currX);

您还想检查currPoint是否在其他两个创建的框内,所以:

return (m1 == m2) && (p1Y <= currY && currY <= p2Y) && (p1X <= currX && currX <= p2X);

编辑:这不是一个很好的方法;查看maxim1000's solution 以获得更正确的方法。

【讨论】:

  • 是的,我刚刚意识到我的错误。我想我可以添加另一个约束来解决这个问题。
  • 但是输入数据并没有说明p1X &lt;= p2Xp1Y &lt;= p2Y(而且,它根本不可能在所有情况下都是真的)。然而,只有满足这些条件,您的最终检查才能正常工作。最重要的是,您的算法依赖于浮点值m1m2 的直接精确比较。不用说,由于浮点计算的不精确性,这根本行不通。
  • ... 甚至没有提到基于斜率的方法不能处理垂直线。被零除呢?
  • @AnT 因为所需的条件是 m1 == m2 您可以将其重新表述为 (currY - p1Y) * (p2X - currX) == (currX - p1X) * (p2Y - currY) 。通过这样做,您可以避免浮点比较和除以零,因此基于斜率的方法是完全有效的。应该满足的第二个条件是sqrDistance(p1, curr) &lt;= sqrDistance(p1,p2) &amp;&amp; sqrDistance(p2, curr) &lt;= sqrDistance(p1,p2)。这样可以确保 currPoint 在 p1 和 p2 之间。使用 sqrDistance(平方距离)以避免浮点数
【解决方案7】:

这与 Javascript 无关。尝试以下算法,点 p1=point1 和 p2=point2,第三点是 p3=currPoint:

v1 = p2 - p1
v2 = p3 - p1
v3 = p3 - p2
if (dot(v2,v1)>0 and dot(v3,v1)<0) return between
else return not between

如果您想确定它也在 p1 和 p2 之间的线段上:

v1 = normalize(p2 - p1)
v2 = normalize(p3 - p1)
v3 = p3 - p2
if (fabs(dot(v2,v1)-1.0)<EPS and dot(v3,v1)<0) return between
else return not between

【讨论】:

    猜你喜欢
    • 2021-03-14
    • 2017-06-26
    • 2014-08-12
    • 2015-07-09
    • 2016-01-14
    • 1970-01-01
    • 1970-01-01
    • 2011-04-19
    • 1970-01-01
    相关资源
    最近更新 更多