Как рассчитать угол вектора от вертикали?

Я пытаюсь узнать угол (в градусах) между двумя 2D-векторами. Я знаю, что мне нужно использовать триггер, но я не очень хорошо с ним справляюсь. Вот что я пытаюсь решить (ось Y увеличивается вниз): alt text

Сейчас пытаюсь использовать этот код, но он вообще не работает (по какой-то причине вычисляет случайные углы):

private float calcAngle(float x, float y, float x1, float y1)
{
    float _angle = (float)Math.toDegrees(Math.atan2(Math.abs(x1-x), Math.abs(y1-y)));
    Log.d("Angle","Angle: "+_angle+" x: "+x+" y: "+y+" x1: "+x1+" y1: "+y1);
    return _angle;
}

Это мои результаты (они постоянны при обеспечении постоянного положения, но когда я меняю положение, угол меняется, и я не могу найти никакой связи между двумя углами):

Позиция 1: x: 100 y: 100 x1: 50 y1: 50 Угол: 45

Позиция 2: x: 92 y: 85 x1: 24 y1: 16 Угол: 44,58

Позиция 3: x: 44 y: 16 x1: 106 y1: 132 Угол: 28,12

Изменить: Спасибо всем, кто ответил и помог мне понять, что это неправильно! Извините, название и вопрос сбивают с толку.


person Niall    schedule 09.08.2010    source источник
comment
Сомневаюсь, что это случайно. Не могли бы вы опубликовать значения x1, x, y1, y? Меняется ли выход даже при постоянном входе?   -  person FrustratedWithFormsDesigner    schedule 09.08.2010
comment
Ваша диаграмма неверна. Вы определили только 2 точки, а вектор, создающий угол Тета, не представлен. Используя, p1 и p2, как на этой диаграмме, можно найти совсем другой угол; угол p1 и p2 составляют с началом координат.   -  person aepryus    schedule 09.08.2010
comment
Вы говорите, что пытаетесь вычислить угол между двумя векторами, но диаграмма, похоже, подразумевает, что вы на самом деле пытаетесь получить угол между одним вектором и осью y. Это правильно?   -  person Troubadour    schedule 09.08.2010
comment
@Troubadour Извините за запутанный вопрос. Я делаю это для игры, поэтому, когда вы достигнете точки, вам нужно повернуться к определенному градусу (по компасу), а затем пройти расстояние, чтобы достичь следующей точки.   -  person Niall    schedule 09.08.2010
comment
@FrustratedWithFormsDesigner Я опубликую свои результаты и фактический код в вопросе, 2 секунды :)   -  person Niall    schedule 09.08.2010
comment
@Niall: Звучит так, как будто у вас есть текущее направление и новый (абсолютный) угол, и вы хотите узнать новый вектор направления. Итак, вы просто пытаетесь взять абсолютный угол компаса и выработать новое направление (при этом старое направление не имеет значения)?   -  person Troubadour    schedule 09.08.2010
comment
Ага. Опять же, извините за вопрос! (и извините за поздний ответ, не видел ваш комментарий до сих пор)   -  person Niall    schedule 09.08.2010
comment
@Niall: Нет проблем. Я добавил ответ, объясняющий вашу проблему. Обратите внимание, что ваш результат для позиции 3 правильный.   -  person Troubadour    schedule 09.08.2010


Ответы (9)


Сначала вы должны понять, как вычислить угол между двумя векторами, а их несколько. Я дам вам то, что считаю самым простым.

  1. Для v1 и v2 их скалярное произведение: v1x * v2x + v1y * v2y
  2. Норма вектора v определяется выражением sqtr (vx ^ 2 + vy ^ 2)

Имея эту информацию, возьмите это определение:

dot(v1, v2) = norm(v1) * norm(v2) * cos(angle(v1, v2))

Теперь вы решаете angle(v1, v2):

angle(v1, v2) = acos( dot(v1, v2) / (norm(v1) * norm(v2)) )

Наконец, взяв определения, данные в начале, вы получите:

angle(v1, v2) = acos( (v1x * v2x + v1y * v2y) / (sqrt(v1x^2+v1y^2) * sqrt(v2x^2+v2y^2)) )

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

