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

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

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

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

48
@ H2CO3 Это кажется лучшим решением для 2D-углов.
добавлено автор Felics, источник
@MartinR «по часовой стрелке» является общим термином, чтобы сказать, что мне нужен угол в определенном «направлении», а не в ближайшем «направлении». Николай О. указал в своем ответе способ описания этого «направления»,
добавлено автор Felics, источник
@icepack Проблема не в том, «если», это дополнительный computatio, чтобы иметь возможность использовать «if» - как один из возможных ненужных кросс-продуктов
добавлено автор Felics, источник
Как вы определяете «угол по часовой стрелке» для векторов в 3D?
добавлено автор Martin R, источник
@Felics: «по часовой стрелке» четко определен в 2D, но не в 3D. Проверка z-координаты поперечного произведения (как и в ответе Николая О.) будет означать в 3D: «по часовой стрелке для наблюдателя, смотрящего сверху на плоскость x/y».
добавлено автор Martin R, источник
if является одним из примитивов языка. У вас мало осталось, если вы не можете их использовать.
добавлено автор SomeWittyUsername, источник
@Felics Кроме того, я должен отметить, что вы не могли определить угол 3D по часовой стрелке непрерывно из-за теоремы о волосатых шарах en.wikipedia .org/wiki/Hairy_ball_theorem У вас всегда будет пара векторов, эпсилон-движение одного из которых приведет к мгновенному переключению часового типа и, как результат, знак угла
добавлено автор kassak, источник
@Felics прочитал мой ответ. Направление по часовой стрелке не определено в 3d. Это плоский термин
добавлено автор kassak, источник
Почему бы не использовать std :: atan2() ?
добавлено автор user529758, источник

6 ответы

2D-корпус

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

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)

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

3D-корпус

В 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))

Плоскость, встроенная в 3D

Одним из особых случаев является случай, когда ваши векторы не расположены произвольно, а лежат в плоскости с известным нормальным вектором 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 . Таким образом, точечный продукт будет в основном измерять длину этого вектора, но с прикрепленным к нему правильным знаком.

118
добавлено
@Tara: Это будет сильно зависеть от окружающей среды. Главным образом CPU против оптимизации графического процессора и компилятора. Наивно тройной продукт будет 9 умножений и 5 добавок/вычитаний, тогда как моя составная формула - 12 умножений и 5 добавлений/вычитаний. Я думаю, что есть компиляторы, которые применили бы закон распределения как часть оптимизации, уменьшив число умножения, чтобы соответствовать тому, что делает тройной продукт из коробки. Я бы не удивился, если бы графические процессоры имели высоко оптимизированные реализации для векторных операций.
добавлено автор MvG, источник
@HariKaramSingh: Я уверен, что я не записывал вывод, или нужно написать этот ответ. Что бы вы хотели увидеть? Первое предложение в разделе 2d - это knowlegde, которое я использовал так часто, я не помню, где я его впервые услышал, но Wikipedia знает об этом тоже [ 1 , 2 ]. От этого к вызову atan2 , по сути, один шаг. Другие разделы по существу являются вариантами этой темы.
добавлено автор MvG, источник
@jianz: угол - это положительный угол относительно системы координат. Если x правильно, а y вверх, то угол вращается против часовой стрелки. Если y вниз, это по часовой стрелке. Большинство сред компьютерной графики используют последнее. Если вы хотите изменить ориентацию, просто измените порядок входов, который перевернет знак детерминанта.
добавлено автор MvG, источник
@rbaleksandar: atan2 обычно находится в диапазоне [-180 °, 180 °]. Чтобы получить [0 °, 360 °] без разграничения случая, можно заменить atan2 (y, x) на atan2 (-y, -x) + 180 ° .
добавлено автор MvG, источник
Двумерный ответ MvG дает противоположный угол, а не ближайший, не так ли?
добавлено автор jianz, источник
@Mvg: Спасибо за разъяснение. В моем случае я работал с географической системой координат, что x является правильным, а y - вверх. Еще раз спасибо.
добавлено автор jianz, источник
@MvG: Я думаю, что неправильно понял ваш первоначальный ответ ... Я думал, вы избавились от вызова atan2() . Однако код для тройного продукта просто заменяет определитель, оставляя остальную часть кода той же. Это было сразу не очевидно для меня. Но спасибо вам за подробности.
добавлено автор Tara, источник
Является ли это только мной, или является тройным методом продукта по быстрому самому результативному?
добавлено автор Tara, источник
Есть ли откуда-нибудь деривации? Просто любопытно...
добавлено автор Hari Karam Singh, источник
Или это. : D Спасибо. Более элегантный ваш путь.
добавлено автор rbaleksandar, источник
Для 2D я получаю (0,180) и (-180,0). Можно проверить, когда результат отрицательный, и добавить 360, чтобы получить хороший угол по часовой стрелке (например, если это -180, добавив 360 результатов в 180, для -90 добавив 360 результатов в 270 и т. Д.). Не знаю, это только мой расчет или реализация qAtan2 (y, x) (из рамки Qt), но если у кого-то такая же проблема, как мне, это может помочь.
добавлено автор rbaleksandar, источник
Имейте upvote - я не могу беспокоиться о том, чтобы выяснить, правильны ли другие ответы или нет, ваш самый читаемый и читаемый, так что это помогло мне.
добавлено автор Excrubulent, источник

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

Редактировать: это против часовой стрелки, угол по часовой стрелке просто противоположный

