简体   繁体   English

如何测试一条线是否与特定点相撞

[英]How to test is a line is colliding with a certain point

So, I have an HTML canvas, and I can draw stuff on it. 因此,我有一个HTML画布,并且可以在其上绘制内容。

What I want to do is make a javascript function to tell me if a line is colliding with a certain point. 我想做的是制作一个javascript函数,告诉我一条线是否与某个点发生碰撞。

I have seen algorithms to test if a line collides with another line, like this one: 我已经看到算法来测试一条线是否与另一条线发生冲突,例如:

 var IsIntersecting = function(Point a, Point b, Point c, Point d) { var denominator = ((bX - aX) * (dY - cY)) - ((bY - aY) * (dX - cX)); var numerator1 = ((aY - cY) * (dX - cX)) - ((aX - cX) * (dY - cY)); var numerator2 = ((aY - cY) * (bX - aX)) - ((aX - cX) * (bY - aY)); if (denominator == 0) { return numerator1 == 0 && numerator2 == 0;} var r = numerator1 / denominator; var s = numerator2 / denominator; return (r >= 0 && r <= 1) && (s >= 0 && s <= 1); } 

But I want to see if it collides with a specific point. 但我想看看它是否与特定点冲突。 I have not found a good algorithm to do this yet. 我还没有找到一个好的算法来做到这一点。 Can you help me? 你能帮助我吗?

Usage 用法

a and b are endpoints of the line that point p should fall between. ab是点p应当位于其间的直线的端点。

Hypothetically, this should work: 假设地,这应该起作用:

function pointOnLine(a, b, p) {
  var lx = b.x - a.x
  var ly = b.y - a.y
  var dx = p.x - a.x
  var dy = p.y - a.y
  var l = Math.sqrt(lx * lx, ly * ly)
  var d = Math.sqrt(dx * dx, dy * dy)
  var q = d / l

  return d <= l && q * lx === dx && q * ly === dy
}

However, due to floating point error, and considering you're probably working with pixel coordinates, I think this would suffice with a default error of .5 : 但是,由于浮点错误,并且考虑到您可能正在使用像素坐标,因此我认为默认误差为.5满足要求:

function pointOnLine(a, b, p) {
  var e = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : .5;
  var lx = b.x - a.x
  var ly = b.y - a.y
  var dx = p.x - a.x
  var dy = p.y - a.y
  var l = Math.sqrt(lx * lx, ly * ly)
  var d = Math.sqrt(dx * dx, dy * dy)
  var q = d / l

  return d <= l && Math.abs(q * lx - dx) < e && Math.abs(q * ly - dy) < e
}

ES6 improvement ES6改进

Here's a similar method that solves an edge-case issue when Math.sqrt() returns Infinity for components that have an absolute value greater than Math.sqrt(Number.MAX_VALUE) . 这是一种类似的方法,当Math.sqrt()返回绝对值大于Math.sqrt(Number.MAX_VALUE)组件的Infinity时,可以解决这种情况。 It uses a new method called Math.hypot() : 它使用了一个称为Math.hypot()的新方法:

function pointOnLine({ x: ax, y: ay }, { x: bx, y: by }, { x: px, y: py }, e = .5) {
  const lx = bx - ax
  const ly = by - ay
  const dx = px - ax
  const dy = py - ay
  const l = Math.hypot(lx, ly)
  const d = Math.hypot(dx, dy)
  const q = d / l

  return d <= l && Math.abs(q * lx - dx) < e && Math.abs(q * ly - dy) < e
}

How it works 这个怎么运作

This method gets the component differences between the endpoints of the line lx , ly , and then calculates the length of the line l . 此方法获取线lx ly的端点之间的分量差,然后计算线l的长度。

It then gets the component differences between the test point p and point a of the line dx , dy , and calculates the distance between those points d . 然后,它获得测试点p与线dxdya之间的分量差,并计算这些点d之间的距离。

After that, it calculates the quotient q of d / l , and the test first checks if distance d is less than length l to ensure that it's possible for the point to fall between the endpoints of the line. 之后,它计算d / l的商q ,然后测试首先检查距离d是否小于长度l以确保该点有可能落在直线的端点之间。 Lastly, it checks to see if the component differences between the line endpoints multiplied by the quotient q are equal to the component differences between the points p and a . 最后,它检查线端点之间乘以商q的分量差是否等于点pa之间的分量差。

If so, then the point p is determined to be on the line between the endpoints a and b . 如果是这样,则确定点p在端点ab之间的线上。

A more performant solution. 更高效的解决方案。

Here is another way to find if a point is near a line. 这是查找点是否在直线附近的另一种方法。

