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

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

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

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

10 ответов

Решение

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

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

Этот ответ такой же, как и у 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,

Поскольку одно из самых простых и элегантных решений скрыто в одном из комментариев, я думаю, что было бы полезно опубликовать его как отдельный ответ. acos может привести к неточности для очень малых углов, поэтому atan2обычно предпочтительнее. Для случая 3D:

      dot = x1 * x2 + y1 * y2 + z1 * z2
cross_x = (y1 * z2 – z1 * y2)
cross_y = (z1 * x2 – x1 * z2)
cross_z = (x1 * y2 – y1 * x2)
det = sqrt(cross_x * cross_x + cross_y * cross_y + cross_z * cross_z)
angle = atan2(det, dot)

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

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

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

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

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

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

Для двумерного случая atan2 может легко вычислить угол между вектором (1, 0) (ось X) и одним из ваших векторов. Формула:

      Atan2(y, x)

Таким образом, вы можете легко вычислить разницу двух углов относительно оси X.

      angle = -(atan2(y2, x2) - atan2(y1, x1))

Почему он не используется как решение по умолчанию? atan2 недостаточно эффективен. Решение из верхнего ответа лучше. Тесты на C# показали, что этот метод имеет на 19,6% меньшую производительность (100 000 000 итераций). Это не критично, но неприятно.

Итак, еще информация, которая может оказаться полезной:

Наименьший угол между внешним и внутренним в градусах:

      abs(angle * 180 / PI)

Полный угол в градусах:

      angle = angle * 180 / PI
angle = angle > 0 ? angle : 360 - angle

или

      angle = angle * 180 / PI
if (angle < 0) angle = 360 - angle;

Формула для угла по часовой стрелке,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)))

просто скопируйте и вставьте это.

angle = (acos((v1.x * v2.x + v1.y * v2.y)/((sqrt(v1.x*v1.x + v1.y*v1.y) * sqrt(v2.x*v2.x + v2.y*v2.y))))/pi*180);

пожалуйста;-)

Другие вопросы по тегам