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


79

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

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

Чи знаєте ви прямий спосіб обчислення кута за годинниковою стрілкою?


6
Чому б не використовувати std::atan2()?

2
Як ви визначаєте "кут за годинниковою стрілкою" для векторів у 3D?
Martin R

@ H2CO3 Це виглядає найкращим рішенням для 2D кутів.
Мірча Іспас,

@MartinR "за годинниковою стрілкою" - загальний термін, який означає, що я хочу кут у певному "напрямку", а не в найближчому "напрямку". У своїй відповіді Микола О. вказав спосіб опису цього "напрямку"
Мірча Іспас,

4
@Felics: "за годинниковою стрілкою" чітко визначено в 2D, але не в 3D. Перевірка z-координати поперечного добутку (як у відповіді Ніколая О.) означала б у 3D: "за годинниковою стрілкою для спостерігача, який дивиться зверху на площину х / у".
Martin R

Відповіді:


192

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)

Орієнтація цього кута збігається з орієнтацією системи координат. У лівій системі координат , тобто х, спрямованому вправо та у вниз, як це звичайно для комп'ютерної графіки, це означатиме, що ви отримаєте позитивний знак для кутів за годинниковою стрілкою. Якщо орієнтація системи координат математична з 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 . Точковий добуток, таким чином, в основному вимірюватиме довжину цього вектора, але з прикріпленим до нього правильним знаком.


4
Отримайте підтримку - мені неможливо заважати з’ясувати, чи інші відповіді правильні чи ні, ваша є найбільш чіткою та читабельною, тож саме вона мені допомогла.
Ексклюзивний

2
Для 2D я отримую (0,180) та (-180,0). Можна перевірити, коли результат негативний, і додати 360, щоб отримати гарний кут за годинниковою стрілкою (наприклад, якщо це -180, додаючи 360 результатів у 180, для -90 додаючи 360 результатів у 270 тощо). Не знаю, чи це лише мій розрахунок чи реалізація qAtan2(y, x)(із фреймворку Qt), але якщо хтось має таку ж проблему, як я, це може допомогти.
rbaleksandar

9
@rbaleksandar: atan2зазвичай знаходиться в межах [-180 °, 180 °]. Щоб отримати [0 °, 360 °] без різниці регістрів, можна замінити atan2(y,x)на atan2(-y,-x) + 180°.
MvG

1
@jianz: Кут - це позитивний кут щодо системи координат. Якщо x правильне, а y вгору, то кут проти годинникової стрілки. Якщо y опускається, це за годинниковою стрілкою. Більшість середовищ комп'ютерної графіки використовують останнє. Якщо ви хочете змінити орієнтацію, просто змініть порядок входів, який переверне знак визначника.
MvG

3
Ніколи, ніколи не приймайте знаки крапкового продукту! Це математично правильно, але жахливо неточно на практиці. Ви можете замінити свій метод 3d на інший atan2 (det, крапка); у цьому випадку det буде довжиною поперечного добутку.
Дон Хетч

5

Для обчислення кута потрібно просто зателефонувати atan2(v1.s_cross(v2), v1.dot(v2))за 2D-корпусом. Де s_crossскалярний аналог перехресного виробництва (підписана площа паралелограма). Для 2D корпусу це було б виготовлення клину. Для 3D-випадку потрібно визначити обертання за годинниковою стрілкою, оскільки з одного боку площини за одним годинником знаходиться один напрямок, з іншого боку площини - інший напрямок =)

Редагувати: це кут проти годинникової стрілки, кут за годинниковою стрілкою прямо протилежний


v1.cross (v2) - це вектор, а не скаляр і не може використовуватися таким чином. Микола Микола у своїй відповіді описує, як з’ясувати „напрямок” кута. Одним із способів отримати 2D-кут є: angle = atan2f (v2.x, v2.y) - atan2f (v1.x, v1.y)
Mircea Ispas

1
@Felics У двовимірному перехресному виробництві часто означає виготовлення клину en.wikipedia.org/wiki/Wedge_product, що є підписаною областю паралелограма. Для двовимірного випадку ця формула абсолютно правильна, оскільки вона має крапку = | v1 || v2 | * cos і хрест = | v1 || v2 | sin. Ось чому atan2 дає правильний кут у всьому діапазоні кола. І як я вже сказав для випадку 3d, вам потрібно зробити деякі припущення, щоб мати деяке продовження орієнтації за годинниковою стрілкою
kassak