Finding if a point is on a line can not be done with any reliability as floating point numbers have a limited precision and the error this introduces means that the ideal result calcs === 0 will fail many times with even the smallest of errors 1e-15 (smaller than an atom) so we call the function isPointNearLine not isPointOnLine 由于浮点数的精度有限,因此无法可靠地确定点是否在直线上,并且由此引入的错误意味着理想结果calcs === 0将失败很多次,即使最小的错误为1e- 15(小于原子),因此我们将函数isPointNearLine而不是isPointOnLine

function isPointNearLine(a,b,p){
    const v1 = { x : b.x - a.x, y : b.y - a.y };
    const l2 = v1.x * v1.x + v1.y * v1.y;
    if(l2=== 0){ return false } // line has no length so can't be near anything
    const v2 = { x : p.x - a.x, y : p.y - a.y };
    const u = (v1.x * v2.x + v1.y * v2.y) / l2;
    return u >= 0 && u <= 1 && Math.abs((v1.x * v2.y - v1.y * v2.x) / Math.sqrt(l2)) < 1;
}    

Performance 性能

As a games programmer performance is always the most important part of any algorithm. 作为游戏,程序员的性能始终是任何算法中最重要的部分。 The above function is a generic good performance solution. 上面的功能是一种通用的性能良好的解决方案。 Flat out on Firefox the function can compute 28Million solutions a second. 在Firefox上完全可以使用,该功能每秒可以计算2800万个解决方案。 When you compare that to Patrick Roberts answer it is significantly quicker. 当您将其与Patrick Roberts进行比较时,答案要快得多。 Patrick Robert's ES6 solution gets only 0.98Million solutions a second. Patrick Robert的ES6解决方案每秒仅获得98万个解决方案。

But to be fair his solution is not written with performance in mind. 但是公平地说,他的解决方案在编写时并没有考虑到性能。 With a quick rewrite 快速重写

// don't use destructuring as it is presently very very slow
function pointOnLine(a, b, p) {
  const lx = b.x - a.x
  const ly = b.y - a.y
  const dx = p.x - a.x
  const dy = p.y - a.y
  const l = Math.sqrt(lx * lx + ly * ly) // don't use hypot as it is 5 times slower 
  const d = Math.sqrt(dx * dx + dy * dy) // than using sqrt and the 2 multiplications and one addition
  const q = d / l
  return d <= l && Math.abs(q * lx - dx) < 0.5 && Math.abs(q * ly - dy) < 0.5
}

and you get a > 2400% performance increase at 24Million solutions a second. 每秒处理2400万个解决方案,您的性能提高> 2400%。

Though now his function has an advantage as he uses Numbers rather than Objects to store the intermediate results and his code is ignoring the zero length line (I always consider that line length is a higher level functionality and should never be a problem at low level functions) So a rewrite of my code 尽管现在他的函数具有优势,因为他使用Numbers而不是Objects来存储中间结果,并且他的代码忽略了零长度的行(我一直认为行长度是更高级别的功能,在低级别功能上绝对不成问题)所以重写了我的代码

function isPointNearLine(a,b,p){
    const lx =  b.x - a.x;
    const ly =  b.y - a.y;
    const l2 = lx * lx + ly * ly;
    const dx = p.x - a.x;
    const dy = p.y - a.y;
    const u = (lx * dx + ly * dy) / l2;
    return u >= 0 && u <= 1 && Math.abs((lx * dy - ly * dx) / Math.sqrt(l2)) < 1;
}  

Now at 35.7Million solutions a second. 现在每秒处理3570万个解决方案。

The main reason that my function is so much quicker than his is that he has 2 extra function calls in his code. 我的函数比他的函数快得多的主要原因是,他的代码中有2个额外的函数调用。 His function requires at least 2 calls to Math.sqrt and at max 2 sqrt and 2 abs calls. 他的函数需要至少2次调用Math.sqrt ,最大2 sqrt和2次abs调用。 My function can find a solution without any function calls and at worst a abs and sqrt 我的函数可以找到没有任何函数调用的解决方案,最糟糕的是abssqrt

Though my function may look like it is doing more avoiding the function calls is worth the extra operations. 尽管我的函数看起来像在做更多的事情,但避免函数调用值得进行额外的操作。

Benchmarks use 3 random points 基准使用3个随机点

A.x = Math.random() * 1000 - 500;
A.y = Math.random() * 1000 - 500;
B.x = Math.random() * 1000 - 500;
B.y = Math.random() * 1000 - 500;
P.x = Math.random() * 1000 - 500;
P.y = Math.random() * 1000 - 500; 

Only the function is timed 只有功能是定时的

// time start
isPointNearLine(A,B,P);
// time end

Run on a 在一个

  • OS Win10 32Bit, 操作系统 Win10 32Bit,
  • Browser Firefox 55.0b9 (32-bit), 浏览器 Firefox 55.0b9(32位),
  • Hardware i7 Q720 @1.6Ghz laptop. 硬件 i7 Q720 @ 1.6Ghz笔记本电脑。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM