Направление кратчайшего вращения между двумя векторами
Мой вопрос касается определения направления наименьшего угла между двумя векторами в 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 градусов, повернуть по часовой стрелке или повернуть против часовой стрелки, любые идеи будут полезны. Благодарю.
РЕДАКТИРОВАТЬ: Я хотел бы добавить, что этот сайт очень полезен. Я часто использую вопросы, поставленные другими, чтобы решить свои собственные проблемы, и я хочу воспользоваться возможностью, чтобы сказать спасибо.
1 ответ
Как вы написали в своем коде, угол между двумя (нормализованными) векторами является обратным косинусом их точечного произведения.
Чтобы получить угол со знаком, вы можете использовать третий вектор, представляющий нормаль плоскости, на которой лежат два других вектора - в вашем 2D-случае это будет трехмерный вектор, указывающий прямо "вверх", скажем, (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)
, поэтому вектор всегда будет перпендикулярен плоскости, но с другой стороны, в зависимости от угла (со знаком) между a
а также b
(есть нечто, называемое правилом правой руки).
Таким образом, перекрестное произведение дает нам вектор со знаком, перпендикулярный плоскости, которая меняет направление, когда угол между векторами проходит 180°. Если мы заранее знаем вектор, перпендикулярный плоскости, которая направлена прямо вверх, то мы можем определить, находится ли перекрестное произведение в том же направлении, что и нормаль плоскости, или нет, проверив знак их точечного произведения.
Основываясь на ответе @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