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

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

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

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

    2D-корпус

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

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

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

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

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

    Угол наклона против часовой стрелки от 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) = угол против часовой стрелки между осью х и вектором (cos (theta), sin (theta))

    (4) = тета

    где ||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)

    что равно

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

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

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

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

    Для 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; } 

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

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

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

     #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) 
    Давайте будем гением компьютера.