前言:本答案假定读者具有一定的线性代数、三角学和旋转/变换知识。
你的问题源于旋转角度的使用。由于反三角函数的不连续性质,相对接近的输入值很难(甚至根本不可能)消除函数值的“跳跃”。具体而言,当
x < 0
时,
atan2(+0, x) = +pi
(其中
+0
是非常接近零的正数),但是
atan2(-0, x) = -pi
。这正是你遇到的导致问题的
2 * pi
差异的原因。
因此,通常最好直接使用向量、旋转矩阵和/或四元数。它们使用角度作为三角函数的参数,这些函数是连续的并消除了任何不连续性。在我们的情况下,
球面线性插值(slerp) 应该能解决问题。
由于您的代码测量了鼠标相对位置与物体绝对旋转形成的角度,我们的目标是将物体旋转,使得
本地 轴
(1, 0)
(
= (cos rotateRad, sin rotateRad)
在
世界 空间中) 指向鼠标。实际上,我们必须旋转物体,以便
(cos p.rotateRad, sin p.rotateRad)
等于
(win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized
。
在这里 slerp 是如何发挥作用的?考虑到上述语句,我们只需通过适当的参数从
(cos p.rotateRad, sin p.rotateRad)
(由
current
表示)到
(win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized
(由
target
表示) 几何上执行
slerp 即可,该参数将由旋转速度确定。
既然我们已经打好了基础,我们可以继续计算新的旋转角度。根据slerp公式,
slerp(p0, p1; t) = p0 * sin(A * (1-t)) / sin A + p1 * sin (A * t) / sin A
其中A
是单位向量p0
和p1
之间的夹角,即cos A = dot(p0, p1)
。
在我们的情况下,p0 == current
且p1 == target
。唯一剩下的就是计算参数t
,它也可以被视为通过slerp旋转角度的分数。由于我们知道每个时间步长都会旋转一个角度p.turnSpeed * dt
,t = p.turnSpeed * dt / A
。替换t
的值后,我们的slerp公式变为
p0 * sin(A - p.turnSpeed * dt) / sin A + p1 * sin (p.turnSpeed * dt) / sin A
为了避免使用acos计算A,我们可以使用sin的复合角公式进一步简化。请注意,slerp操作的结果存储在result中。
result = p0 * (cos(p.turnSpeed * dt) - sin(p.turnSpeed * dt) * cos A / sin A) + p1 * sin(p.turnSpeed * dt) / sin A
我们现在有计算
result
所需的一切。如前所述,
cos A = dot(p0, p1)
。同样,
sin A = abs(cross(p0, p1))
,其中
cross(a, b) = a.X * b.Y - a.Y * b.X
。
现在问题实际上是找到从
result
旋转的方法。请注意,
result = (cos newRotation, sin newRotation)
。有两种可能性:
- Directly calculate
rotateRad
by p.rotateRad = atan2(result.Y, result.X)
, or
If you have access to the 2D rotation matrix, simply replace the rotation matrix with the matrix
|result.X -result.Y|
|result.Y result.X|