5
добавлено
v1.cross (v2) является вектором, а не скаляром и не может использоваться таким образом. Николай О. описывает в своем ответе, как узнать «направление» угла. Один из способов получить двумерный угол: угол = atan2f (v2.x, v2.y) - atan2f (v1.x, v1.y)
добавлено автор Felics, источник
ваши формулы не работают, например, для векторов (-1, 0) и (0, 1)
добавлено автор user2083364, источник
@kassak: вы можете заменить cross и dot на явную формулу в 2D-случае, что устранит все сомнения по поводу cross , возвращающего трехмерный вектор (но это всего лишь предложение, которое вы можете игнорировать). - В противном случае мне нравится это решение, потому что для этого требуется только один вызов функции atan2f .
добавлено автор Martin R, источник
@Felics: Обратите внимание, что atan2f имеет y-координату как первый аргумент, поэтому он должен быть angle = atan2f (v2.y, v2.x) - atan2f (v1.y, v1. х) .
добавлено автор Martin R, источник
Я тоже сделал эту ошибку =) исправил
добавлено автор kassak, источник
@Martin R благодарит за хороший совет. Я сделал некоторые исправления, чтобы сделать смысл формулы clearer
добавлено автор kassak, источник
@ user2083364 спасибо, см. править.
добавлено автор kassak, источник
@Felics В 2D перекрестном производстве часто подразумевается производство клина ru.wikipedia.org/wiki/Wedge_product Это является подписанной областью параллелограмма. Для 2D-случая эта формула абсолютно правильна, так как она dot = | v1 || v2 | * cos и cross = | v1 || v2 | sin. Вот почему atan2 дает правильный угол во всем диапазоне кругов. И, как я сказал для 3d-случая, вам нужно сделать некоторые предположения, чтобы иметь некоторое расширение ориентации по часовой стрелке
добавлено автор kassak, источник

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

Угол против часовой стрелки theta </​​code> от x до y , относительно точки зрения их заданного нормального n ( || n || = 1 ), задается формулой

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

     

(1) = atan2 (|| x || || y || sin (theta), || x || || y || cos (theta))

     

(2) = atan2 (sin (theta), cos (theta))

     

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

     

(4) = theta </​​p>

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

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

cross (x, y) = || x || || у || sin (theta) n,

и так

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

     

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

     

= || x || || у || sin (theta) dot (n, n)

что равно

<�Р> || х || || у || грех (тета)

if || n || = 1 .

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

3
добавлено

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

2
добавлено
Даже это правильно, это то, чего я хочу избежать - вычислить какое-то значение и определить, представляет ли вычисляемое значение мой угол или дополнение моего угла.
добавлено автор Felics, источник
Я хочу знать, возможно ли это. :) Почему бы использовать какой-то необоснованный способ делать вещи, если есть (может быть!) Лучший способ. Если нет лучшего способа, я буду «стандартным», но всегда полезно просить лучшего!
добавлено автор Felics, источник
@NickolayOlshevsky Что вы подразумеваете под контролем через z-координату , как я могу это сделать?
добавлено автор Ogen, источник
Насколько я помню, вы должны проверить знак z-координаты.
добавлено автор Nickolay Olshevsky, источник
На самом деле, стандартные способы не всегда эффективны)
добавлено автор Nickolay Olshevsky, источник
Почему вы так часто избегаете дополнительной строки кода?
добавлено автор Nickolay Olshevsky, источник

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

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

Кроме того, если ваши детали проблемы таковы, что существует определенный более вероятный результат для направления углов, то вы можете использовать встроенные функции компиляторов для предоставления этой информации компилятору, чтобы он мог более эффективно оптимизировать ветвление. Например, в случае gcc это функция __ builtin_expect . Это несколько более удобно использовать, когда вы вставляете его в такие макросы скорее всего и маловероятные (например, в ядре linux):

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)
0
добавлено

Для 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;
}
0
добавлено
pro.cxx
pro.cxx
3 049 участник(ов)

C/C++ chat 0. Простые вопросы, лабы и о IDE — в чат новичков @supapro 1. Не хамим, не переходим на личности, не вбрасываем утверждения без доказательств 2. No Ads, offtop, flood Объявления о вакансиях и евенты - в лс @AlexFails https://t.me/ProCxx/259155

supapro.cxx
supapro.cxx
1 925 участник(ов)

Чат для тех, кто немного знает C++, простые вопросы по реализации, синтаксису и ide – сюда, а для другого есть: /Главный чат по серьезным вопросам — @ProCxx /Чат по обсуждению всего — @fludpac

Infernal Math
Infernal Math
389 участник(ов)

http://www.zepta.ru/index.php?title=Заглавная_страница Приглашение в чат: https://t.me/matheden

C++ Russia
C++ Russia
384 участник(ов)

Сообщество разработчиков C++ в Telegram.

cxx.Дискуссионная
cxx.Дискуссионная
298 участник(ов)

это не двач, общайтесь вежливо; разговор на почти любые темы; Не согласны с баном? В лс @AlexFails, @ivario

comput.math
comput.math
289 участник(ов)

Прикладная математика и численные методы. Без оффтопа, рекламы, флуда. Вышмат: @higher_math Физика: @physpub Новичкам: @starter_math @JuliaLanguage @rlang_ru Книги брать здесь: libgen.io И по хештегу #book

higher.math
higher.math
234 участник(ов)

Higher mathematics / высшая математика Подборка книжек: https://ru.stackoverflow.com/a/683632/1084 Вычмат: @comput_math Физика: @physpub LaTeX: @pro_latex

C++ для маленьких и тупых
C++ для маленьких и тупых
105 участник(ов)

Лоу левел (по среднему IQ участников) чатик ExtremeCode @extremecode Флудилка @extremecode_rest

Starter Math
Starter Math
79 участник(ов)

Для тех, кто боится спросить в @higher_math Вычмат: @comput_math Физика: @physpub