Ответ будет в радианах, но вы знаете, что пи радиан (то есть 3,14 радиана) равен 180 градусам, поэтому вы просто умножаете его на коэффициент преобразования 180 / пи.

person Escualo    schedule 09.08.2010
comment
Спасибо за объяснение! Я пробую ваш алгоритм, но когда угол тупой, я должен его отразить, а когда нет, я должен добавить 45 градусов. Есть ли этому повод? Я имею в виду, что я в порядке, делаю простой оператор if else, но я хотел бы знать, почему я это делаю :) - person Niall; 09.08.2010
comment
Не волнуйтесь, я просто понял, что мне нужно было использовать atan2 и отразить угол. Спасибо за ваш ответ и за его объяснение! :) - person Niall; 09.08.2010
comment
Ага. Если вы подумаете о скалярном произведении двух векторов, вы поймете, почему арктангенс решает проблему квадранта. Другими словами, он выбирает правильный знак для угла. Удачи. - person Escualo; 10.08.2010
comment
sqrt (vx ^ 2 + vy ^ 2) - величина вектора, а не нормаль. Один нормальный - (-vy / mag, vx / mag). Обратите внимание, что значения x и y поменялись местами. - person Gabe Johnson; 10.11.2015
comment
Нормализуйте оба вектора. Тогда это просто: angle = acos (vectorA.dot(vectorB)) - person Hal50000; 24.01.2016

Ага! Оказывается, мне просто нужно было перевернуть угол и использовать atan2. Это мой последний код:

private float calcAngle(float x, float y, float x1, float y1)
{
    float _angle = (float)Math.toDegrees(Math.atan2(x1-x, y-y1));
    return _angle;
}

Спасибо всем за то, что помогли мне разобраться в этом, а также за то, что помогли мне понять, что я на самом деле делаю! :)

person Niall    schedule 09.08.2010
comment
Итак, ваша координата Y увеличивается вниз по пути, и вы хотите, чтобы Север был прямо вверх. Вместо вычитания из 180 вы можете просто перевернуть знаки y, то есть ваш второй аргумент для atan2 равен y-y1. - person Troubadour; 09.08.2010
comment
предупреждение: деление на ноль будет результатом, когда (y1 - y) == 0. - person Leftium; 09.08.2010
comment
@wonsungi: Нет, не будет. atan2 заботится об этом и возвращает либо + pi / 2, либо -pi / 2 в зависимости от знаков двух аргументов (включая нулевой аргумент, поскольку он все еще может иметь знак). По крайней мере, в версии C. Я предполагаю, что другие языки сделали бы что-то подобное. - person Troubadour; 09.08.2010
comment
@Troubadour: Ты прав! Спасибо ~ Этот охранник, должно быть, был до того, как я узнал о atan2; Я использовал asin и делал деление вручную ... - person Leftium; 10.08.2010

Не принимайте абсолютное значение аргументов atan2. Весь смысл atan2 в том, что он использует знаки своих аргументов, чтобы определить, в каком qaudrant находится угол. Принимая абсолютные значения, вы заставляете atan2 возвращать только значения от 0 до pi / 2 вместо -pi to pi.

person Troubadour    schedule 09.08.2010
comment
Спасибо за объяснение, почему я не должен брать абсолютные значения при использовании atan2 :) Оказывается, в конце концов мне просто пришлось отразить угол (например, 180 + (- toDegrees (atan2 (x1-x, y1-y)))) - person Niall; 09.08.2010
comment
@Niall: Рад помочь. :) Смотрите мой комментарий к вашему ответу. Кроме того, поскольку вы на самом деле пытаетесь определить угол между отрицательной осью Y и одним вектором, возможно, вам следует отредактировать заголовок вопроса, поскольку в данный момент он вводит в заблуждение. Что-то вроде Как посчитать угол вектора от вертикали? и укажите в вопросе, что y увеличивается вниз в вашей системе. - person Troubadour; 09.08.2010
comment
Похоже, это должен быть комментарий для меня. Это не касается самого вопроса. - person Llew Vallis; 05.10.2017

Похоже, Найл понял это, но я все равно закончу свое объяснение. Помимо объяснения того, почему решение работает, у моего решения есть два преимущества:

  • Возможное деление на ноль внутри atan2 () исключается
  • Возвращаемое значение всегда положительно в диапазоне от 0 до 360 градусов.

