两个向量之间最短旋转的方向

7

我的问题涉及在2D中计算两个向量之间最小角度的方向。我正在使用C++制作一个游戏,其中障碍物之一是热导弹发射器。我已经通过计算目标和子弹之间的向量、对向量进行归一化,然后将其乘以速度来使其工作。但是,现在我回到这个类来改进它。我希望它只有在子弹向量与向量bulletloc->target之间的某个角度内时才锁定玩家,否则我希望它缓慢地向目标旋转一定的角度,从而给玩家足够的空间来避免它。我已经在vb.net项目中完成了所有这些操作(以便简化问题、解决问题,然后重新编写C++代码)。然而,即使最快的路线是逆时针方向,子弹始终顺时针旋转到目标。因此,问题在于找出应用旋转的方向,以覆盖最小角度。以下是我的代码,您可以尝试并查看我所描述的内容:

    Function Rotate(ByVal a As Double, ByVal tp As Point, ByVal cp As Point, ByVal cv As Point)
    'params a = angle, tp = target point, cp = current point, cv = current vector of bullet'
    Dim dir As RotDir 'direction to turn in'
    Dim tv As Point 'target vector cp->tp'
    Dim d As Point 'destination point (d) = cp + vector'
    Dim normal As Point
    Dim x1 As Double
    Dim y1 As Double
    Dim VeritcleResolution As Integer = 600

    tp.Y = VeritcleResolution - tp.Y 'modify y parts to exist in plane with origin (0,0) in bottom left'
    cp.Y = VeritcleResolution - cp.Y
    cv.Y = cv.Y * -1

    tv.X = tp.X - cp.X 'work out cp -> tp'
    tv.Y = tp.Y - cp.Y

    'calculate angle between vertor to target and vecrot currntly engaed on'
    Dim tempx As Double
    Dim tempy As Double

    tempx = cv.X * tv.X
    tempy = cv.Y * tv.Y

    Dim DotProduct As Double

    DotProduct = tempx + tempy 'dot product of cp-> d and cp -> tp'

    Dim magCV As Double 'magnitude of current vector'
    Dim magTV As Double 'magnitude of target vector'

    magCV = Math.Sqrt(Math.Pow(cv.X, 2) + Math.Pow(cv.Y, 2))
    magTV = Math.Sqrt(Math.Pow(tv.X, 2) + Math.Pow(tv.Y, 2))

    Dim VectorAngle As Double

    VectorAngle = Acos(DotProduct / (magCV * magTV))
    VectorAngle = VectorAngle * 180 / PI 'angle between cp->d and cp->tp'

    If VectorAngle < a Then 'if the angle is small enough translate directly towards target'
        cv = New Point(tp.X - cp.X, tp.Y - cp.Y)
        magCV = Math.Sqrt((cv.X ^ 2) + (cv.Y ^ 2))

        If magCV = 0 Then
            x1 = 0
            y1 = 0
        Else
            x1 = cv.X / magCV
            y1 = cv.Y / magCV
        End If

        normal = New Point(x1 * 35, y1 * 35)
        normal.Y = normal.Y * -1

        cv = normal
    ElseIf VectorAngle > a Then 'otherwise smootly translate towards the target'
        Dim x As Single
        d = New Point(cp.X + cv.X, cp.Y + cv.Y)


        a = (a * -1) * PI / 180 'THIS LINE CONTROL DIRECTION a = (a*-1) * PI / 180 would make the rotation counter clockwise'

        'rotate the point'
        d.X -= cp.X
        d.Y -= cp.Y

        d.X = (d.X * Cos(a)) - (d.Y * Sin(a))
        d.Y = (d.X * Sin(a)) + (d.Y * Cos(a))

        d.X += cp.X
        d.Y += cp.Y

        cv.X = d.X - cp.X
        cv.Y = d.Y - cp.Y

        cv.Y = cv.Y * -1
    End If

    Return cv

End Function

我想到的一个办法是计算两个向量之间的方位角,如果差值大于180度,则顺时针旋转,否则逆时针旋转。希望这个想法能帮到您。另外,我要补充说这个网站非常有帮助。我经常借鉴他人的问题来解决自己的问题,所以我想借此机会说声谢谢。

顺便提一下:如果你编写(或使用别人的)向量结构来封装这些内容,你的代码将会更易于阅读、修改和维护。 - Cameron
是的,我使用点来表示向量,我用VB编写它来尝试解决工作中的问题。我的主要项目有一个向量类 :) - David Kimbrey
2个回答

12

