如何确保 Math.acos 获得 [-1..1] 范围内的值
How to ensure Math.acos gets value in range [-1..1]
计算 2 个向量之间的角度必须使用 Math.acos
(编辑:事实证明不必使用 Math.acos
因为有 Math.atan2
-way),它只接受 [-1..1]
范围内的值。但是如果 vector1.x==vector2.x
和 vector1.y==vector2.y
并且由于 JS 的性质导致 0.1+0.2>0.3
有时 Math.acos
得到一些东西 >1
并且,毫不奇怪,returns NaN
.
我在所有计算之前通过 if-check 解决了它 if (v1.x==v2.x&&v1.y==v2.y)
我只是 return 0
还有 if (v1.x==-v2.x&&v1.y==-v2.y) return Math.PI
.
我是那样做的(EDIT 在接受的答案中有更好的版本):
function angle(origin, p1, p2, sign=false){
if (p1.x==p2.x && p1.y==p2.y) return 0
if (p1.x==-p2.x && p1.y==-p2.y) return Math.PI
const a = {x: p1.x-origin.x, y: p1.y-origin.y}
const b = {x: p2.x-origin.x, y: p2.y-origin.y}
sign = sign && a.x*b.y < a.y*b.x ? -1 : 1
return sign * Math.acos(
(a.x*b.x+a.y*b.y)/(Math.sqrt(a.x**2+a.y**2)*Math.sqrt(b.x**2+b.y**2))
)
}
它有效,但是如果你做一些动态图形,是否有比以 ~60fps 进行一堆中等复杂逻辑检查更好、更有效的方法?
不要使用 acos,请使用 atan2。要使 acos 起作用,您必须对差异进行归一化处理,并以不同方式处理 angle>pi 的情况。使用 atan2,您只需提供 y、x 坐标的差值,其余的都会得到正确处理。
编辑
你想要向量之间的角度,而不是差向量的参数,我的错。我们只需要改变您处理计算错误的方式。使用一些伪代码:
function angle_between(ax, ay, bx, by) {
var al = ax*ax+ay*ay;
var bl = bx*bx+by*by;
var dot = (ax*bx+ay*by)/Math.sqrt(al2*bl2);
if (dot >= 1) return 0;
if (dot <= -1) return Math.PI;
return Math.acos(dot);
}
EDIT2
好吧,我们也来看看atan2的解决方案。正如@njuffa 指出的那样,atan2 仍然可以用来计算两个向量之间的角度。少一个平方根,这很好。它还为我们提供了一个带符号的角度,这对于某些应用程序来说甚至更好。
function signed_angle_between(ax, ay, bx, by) {
var dot = ax*bx + ay*by;
var cross = ax*by - ay*bx;
return Math.atan2(cross, dot);
}
console.log(signed_angle_between(3, 4, 30, 40));
console.log(signed_angle_between(2, 5, -50, 20));
console.log(signed_angle_between(2, 5, 50, -20));
console.log(signed_angle_between(1, 1, -1, -1));
基于acos
的计算不仅存在由舍入误差触发的虚假NaN问题导致其参数超过单位,而且还存在接近0和接近π的结果的数值问题,导致结果不准确。
避免这两个问题的更好方法是基于atan2
:角度(a, b) = atan2 (| a × b |, a · b)。我不知道 Javascript,但希望以下实现此功能的 ISO-C 代码能够一对一地转换为 Javascript:
double angle (double ax, double ay, double bx, double by)
{
double dot = ax * bx + ay * by;
double norm_cross = fabs (ax * by - ay * bx);
return atan2 (norm_cross, dot);
}
根据我的经验,使用 atan2
的计算应该与通过 acos
的计算具有大致相同的性能,但当然这取决于所使用的数学库的细节。
计算 2 个向量之间的角度必须使用 Math.acos
(编辑:事实证明不必使用 Math.acos
因为有 Math.atan2
-way),它只接受 [-1..1]
范围内的值。但是如果 vector1.x==vector2.x
和 vector1.y==vector2.y
并且由于 JS 的性质导致 0.1+0.2>0.3
有时 Math.acos
得到一些东西 >1
并且,毫不奇怪,returns NaN
.
我在所有计算之前通过 if-check 解决了它 if (v1.x==v2.x&&v1.y==v2.y)
我只是 return 0
还有 if (v1.x==-v2.x&&v1.y==-v2.y) return Math.PI
.
我是那样做的(EDIT 在接受的答案中有更好的版本):
function angle(origin, p1, p2, sign=false){
if (p1.x==p2.x && p1.y==p2.y) return 0
if (p1.x==-p2.x && p1.y==-p2.y) return Math.PI
const a = {x: p1.x-origin.x, y: p1.y-origin.y}
const b = {x: p2.x-origin.x, y: p2.y-origin.y}
sign = sign && a.x*b.y < a.y*b.x ? -1 : 1
return sign * Math.acos(
(a.x*b.x+a.y*b.y)/(Math.sqrt(a.x**2+a.y**2)*Math.sqrt(b.x**2+b.y**2))
)
}
它有效,但是如果你做一些动态图形,是否有比以 ~60fps 进行一堆中等复杂逻辑检查更好、更有效的方法?
不要使用 acos,请使用 atan2。要使 acos 起作用,您必须对差异进行归一化处理,并以不同方式处理 angle>pi 的情况。使用 atan2,您只需提供 y、x 坐标的差值,其余的都会得到正确处理。
编辑
你想要向量之间的角度,而不是差向量的参数,我的错。我们只需要改变您处理计算错误的方式。使用一些伪代码:
function angle_between(ax, ay, bx, by) {
var al = ax*ax+ay*ay;
var bl = bx*bx+by*by;
var dot = (ax*bx+ay*by)/Math.sqrt(al2*bl2);
if (dot >= 1) return 0;
if (dot <= -1) return Math.PI;
return Math.acos(dot);
}
EDIT2
好吧,我们也来看看atan2的解决方案。正如@njuffa 指出的那样,atan2 仍然可以用来计算两个向量之间的角度。少一个平方根,这很好。它还为我们提供了一个带符号的角度,这对于某些应用程序来说甚至更好。
function signed_angle_between(ax, ay, bx, by) {
var dot = ax*bx + ay*by;
var cross = ax*by - ay*bx;
return Math.atan2(cross, dot);
}
console.log(signed_angle_between(3, 4, 30, 40));
console.log(signed_angle_between(2, 5, -50, 20));
console.log(signed_angle_between(2, 5, 50, -20));
console.log(signed_angle_between(1, 1, -1, -1));
基于acos
的计算不仅存在由舍入误差触发的虚假NaN问题导致其参数超过单位,而且还存在接近0和接近π的结果的数值问题,导致结果不准确。
避免这两个问题的更好方法是基于atan2
:角度(a, b) = atan2 (| a × b |, a · b)。我不知道 Javascript,但希望以下实现此功能的 ISO-C 代码能够一对一地转换为 Javascript:
double angle (double ax, double ay, double bx, double by)
{
double dot = ax * bx + ay * by;
double norm_cross = fabs (ax * by - ay * bx);
return atan2 (norm_cross, dot);
}
根据我的经验,使用 atan2
的计算应该与通过 acos
的计算具有大致相同的性能,但当然这取决于所使用的数学库的细节。