atan2() возвращает угол против часовой стрелки относительно положительной оси X. Найл искал угол по часовой стрелке относительно положительной оси Y (между вектором, образованным двумя точками, и положительной осью Y).

Следующая функция адаптирована из моей игры с астероидами, в которой я хотел вычислить направление, в котором "указывал" корабль / вектор скорости:

// Calculate angle between vector from (x1,y1) to (x2,y2) & +Y axis in degrees.
// Essentially gives a compass reading, where N is 0 degrees and E is 90 degrees.

double bearing(double x1, double y1, double x2, double y2)
{
    // x and y args to atan2() swapped to rotate resulting angle 90 degrees
    // (Thus angle in respect to +Y axis instead of +X axis)
    double angle = Math.toDegrees(atan2(x1 - x2, y2 - y1));

    // Ensure result is in interval [0, 360)
    // Subtract because positive degree angles go clockwise
    return (360 - angle) %  360;
}
person Leftium    schedule 09.08.2010
comment
Спасибо, что помогли мне понять, почему это работает! :) К сожалению, сейчас я работаю на Java (в основном я разработчик C и Obj-C, но всегда полезно изучать что-то новое! Я начал с Java несколько лет назад, так что это не так уж сложно :)) Я не могу использовать #define. Кроме того, как вы думаете, мне следует использовать double вместо float? Поскольку все математические функции в Java используют double, но однажды у меня была проблема с double vs float (вероятно, что-то еще в моем коде), и с тех пор я всегда использовал float. - person Niall; 09.08.2010
comment
Я изменил код, сделав его более похожим на Java: Math.toDegrees() фактически то же самое, что умножение на предыдущую константу DEG_PER_RAD. (В противном случае вы можете создать константу Java с помощью static final double). Float имеет меньшую точность, чем double, и, вероятно, приведет к снижению производительности, поскольку потребуются преобразования между float и double. Если экономия памяти не является большой проблемой, я всегда буду использовать double. Также Трубадур указал, что atan2 не нуждается в защите от деления на ноль. - person Leftium; 10.08.2010

Должен быть :

atan( abs(x1 - x)/abs(y1 - y) ) 

abs означает абсолютный (чтобы избежать отрицательных значений)

person 3rgo    schedule 09.08.2010
comment
Если вы сделаете это таким образом, вы получите только угол между -pi / 2 и pi / 2. OP спрашивает о atan2. - person Troubadour; 09.08.2010

Я считаю, что уравнение для угла между двумя векторами должно больше выглядеть:

toDegrees(acos((x*x1+y*y1)/(sqrt(x*x+y*y)*sqrt(x1*x1+y1*y1))))

Приведенное выше уравнение вычислит угол, образованный между вектором p1-p2 и линией, образованной продолжением ортогонального элемента от точки p2 к вектору p1.

Скалярное произведение двух векторов V1 и V2 равно | V1 | * | V2 | cos (theta). Следовательно, theta равно acos ((V1 dot V2) / (| V1 | | V2 |)). V1 точка V2 - это V1.x V2.x + V1.y V2.y. Величина V (т.е. | V |) - это патогорейская теорема ... sqrt (V.x ^ 2 + V.y ^ 2)

person aepryus    schedule 09.08.2010

Моим первым предположением было бы вычислить угол каждого вектора с осями, используя atan (y / x), а затем вычесть этих ангелов и взять абсолютное значение, то есть:

абс (атан (y / x) - атан (y1 / x1))

person smichak    schedule 09.08.2010

Вы используете целые числа? Аргументы должны быть двойниками, и я буду использовать фабулы для результата, а не для аргументов. Результат будет в радианах; чтобы получить степень, используйте:

res * = (360.0 / (2.0 * Math.PI));

person Jess    schedule 09.08.2010
comment
Тот, кто проголосовал против этого, должен понимать, что он был опубликован до кода, опубликованного OP, который подтверждает использование точности с плавающей запятой. - person Troubadour; 09.08.2010

Угол второго вектора относительно первого = atan2(y2,x2) - atan2(y1,x1).

http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm

person Mark Cidade    schedule 09.08.2010