2009-09-15 3 views
27

Найти угол между двумя векторами не сложно using the cosine rule. Однако, поскольку я программирую платформу с очень ограниченными ресурсами, я бы хотел избежать таких расчетов, как sqrt и arccos. Даже простые подразделения должны быть ограничены в максимально возможной степени.Недорогой алгоритм для поиска меры угла между векторами

К счастью, мне не нужен угол сам по себе, но требуется только некоторое значение, пропорциональное указанному углу.

Итак, я ищу некоторый вычислительно дешевый алгоритм для вычисления величины, связанной с углом между двумя векторами. До сих пор я не нашел того, что соответствует законопроекту, и я не смог сам что-то придумать.

+1

hmm: important question: являются компонентами векторов, хранящихся в формате с фиксированной или плавающей запятой? –

+0

Ничего. Поскольку координаты, о которых идет речь, являются пиксельными координатами, они всегда являются целыми. Не требуется плавающая точка/фиксированная точка. Итак, я думаю, вы могли бы сказать, что они фиксированные точки с множителем 1 :) – Jeroen

ответ

12

Вы пробовали алгоритм CORDIC? Это общая схема для решения прямоугольных задач полярных ↔ только с таблицей add/subtract/bithift +, по сути делающей поворот под углами формы tan -1 (2 -n). Вы можете обменять точность с временем выполнения, изменив количество итераций.

В вашем случае возьмите один вектор в качестве фиксированного задания и скопируйте его во временный вектор, который вы вращаете, используя кордовые углы к первому вектору (грубо делясь пополам), пока не достигнете нужной угловой точности.

(редактирование:. использование знака скалярного произведения, чтобы определить, на каждом этапе, следует ли повернуть вперед или назад Хотя если размножается достаточно дешевы, чтобы разрешить использование скалярного произведения, то не беспокойтесь Cordic, возможно использовать таблицу грех/соз пара для вращения матриц углов π/2 п для решения проблемы с бисекцией)

(редактировать:. Мне нравится предложение Эрика Bainville в в комментариях: вращает оба вектора к нулю и следить разности углов.)

+1

+1. CORDIC - это то, что вы хотите. Возможно, быстрее вращать два вектора одновременно по оси X, соблюдая разность углов. –

+0

... и CORDIC дадут вам норму каждого вектора бесплатно. –

+0

@ Эрик: хорошая точка вокруг вращающихся векторов одновременно. –

2

Решение будет тривиально, если векторы были определены/сохранены с использованием полярных координат вместо декартовых координат (или «а также» с использованием декартовых координат).

+0

К сожалению, это не тот случай. Единственным способом было бы преобразовать координаты в полярность в моем коде, но это столкнулось с той же проблемой, которую я пытаюсь решить в первую очередь. – Jeroen

0

dot product может работать в вашем случае. Он не пропорционален углу, но «связан».

+0

OP знает, что для точечного произведения требуется arccos для получения угла –

+0

. Или квадратные корни для нормализации длины. Оба из которых пытаются избежать. –

12

Назад в день нескольких К оперативной памяти и машин с ограниченными математическими возможностями Я использовал таблицы поиска и линейную интерполяцию. Основная идея проста: создайте массив с таким большим разрешением, сколько вам нужно (больше элементов уменьшает ошибку, создаваемую интерполяцией). Затем интерполируйте между значениями поиска.

Here is an example в обработке (original dead link).

Вы можете сделать это с помощью других функций триггера. На процессоре 6502 это позволяло вычислять полную 3D-кадровую рамочную графику с увеличением скорости на порядок.

+1

+1: интерполяция в таблицы - быстрая и малая. Не так точен, но часто достаточно точен. Часто достаточно 256 значений косинуса. –

+0

Спасибо за предложение, но это все равно только избавится от аркко. определение остальных элементов правила косинуса в этом случае (длины всех сторон треугольника) по-прежнему потребует нескольких sqrt. – Jeroen

+0

Меньше, чем этого может быть достаточно. Если вы хотите сделать небольшой дополнительный расчет, вам понадобятся только значения от нуля до половины pi/90 градусов, и, конечно же, вам нужно решить, какая гранулярность и точность вам нужны. Трудно сказать что-то определенное о деталях, не учитывая индивидуальный случай. –

1

скалярное произведение двух векторов (x1, y1) и (x2, y2) является

x1 * x2 + y1 * y2 

и equivilent произведению длин двух векторов раз косинус угла между ними.

