测试一个点是否大约在由另外两个点形成的线段上

Test if a point is approximately on a line segment formed by two other points

我想确定是否在点之间以及在连接这两个点的线段上进行了点击。

我的问题与How can you determine a point is between two other points on a line segment?类似,但有两点不同:

  1. 我在 javascript 工作,坐标是整数(像素)
  2. 该点不必正好在线段上。需要一个宽容。

以下代码改编自this answer,但我不知道如何在线段周围插入公差

ab是线段的端点,c是点击的点。我想检查 c 是否在 ab 之间以及大致在连接 ab

的线段上
PencilTool.prototype._isBetween = function(a,b,c){
    //test if a, b and c are aligned
    var crossproduct = (c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y);
    if(Math.abs(crossproduct) !== 0){ return false; }

    var dotproduct = (c.x - a.x) * (b.x - a.x) + (c.y - a.y)*(b.y - a.y)
    if(dotproduct < 0){ return false }

    var squaredlengthba = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y);
    if(dotproduct > squaredlengthba){ return false; }

    return true;
}

大概就是你想要的公式。

这是此公式的 link 维基百科页面。

此来源非常详尽,但可能不是最容易阅读的。您可以阅读 wiki 上的解释,但我将在这里以另一种方式进行解释,希望能帮助您和其他读者形象化地理解它。

x0 和 y0 是您点击点的坐标。 x1 和 y1 是第一条线端点的坐标。 x2 和 y2 是同一直线上第二个端点的坐标。

该公式以三个点的三组坐标为参数。
前两组坐标构成直线
第三个参数是您的点击点。 它returns一段距离。

好的,现在让我们试着想象一下这个公式在做什么。 所以你拿点击点和直线的两个端点,你想象一个三角形。我们得到了三个点,这就是我们需要的三角形。

所以,要找到三角形的高度,您有一个公式,它是熟悉的 A = (1/2)bh

的重新排列

所以当你找到这样的三角形的高度时,你就是在找到点击点和直线之间的距离。 (准确的说是点击点到直线的最短距离)

上面更大的距离公式,基本上就是这样做的。这里的区别,以及为什么它看起来更复杂,是明确显示了计算 A 的部分。

关于你提到的公差,只需设置一个公差变量,然后将距离与该公差进行比较。如果您想要更多 "fuzzy" 在线附近的点击容差,您需要做更多的数学计算,但我假设您只想知道点击是否与线有一定距离。

当你写这个函数的时候,一定要好好记账,在正确的地方设置正确的坐标,否则你会返回一段距离,而不是你想要的。 既然你提到你使用的是整数,你可能无法从距离公式中得到一个完美的整数,我的意思是,看看那个平方根,所以如果你没有得到一个完美的整数,不用担心,只需四舍五入或向下,

您可以在两点之间叠加 div,根据需要旋转,并根据需要加厚。将 hover CSS 或单击事件附加到 div。

使用距离公式确定div的宽度:

使用此公式确定要旋转的角度:

Math.atan2((y1-y2),(x1-x2))*(180/Math.PI)

为简单起见,我在我的代码片段中使用了 jQuery,但我可以在原版中快速重写 JavaScript:

var x1= 10+Math.random()*500/4,
    y1= 10+Math.random()*300/4,
    x2= Math.random()*500/2 + 500/4,
    y2= Math.random()*300/2 + 300/4,
    pt= $('.a').width()/2,
    dx= (x2-x1),
    dy= (y2-y1),
    angle= Math.atan2((y1-y2),(x1-x2))*(180/Math.PI),
    tolerance= 30;

$('.a').css({left: x1, top : y1});
$('.b').css({left: x2, top : y2});

$('div.c').css({
  width: Math.sqrt(dx*dx + dy*dy),
  height: tolerance,
  left: x2+pt,
  top: y2+pt,
  transformOrigin: '0px '+tolerance/4+'px',
  transform: 'rotate('+angle+'deg)'
});

$('div.c')
  .click(function() {
    alert('clicked!');
  });
body {
  margin: 0;
  padding: 0;
}

div.a, div.b, div.c {
  position: absolute;
  border-radius: 50%;
  height: 1.2em;
  width: 1.2em;
  text-align: center;
  background: orange;
}

div.c {
  border: 1px solid #eee;
  background: transparent;
  border-radius: 0;
}

div.c:hover ~ div.a, div.c:hover ~ div.b {
  background: lightgreen;
}

div.c hr {
  position: relative;
  top: 50%;
  transform: translateY(-50%);
  height: 1.2em;
  border: none;
  border-top: 1px solid orange;
}

div.c:hover hr {
  border-top: 1px solid green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="c"><hr></div>
<div class="a">A</div>
<div class="b">B</div>

这是我现在使用的最终 javascript 函数。

第一部分使用 中解释的距离公式,我们首先测试 c 是否在经过的直线的预定义距离 (tolerance) 内ab.

第二部分摘自Cyrille Ka' answer from a similar question

Then, to know if c is between a and b, you also have to check that the dot product of (b-a) and (c-a) is positive and is less than the square of the distance between a and b.

// a and b are vertices of the segment AB and c is the tested point (here from a mouseclick event (e))
var a={}, b={}, c={};
a.x = 100;
a.y = 100;
b.x = 200;
b.y = 200;
c.x = e.screenX
c.y = e.screeny

console.log(isBetween(a, b, c, 5));

function isBetween(a, b, c, tolerance) {

    //test if the point c is inside a pre-defined distance (tolerance) from the line
    var distance = Math.abs((c.y - b.y)*a.x - (c.x - b.x)*a.y + c.x*b.y - c.y*b.x) / Math.sqrt(Math.pow((c.y-b.y),2) + Math.pow((c.x-b.x),2));
    if (distance > tolerance) { return false; }

    //test if the point c is between a and b
    var dotproduct = (c.x - a.x) * (b.x - a.x) + (c.y - a.y)*(b.y - a.y);
    if (dotproduct < 0) { return false; }

    var squaredlengthba = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y);
    if (dotproduct > squaredlengthba) { return false; }

    return true;
};