Прямой способ вычисления угла по часовой стрелке между двумя векторами

Я хочу выяснить угол по часовой стрелке между 2 векторами (2D, 3D).

Классический способ с точечным произведением дает мне внутренний угол (0-180 градусов), и мне нужно использовать некоторые операторы if, чтобы определить, является ли результат нужным мне углом или его дополнением.

Вы знаете прямой способ вычисления угла по часовой стрелке?

54

Решение

Так же, как скалярное произведение пропорционально косинусу угла, то определитель пропорционально его синусу. Таким образом, вы можете вычислить угол следующим образом:

dot = x1*x2 + y1*y2      # dot product between [x1, y1] and [x2, y2]
det = x1*y2 - y1*x2      # determinant
angle = atan2(det, dot)  # atan2(y, x) or atan2(sin, cos)

Ориентация этого угла совпадает с ориентацией системы координат. В левосторонняя система координат, то есть Икс направо и Y вниз, как обычно для компьютерной графики, это будет означать, что вы получите положительный знак для углов по часовой стрелке. Если ориентация системы координат является математической с Y вверх, вы получаете против часовой стрелки, как это принято в математике. Изменение порядка входов изменит знак, поэтому, если вы недовольны этими знаками, просто поменяйте местами входы.

В 3D два произвольно расположенных вектора определяют свою ось вращения, перпендикулярную обоим. Эта ось вращения не имеет фиксированной ориентации, что означает, что вы также не можете однозначно определить направление угла поворота. Одно из общепринятых правил состоит в том, чтобы углы всегда были положительными и ориентировали ось таким образом, чтобы она соответствовала положительному углу. В этом случае произведение точек нормализованных векторов достаточно для вычисления углов.

dot = x1*x2 + y1*y2 + z1*z2    #between [x1, y1, z1] and [x2, y2, z2]
lenSq1 = x1*x1 + y1*y1 + z1*z1
lenSq2 = x2*x2 + y2*y2 + z2*z2
angle = acos(dot/sqrt(lenSq1 * lenSq2))

Один частный случай — это случай, когда ваши векторы не расположены произвольно, а лежат в плоскости с известным вектором нормалей. N. Тогда ось вращения будет в направлении N а также, и ориентация N установит ориентацию для этой оси. В этом случае вы можете адаптировать 2D вычисления выше, в том числе N в определитель сделать его размером 3 × 3.

dot = x1*x2 + y1*y2 + z1*z2
det = x1*y2*zn + x2*yn*z1 + xn*y1*z2 - z1*y2*xn - z2*yn*x1 - zn*y1*x2
angle = atan2(det, dot)

Одним из условий этого является то, что нормальный вектор N имеет длину блока. Если нет, вам придется нормализовать его.

Как тройной продукт

Этот детерминант также может быть выражен как тройной продукт, как @Excrubulent указал в предлагаемом редактировании.

det = n · (v1 × v2)

Это может быть проще для реализации в некоторых API и дает другое представление о том, что здесь происходит: перекрестное произведение пропорционально синусу угла и будет лежать перпендикулярно плоскости, следовательно, будет кратным N. Таким образом, скалярное произведение будет в основном измерять длину этого вектора, но с правильным знаком, прикрепленным к нему.

126

Другие решения

Для вычисления угла вам просто нужно позвонить atan2(v1.s_cross(v2), v1.dot(v2)) для 2D случая.
куда s_cross является скалярным аналогом перекрестного производства (подписанная область параллелограмма).
Для 2D случая это будет производство клина.
Для трехмерного случая вам нужно определить вращение по часовой стрелке, потому что с одной стороны плоскости по часовой стрелке — одно направление, с другой стороны плоскости — другое направление =)

Изменить: это против часовой стрелки угол, по часовой стрелке прямо напротив

5

Этот ответ такой же, как и у MvG, но объясняет его по-другому (это результат моих попыток понять, почему работает решение MvG). Я публикую это на случай, если другие найдут это полезным.

Угол против часовой стрелки theta от x в yпо отношению к точке зрения их данного нормального n (||n|| = 1), дан кем-то

atan2 (точка (n, крест (x, y)), точка (x, y))

(1) = atan2 (|| x || || y || sin (тета), || x || || y || cos (тета))