根据您在代码中编写的内容,两个(标准化的)向量之间的角度是它们点积的反余弦。

要获得一个带符号的角度,您可以使用第三个向量来表示其他两个向量所在平面的法线——在您的2D情况下,这将是一个指向“上方”的3D向量,例如(0,0,1)。

然后,取第一个向量(您想要相对于该向量的角度)与第二个向量的叉积(请注意,叉积不可交换)。角度的符号应与生成的向量和平面法线之间的点积的符号相同。

以下是代码(C#,抱歉)- 请注意,假定所有向量都已归一化:

public static double AngleTo(this Vector3 source, Vector3 dest)
{
    if (source == dest) {
        return 0;
    }
    double dot; Vector3.Dot(ref source, ref dest, out dot);
    return Math.Acos(dot);
}

public static double SignedAngleTo(this Vector3 source, Vector3 dest, Vector3 planeNormal)
{
    var angle = source.AngleTo(dest);
    Vector3 cross; Vector3.Cross(ref source, ref dest, out cross);
    double dot; Vector3.Dot(ref cross, ref planeNormal, out dot);
    return dot < 0 ? -angle : angle;
}

这个方法利用了两个向量的叉积总是得到一个垂直(法向)于这两个向量所在平面的第三个向量的事实(因此它本质上是一个三维操作)。a x b = -(b x a),所以该向量始终垂直于该平面,但取决于ab之间的(带符号的)角度,位于不同的一侧(有一种称为right-hand rule的东西)。
因此,叉积给我们一个有符号的垂直于平面的向量,当向量之间的夹角超过180°时,其方向会改变。如果我们预先知道一个垂直于平面且“指向正上方”的向量,则可以通过检查它们的点积的符号来确定叉积是否与该平面法线方向相同。

1
为了确定我理解你所说的内容是否正确,考虑以下两个向量V = -2x,4y,0z和U = 2x,y4,0z。要计算V和U之间的夹角符号(其中V是起点,U是终点,意味着答案应该是顺时针),我应该通过确定矩阵{(x,y,z),(-2,4,0),(4,2,0)}来取两个向量的叉积,其中x和y分量变为零,z分量=(-22)-(44)=-20,然后得到该向量(0x,0y,-20z)与单位向量(0,0,1)的点积=-20。 - David Kimbrey
由于符号为负,角度的符号也应为负吗?如果是的话,我一定做错了什么(矩阵构建方式错误?) - David Kimbrey
1
@David:你可以使用这个叉积计算器来帮助检查你的数学。你的例子做得很正确;点积是负数,最终角度也应该是负数。之所以不是你期望的结果,是因为你认为顺时针角度为正是错误的(或者说,违反了惯例)。想象一下单位圆——对应于角度0的点在圆的最右侧,随着角度的增加,该点向逆时针移动。 - Cameron
@David:如果你想要一个顺时针递增的角度约定,只需取反结果,或者使用相反的平面法线 (0, 0, -1)。不过这可能会让其他人阅读你的代码感到困惑(如果我自己这样做,我会添加注释)。这有帮助吗? :-) - Cameron
@David:嘿,没问题!很高兴能帮到你。而且你的百分比想法听起来不错! - Cameron
显示剩余2条评论

0

根据@Cameron的回答,这是我用的Python翻译:
作为奖励,我添加了signed_angle_between_headings函数,直接返回两个参考北向航向之间最快旋转角度。

    import math
    import numpy as np
    
    
    def angle_between_vectors(source, dest):
        if np.array_equal(source, dest):
            return 0
        dot = np.dot(source, dest)
        return np.arccos(dot)
    
    def signed_angle_from_to_vectors(source, dest, plane_normal):
        angle = angle_between_vectors(source, dest)
        cross = np.cross(source, dest)
        dot = np.dot(cross, plane_normal)
        return -angle if dot < 0 else angle
    
    def signed_angle_between_headings(source_heading, destination_heading):
        if source_heading == destination_heading:
            return 0
        RAD2DEGFACTOR = 180 / math.pi
    
        source_heading_rad = source_heading / RAD2DEGFACTOR
        dest_heading_rad = destination_heading / RAD2DEGFACTOR
        source_vector = np.array([np.cos(source_heading_rad), np.sin(source_heading_rad), 0])
        dest_vector = np.array([np.cos(dest_heading_rad), np.sin(dest_heading_rad), 0])
    
        signed_angle_rad = signed_angle_from_to_vectors(source_vector, dest_vector, np.array([0,0,1]))
    
        return signed_angle_rad * RAD2DEGFACTOR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接