Так что, если вы нормализуют два вектора первого (разделить координаты по длине)

Where length of V1 L1 = sqrt(x1^2 + y1^2), 
    and length of V2 L2 = sqrt(x2^2 + y2^2), 

Тогда нормированные векторы

(x1/L1, y1/L1), and (x2/L2, y2/L2), 

И скалярное произведение нормированных векторов (который является таким же, как косинус угла между векторами) будет

(x1*x2 + y1*y2) 
----------------- 
    (L1*L2) 

Конечно, это ma y так же сложно вычислить, как вычислить косинус

+0

OP знает, что для точечного произведения требуется арккос, чтобы получить угол –

+0

@ Джейсон, да, но точечный продукт сам по себе является «количеством, которое связано с углом», которое вы не должны обязательно преобразовывать в радианы или градусы используйте его ... –

1

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

acos((x1*x2 + y1*y2) * invsqrt((x1*x1+y1*y1)*(x2*x2+y2*y2))); 
+0

Если нет проблем с переполнением, я бы объединил invsqrt в один вызов invsqrt ((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)); лучше запускать сложный алгоритм один раз, а не дважды. –

+0

Отличное наблюдение :-) – neoneye

1

Перекрестное произведение пропорционально углу между двумя векторами, а когда векторы нормированы и угол мал, поперечное произведение очень близко к фактическому углу в радианах из-за малой угловой аппроксимации.

специфический:

I1Q2-I2Q1 пропорционален углу между I1Q1 и I2Q2.

16

Если вам не нужен фактический эвклидовый угол, но что-то, что вы можете использовать в качестве базы для сопоставлений углов, то выбор геометрии такси может быть выбором, потому что вы можете отказаться от тригонометрии, и это медлительность, ПОДДЕРЖИВАЯ ТОЧНОСТЬ (или, по крайней мере, с действительно незначительным потере точности, см. ниже).

В современных современных браузерах коэффициент ускорения находится в диапазоне от 1,44 до 15,2, а точность почти такая же, как у atan2. Расчет угла алмаза в среднем в 5,01 раза быстрее, чем atan2, и с использованием встроенного кода в Firefox 18 ускорение достигает коэффициента 15,2. Сравнение скорости: http://jsperf.com/diamond-angle-vs-atan2/2.

Код очень прост:

function DiamondAngle(y, x) 
{ 
    if (y >= 0) 
     return (x >= 0 ? y/(x+y) : 1-x/(-x+y)); 
    else 
     return (x < 0 ? 2-y/(-x-y) : 3+x/(x-y)); 
} 

Приведенный выше код дает угол между 0 и 4, в то время как atan2 дает угол между -PI и PI, как следующая таблица показывает:

enter image description here

Обратите внимание, что угол алмаза всегда положительный и находится в диапазоне 0-4, тогда как atan2 дает также отрицательные радианы. Таким образом, угол алмаза более нормализуется. И еще одно замечание: atan2 дает немного более точный результат, потому что длина диапазона 2 * pi (т.е. 6,283185307179586), а в углах алмаза - 4. На практике это не очень важно, например. рад 2.3000000000000001 и 2.3000000000000002 оба находятся под углом к ​​алмазу 1.4718731421442295, но если мы понижаем точность, сбросив один ноль, рад 2.300000000000001 и 2.300000000000002 дает оба разных угла алмаза. Эта «точность потери» в углах алмаза настолько мала, что имеет некоторое существенное влияние только в том случае, если расстояния огромны. Вы можете играть с преобразованиями в http://jsbin.com/bewodonase/1/edit?output (Старая версия: http://jsbin.com/idoyon/1):

enter image description here

Приведенный выше код достаточно для быстрого угловыми сравнений, но во многих случаях возникает необходимость преобразовать угол бриллиантом в радианы и вице verca. Если вы, например. имеют некоторый толерант в виде радианских углов, а затем у вас есть цикл 100 000 раз, где этот перенос сравнивается с другими углами, нецелесообразно проводить сравнения с использованием atan2.Вместо этого, перед циклом, вы изменяете допуск радиана к допуску на такси (алмазные углы) и делаете внутрикорректные сравнения с использованием толерантности к алмазу, и таким образом вам не нужно использовать медленные тригонометрические функции в критически важных частях кода (= в петли).

Код, который делает это преобразование заключается в следующем:

function RadiansToDiamondAngle(rad) 
{ 
    var P = {"x": Math.cos(rad), "y": Math.sin(rad) }; 
    return DiamondAngle(P.y, P.x); 
} 

Как вы заметили, есть cos и sin. Как вы знаете, они медленные, но вам не нужно делать преобразование в цикле, но до цикла и ускорения огромны.

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

function DiamondAngleToRadians(dia) 
{ 
    var P = DiamondAngleToPoint(dia); 
    return Math.atan2(P.y,P.x); 
} 

function DiamondAngleToPoint(dia) 
{ 
    return {"x": (dia < 2 ? 1-dia : dia-3), 
    "y": (dia < 3 ? ((dia > 1) ? 2-dia : dia) : dia-4)}; 
} 

Здесь вы используете atan2, который медленно, но идея состоит в том, чтобы использовать это вне всяких петель. Вы не можете преобразовать угол алмаза в радианы, просто умножившись на какой-то фактор, но вместо этого найдя точку в геометрии такси, в которой угол алмаза между этой точкой и положительной осью X является рассматриваемым углом алмаза и преобразующим эту точку в радианы с использованием atan2.

Этого должно быть достаточно для быстрого сравнения углов.

Конечно, есть другие методы ускорения atan2 (например, CORDIC и таблицы поиска), но AFAIK все они теряют точность и могут быть медленнее, чем atan2.

ПРЕДПОСЫЛКА: Я проверил несколько методов: точечные продукты, внутренние продукты, закон косинуса, единичные круги, таблицы поиска и т. Д., Но ничего не было достаточно, если важны как скорость, так и точность. Наконец, я нашел страницу в http://www.freesteel.co.uk/wpblog/2009/06/encoding-2d-angles-without-trigonometry/, которая имела желаемые функции и принципы.

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

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

+0

Это именно то, что мне нужно - спасибо. Но я назвал параметры dX и dY, так как они действительно дельта оси, а не ординаты точек. Я думаю, что это более понятно. –

4

Здесь на SO У меня все еще нет возможности прокомментировать (хотя у меня есть на math.se), так что это на самом деле ответ на сообщение Тимо на углах алмаза.

Все понятие углов алмаза, основанных на норме L1, является наиболее интересным, и если бы это было просто сравнение того, какой вектор имеет больший/меньший w.r.t. положительной оси X было бы достаточно. Тем не менее, OP действительно упомянул угол между двумя универсальными векторами, и я полагаю, что OP хочет сравнить его с некоторым допуском для поиска гладкости/углового статуса или sth, как это, но, к сожалению, кажется, что только с формулами, представленными на jsperf.com или freesteel.co.uk (ссылки выше), похоже, это невозможно сделать, используя алмазные углы.

Обратите внимание на следующий вывод из моей реализации Asymptote формул:

Vectors : 50,20 and -40,40 
Angle diff found by acos  : 113.199 
Diff of angles found by atan2 : 113.199 
Diamond minus diamond   : 1.21429 
Convert that to degrees  : 105.255 
Rotate same vectors by 30 deg. 
Vectors : 33.3013,42.3205 and -54.641,14.641 
Angle diff found by acos  : 113.199 
Diff of angles found by atan2 : 113.199 
Diamond minus diamond   : 1.22904 
Convert that to degrees  : 106.546 
Rotate same vectors by 30 deg. 
Vectors : 7.67949,53.3013 and -54.641,-14.641 
Angle diff found by acos  : 113.199 
Diff of angles found by atan2 : 113.199 
Diamond minus diamond   : 1.33726 
Convert that to degrees  : 116.971 

Так дело в том, вы не можете сделать алмаз (альфа) -diamond (бета) и сравнить его с определенным допуском в отличие вы можете делать с выходом atan2. Если все, что вы хотите сделать, это алмаз (альфа)> алмаз (бета), то я полагаю, что алмаз прекрасен.

+0

Да, вы правы. Алмазные углы можно сравнивать только, но не добавлять, не вычитать, не умножать и не разделять. Единственными исключениями являются градусы 0, 45, 90, 135, 180, 225, 270, 315, 360. Вы можете безопасно передавать эти углы между такси и эвклидовыми пространствами, используя разделить/умножить. Перенос любого другого угла вызывает ошибочность, и эта ошибка изменяется от 0 до 4.074568596222775 градусов. Я попытался выяснить логику в этой дисперсии, и если ее можно учесть, чтобы увеличить точность передачи пространства-пространства. –