1
@Felics: Зверніть увагу, що atan2fкоординатою y є перший аргумент, тому вона повинна бути angle = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x).
Martin R

1
@kassak: Ви можете замінити crossі dotза явною формулою у випадку 2D, яка усуне всі сумніви щодо crossповернення тривимірного вектора (але це лише пропозиція, яку ви можете ігнорувати). - Інакше мені подобається це рішення, оскільки воно вимагає лише одного atan2fвиклику функції.
Martin R

@Martin R дякую за добру пораду. Я зробив кілька виправлень, щоб зрозуміти значення формули
kassak

4

Ця відповідь така ж, як і у MvG, але пояснює це по-різному (це результат моїх зусиль, намагаючись зрозуміти, чому працює рішення MvG). Я публікую це повідомлення, якщо інші вважають це корисним.

Кут проти годинникової стрілки thetaвід xдо yвідносно точки зору заданої нормаліn ( ||n|| = 1), визначається як

atan2 (крапка (n, хрест (x, y)), крапка (x, y))

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

(2) = atan2 (sin (тета), cos (тета))

(3) = кут проти годинникової стрілки між віссю х та вектором (cos (тета), sin (тета))

(4) = тета

де ||x||позначає величинуx .

Крок (1) слід зазначити, що

хрест (x, y) = || x || || y || sin (тета) n,

і так

крапка (n, хрест (x, y))

= крапка (n, || x || || y || sin (theta) n)

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

що дорівнює

|| х || || y || гріх (тета)

якщо ||n|| = 1.

Крок (2) випливає з визначення atan2, зазначаючи, що atan2(cy, cx) = atan2(y,x)де cє скаляр. Крок (3) випливає з визначення atan2. Крок (4) випливає з геометричних визначень cosі sin.


2

Скалярний (крапковий) добуток двох векторів дозволяє отримати косинус кута між ними. Щоб отримати 'напрямок' кута, слід також розрахувати перехресний добуток, він дозволить вам перевірити (через координату z), чи кут дорівнює годинниковій стрілці чи ні (тобто витягувати його з 360 градусів чи ні).


1
Навіть це правильно, саме цього я хочу уникати - обчислити якесь значення та визначити, чи представляє обчислене значення мій кут або доповнення мого кута.
Мірча Іспас,

Я хочу знати, чи можливо це :) Навіщо використовувати якийсь неефективний спосіб робити щось, якщо є (може!) Кращий спосіб. Якщо немає кращого способу, я прийму "стандартну" річ, але завжди добре просити кращого!
Мірча Іспас,

Насправді, стандартні способи не завжди ефективні)
Микола Ольшевський

@NickolayOlshevsky Що ви маєте на увазі саме під контролем через координату z , як я можу це зробити?
Оген

Вам слід перевірити знак координати z, наскільки я пам’ятаю.
Микола Ольшевський

1

Для двовимірного методу ви можете використовувати закон косинусів та метод "напряму".

Для обчислення кута відрізка 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

Якщо під "прямим шляхом" ви маєте на увазі уникнення ifтвердження, то я не думаю, що існує насправді загальне рішення.

Однак, якщо ваша конкретна проблема дозволить втратити деяку точність при дискретизації кута, і ви не можете втратити час на перетворення типів, ви можете зіставити дозволений діапазон кута phi [-pi, pi) з дозволеним діапазоном деякого підписаного цілого числа . Тоді ви отримаєте комплементарність безкоштовно. Однак я насправді не використовував цей трюк на практиці. Швидше за все, витрати перетворень з плаваючим у ціле число та цілим із плаваючим числом переважатимуть будь-яку користь від прямоти. Краще встановити свої пріоритети для написання коду, що авторизується або розпаралелізується, коли обчислення цього кута робиться багато.

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

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

-1

Формула для кута за годинниковою стрілкою, 2D-корпус, між 2 векторами, xa, ya та xb, yb.

Кут (vec.a-vec, b) = pi () / 2 * ((1 + знак (ya)) * (1-знак (xa ^ 2)) - (1 + знак (yb)) * (1- знак (xb ^ 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)))

-2

просто скопіюйте та вставте це.

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

Ласкаво просимо ;-)


5
Хоча цей фрагмент коду може відповісти на запитання, включаючи пояснення, чому і як він допомагає вирішити проблему, покращує якість та довговічність вашої відповіді, особливо стосовно таких старих питань, як цей. Див. "Як написати хорошу відповідь?" .
лінивий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.