如何测试是一条线与某一点发生碰撞

时间:2017-07-20 19:39:45

标签: javascript html5-canvas

所以,我有一个HTML画布,我可以在上面绘制东西。

我想做的是制作一个javascript函数来告诉我一条线是否与某个点发生碰撞。

我见过算法来测试一条线是否与另一条线发生碰撞,如下所示:

var IsIntersecting = function(Point a, Point b, Point c, Point d)
{
    var denominator = ((b.X - a.X) * (d.Y - c.Y)) - ((b.Y - a.Y) * (d.X - c.X));
    var numerator1 = ((a.Y - c.Y) * (d.X - c.X)) - ((a.X - c.X) * (d.Y - c.Y));
    var numerator2 = ((a.Y - c.Y) * (b.X - a.X)) - ((a.X - c.X) * (b.Y - a.Y));

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

但我想知道它是否与特定点发生碰撞。我还没有找到一个好的算法来做到这一点。你能救我吗?

2 个答案:

答案 0 :(得分:1)

用法

ab是指向p之间的行的端点。

假设,这应该有效:

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
}

但是,由于浮点错误,并且考虑到您可能正在使用像素坐标,我认为这样就可以使用.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改进

这是一种类似的方法,可以解决Math.sqrt()为绝对值大于Infinity的组件返回Math.sqrt(Number.MAX_VALUE)时的边缘问题。它使用一种名为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
}

如何运作

此方法获取行lxly的端点之间的组件差异,然后计算行l的长度。

然后获取行pa的测试点dx和点dy之间的组件差异,并计算这些点之间的距离{{1 }}

之后,它计算d的商q,并且测试首先检查距离d / l是否小于d长度,以确保它可能是l指向该行的端点之间。最后,它检查线端点之间的组件差异乘以商q是否等于点pa之间的组件差异。

如果是,则确定点p位于端点ab之间的线上。

答案 1 :(得分:1)

更高效的解决方案。

这是另一种查找点是否在线附近的方法。

由于浮点数具有有限的精度,因此找出一个点是否在一条线上是不可能完成的,并且这引入的误差意味着理想结果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;
}    

性能

作为游戏程序员,性能始终是任何算法中最重要的部分。上述功能是一种通用的性能解决方案。在Firefox上平稳,该功能可以每秒计算28万亿个解决方案。当你将它与Patrick Roberts进行比较时,答案明显更快。 Patrick Robert的ES6解决方案每秒只能获得0.98亿个解决方案。

但公平地说,他的解决方案并没有考虑到绩效。快速重写

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

你得到一个&gt;每秒24万亿个解决方案的性能提升2400%。

虽然现在他的功能有一个优点,因为他使用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;
}  

现在每秒处理35.7万个解决方案。

我的功能比他快得多的主要原因是他的代码中有2个额外的函数调用。他的功能需要至少拨打2次Math.sqrt,最多2次sqrt和2次abs来电。我的函数可以找到没有任何函数调用的解决方案,最坏的情况是abssqrt

虽然我的功能可能看起来更多,但是避免函数调用值得额外的操作。

基准使用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; 

仅定时功能

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

上运行
  • 操作系统 Win10 32Bit,
  • 浏览器 Firefox 55.0b9(32位),
  • 硬件 i7 Q720 @ 1.6Ghz笔记本电脑。