检测鼠标是否在一条线上 python
Detecting whether mouse is over a line python
对于我的 A-Level 项目,我正在创建一个 dijktras 算法程序。我创建了节点、文本框、按钮等,所有这些都具有正确的悬停检测(使用鼠标),但是,我尝试使用 arctan 为 arcs/lines/connectors 创建悬停检测;通过获得两个连接位置的 y 与 x 的差异(与我们所做的相反),然后从一个位置(点 a)到鼠标位置并进行比较。但是,距离 a 点越近,差异似乎总是越大,因此根据我设置的限制,它很容易检测到比点 b 更靠近点 a 的悬停(特别是当 y 的差异与 x 的差异非常小时。这会产生一些错误,因为我希望程序在整条线上均匀地检测到它。有没有更好的方法可以进行线悬停检查?
另一种方法是根据直线制作边界框。只需将直线偏移一些固定的垂直距离即可。也许您已经有了检测鼠标是否在任意矩形内的逻辑,但如果没有,请参见此处:Finding whether a point lies inside a rectangle or not
计算与直线的距离。
对于直线(来自维基百科 https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line)点 (x0,y0) 与直线 (x1,1)->(x2,y2) 的距离为:
还有其他公式可以计算点到直线的距离,但这个公式很方便,而且对 horizontal/vertical 条线没有特殊情况。
对于圆弧,使用((从鼠标到圆心的距离)-(圆的半径))。
这些都会为您提供一致且独立于鼠标位置的度量,因此您可以将鼠标悬停的阈值定义为某个相对较小的值。您应该处理鼠标在多个 line/arc 范围内的特殊情况 - 没有什么比 UI 在这些情况下随机选择一个更糟糕的了,因为用户总是想选择一个不是' t 随机选择。
更新:再搜索一下,我发现这个 Shortest distance between a point and a line segment - woolie 的 python 答案看起来简洁明了。这计算了通过点 v 和 w 的线的交点,v->w 与通过点 p 的垂线的交点根据值 t 沿着从 v 到 w 的线在 0 和 1 之间变化 - 所以它是很容易确定 p 处的鼠标光标位于线段 v->w 上,因为在这种情况下 0.0<=t<=1.0 或超出线段 v->w 的任一端,因为在这种情况下 t < 0.0 或 t > 1.0
HTH
巴尼
我喜欢巴尼关于使用线点距离的建议,但我假设当你说 "line" 时你的意思是 "line segment"。在这种情况下,您不能只使用线距公式,因为它可能会将鼠标位置投影到与线段共线的点上,但实际上并不位于端点之间。
这是解决这个问题的替代实现。
import math
def magnitude(A):
return math.sqrt(A.x**2 + A.y**2)
#https://en.wikipedia.org/wiki/Dot_product
def dot_product(A,B):
return A.x*B.x + A.y*B.y
#the family of vectors parallel to vector B is defined by
#V(f) = B*f
#where f is a scalar value. when f is between 0 and 1, the vector's length is between 0 and B's magnitude.
#this returns the f value of the vector projection of A onto B.
#https://en.wikipedia.org/wiki/Vector_projection
def scalar_projection_ratio(A,B):
length = dot_product(A,B) / magnitude(B)
return length / magnitude(B)
#finds the vector with the same angle and magnitude as line segment ab.
def vectorize(a,b):
return Vector(b.x - a.x, b.y - a.y)
def clamp(x, left, right):
if x < left: return left
if x > right: return right
return x
#finds the point lying between `a` and `b` which is closest to `p`.
def closest_point_to_line_segment(a,b,p):
B = vectorize(a,b)
P = vectorize(a,p)
f = scalar_projection_ratio(P, B)
#if f is less than 0 or greater than 1, the vector projection of P onto AB does not lie between A and B.
#so we must clamp it to that range.
f = clamp(f, 0, 1)
return Point(a.x + f*B.x, a.y + f*B.y)
def distance_to_line_segment(a,b,p):
t = closest_point_to_line_segment(a,b,p)
return magnitude(vectorize(t,p))
def is_over_line(a, b, p):
return distance_to_line_segment(a,b,p) < 20 #or whatever tolerance is appropriate
此代码假定您有 Vector
和 Point
类,它们只是 x 和 y 值的简单容器。不过,您可以使用元组或您喜欢的任何其他数据结构;它只需要一些名义上的改变。
为了验证我没有在上面的代码中输入任何错误,我用它制作了一个快速热图,显示每个像素到红线段的距离。
最暗的值代表最短的距离,因此这似乎证实了这种方法的有效性。
对于我的 A-Level 项目,我正在创建一个 dijktras 算法程序。我创建了节点、文本框、按钮等,所有这些都具有正确的悬停检测(使用鼠标),但是,我尝试使用 arctan 为 arcs/lines/connectors 创建悬停检测;通过获得两个连接位置的 y 与 x 的差异(与我们所做的相反),然后从一个位置(点 a)到鼠标位置并进行比较。但是,距离 a 点越近,差异似乎总是越大,因此根据我设置的限制,它很容易检测到比点 b 更靠近点 a 的悬停(特别是当 y 的差异与 x 的差异非常小时。这会产生一些错误,因为我希望程序在整条线上均匀地检测到它。有没有更好的方法可以进行线悬停检查?
另一种方法是根据直线制作边界框。只需将直线偏移一些固定的垂直距离即可。也许您已经有了检测鼠标是否在任意矩形内的逻辑,但如果没有,请参见此处:Finding whether a point lies inside a rectangle or not
计算与直线的距离。
对于直线(来自维基百科 https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line)点 (x0,y0) 与直线 (x1,1)->(x2,y2) 的距离为:
还有其他公式可以计算点到直线的距离,但这个公式很方便,而且对 horizontal/vertical 条线没有特殊情况。
对于圆弧,使用((从鼠标到圆心的距离)-(圆的半径))。
这些都会为您提供一致且独立于鼠标位置的度量,因此您可以将鼠标悬停的阈值定义为某个相对较小的值。您应该处理鼠标在多个 line/arc 范围内的特殊情况 - 没有什么比 UI 在这些情况下随机选择一个更糟糕的了,因为用户总是想选择一个不是' t 随机选择。
更新:再搜索一下,我发现这个 Shortest distance between a point and a line segment - woolie 的 python 答案看起来简洁明了。这计算了通过点 v 和 w 的线的交点,v->w 与通过点 p 的垂线的交点根据值 t 沿着从 v 到 w 的线在 0 和 1 之间变化 - 所以它是很容易确定 p 处的鼠标光标位于线段 v->w 上,因为在这种情况下 0.0<=t<=1.0 或超出线段 v->w 的任一端,因为在这种情况下 t < 0.0 或 t > 1.0
HTH 巴尼
我喜欢巴尼关于使用线点距离的建议,但我假设当你说 "line" 时你的意思是 "line segment"。在这种情况下,您不能只使用线距公式,因为它可能会将鼠标位置投影到与线段共线的点上,但实际上并不位于端点之间。
这是解决这个问题的替代实现。
import math
def magnitude(A):
return math.sqrt(A.x**2 + A.y**2)
#https://en.wikipedia.org/wiki/Dot_product
def dot_product(A,B):
return A.x*B.x + A.y*B.y
#the family of vectors parallel to vector B is defined by
#V(f) = B*f
#where f is a scalar value. when f is between 0 and 1, the vector's length is between 0 and B's magnitude.
#this returns the f value of the vector projection of A onto B.
#https://en.wikipedia.org/wiki/Vector_projection
def scalar_projection_ratio(A,B):
length = dot_product(A,B) / magnitude(B)
return length / magnitude(B)
#finds the vector with the same angle and magnitude as line segment ab.
def vectorize(a,b):
return Vector(b.x - a.x, b.y - a.y)
def clamp(x, left, right):
if x < left: return left
if x > right: return right
return x
#finds the point lying between `a` and `b` which is closest to `p`.
def closest_point_to_line_segment(a,b,p):
B = vectorize(a,b)
P = vectorize(a,p)
f = scalar_projection_ratio(P, B)
#if f is less than 0 or greater than 1, the vector projection of P onto AB does not lie between A and B.
#so we must clamp it to that range.
f = clamp(f, 0, 1)
return Point(a.x + f*B.x, a.y + f*B.y)
def distance_to_line_segment(a,b,p):
t = closest_point_to_line_segment(a,b,p)
return magnitude(vectorize(t,p))
def is_over_line(a, b, p):
return distance_to_line_segment(a,b,p) < 20 #or whatever tolerance is appropriate
此代码假定您有 Vector
和 Point
类,它们只是 x 和 y 值的简单容器。不过,您可以使用元组或您喜欢的任何其他数据结构;它只需要一些名义上的改变。
为了验证我没有在上面的代码中输入任何错误,我用它制作了一个快速热图,显示每个像素到红线段的距离。
最暗的值代表最短的距离,因此这似乎证实了这种方法的有效性。