(2) = atan2 (грех (тета), соз (тета))

(3) = против часовой стрелки между осью x и вектором (cos (тета), sin (тета))

(4) = тета

где ||x|| обозначает величину x,

Шаг (1) следует, отметив, что

крест (х, у) = || х || || у || грех (тета) н,

так что

точка (n, крестик (x, y))

= точка (n, || x || || y || sin (theta) n)

= || х || || у || точка греха (тета) (n, n)

что равно

|| х || || у || грех (тета)

если ||n|| = 1,

Шаг (2) следует из определения atan2отмечая, что atan2(cy, cx) = atan2(y,x), где c это скаляр Шаг (3) следует из определения atan2, Шаг (4) следует из геометрических определений cos а также sin,

3

Скалярное (точечное) произведение двух векторов позволяет получить косинус угла между ними.
Чтобы получить «направление» угла, вы также должны рассчитать перекрестное произведение, оно позволит вам проверить (через координату z) угол по часовой стрелке или нет (т.е. вы должны извлечь его из 360 градусов или нет).

2

Для 2D-метода вы можете использовать закон
косинусы и метод «направления».

Для расчета угла сегмента P3: P1
подметание по часовой стрелке к сегменту P3: P2.

P1 P2

P3
    double d = direction(x3, y3, x2, y2, x1, y1);

// c
int d1d3 = distanceSqEucl(x1, y1, x3, y3);

// b
int d2d3 = distanceSqEucl(x2, y2, x3, y3);

// a
int d1d2 = distanceSqEucl(x1, y1, x2, y2);

//cosine A = (b^2 + c^2 - a^2)/2bc
double cosA = (d1d3 + d2d3 - d1d2)
/ (2 * Math.sqrt(d1d3 * d2d3));

double angleA = Math.acos(cosA);

if (d > 0) {
angleA = 2.*Math.PI - angleA;
}

This has the same number of transcendental

операции как предложения выше и только один
более или менее операция с плавающей запятой.

методы, которые он использует:

 public int distanceSqEucl(int x1, int y1,
int x2, int y2) {

int diffX = x1 - x2;
int diffY = y1 - y2;
return (diffX * diffX + diffY * diffY);
}

public int direction(int x1, int y1, int x2, int y2,
int x3, int y3) {

int d = ((x2 - x1)*(y3 - y1)) - ((y2 - y1)*(x3 - x1));

return d;
}
1

Если «прямым путем» вы имеете в виду избегать if утверждение, то я не думаю, что есть действительно общее решение.

Однако, если ваша конкретная проблема позволила бы потерять некоторую точность в дискретизации углов, и вы можете потерять некоторое время в преобразованиях типов, вы можете отобразить допустимый диапазон угла [-pi, pi) на разрешенный диапазон некоторого целочисленного типа со знаком. , Тогда вы получите комплементарность бесплатно. Однако я не использовал этот трюк на практике. Скорее всего, затраты на преобразование с плавающей точкой в ​​целое и целое в число с плавающей точкой перевесят любую выгоду от непосредственности. Лучше установить приоритеты при написании кода с автоматическим векторизацией или распараллеливанием, когда это вычисление углов выполняется много.

Кроме того, если детали вашей проблемы таковы, что есть определенное более вероятное решение для углового направления, то вы можете использовать встроенные функции компилятора для предоставления этой информации компилятору, чтобы он мог более эффективно оптимизировать ветвление. Например, в случае GCC это __builtin_expect функция. Это несколько удобнее использовать, когда вы оборачиваете likely а также unlikely макросы (как в ядре Linux):

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)
0

Формула для угла по часовой стрелке, 2D случай, между 2 векторами, xa, ya и xb, yb.

Угол (vec.a-vec, b) = pi () / 2 * ((1 + знак (ya)) * (1-знак (xa ^ 2)) — (1 + знак (yb)) * (1- знак (хь ^ 2)))

                        +pi()/4*((2+sign(ya))*sign(xa)-(2+sign(yb))*sign(xb))

+sign(xa*ya)*atan((abs(ya)-abs(xa))/(abs(ya)+abs(xa)))

-sign(xb*yb)*atan((abs(yb)-abs(xb))/(abs(yb)+abs(xb)))
0