Интерполяция вращения - PullRequest
       22

Интерполяция вращения

25 голосов
/ 25 апреля 2010

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

У кого-нибудь есть идеи по поводу кода ротационной интерполяции? Имеется функция линейной интерполяции: Lerp (от, до, сумма), где сумма равна 0 ... 1, которая возвращает значение между от и до, по сумме. Как я могу применить эту же функцию к вращательной интерполяции между 0 и 360 градусами? Учитывая, что градусы не должны возвращаться за пределы 0 и 360.

Учитывая этот круг единицы для степеней:

Unit Circle

где от = 45 и до = 315, алгоритм должен взять кратчайший путь к углу, то есть он должен пройти через ноль, до 360, а затем до 315 - и не полностью вокруг 90, 180, 270 до 315 .

Есть ли хороший способ добиться этого? Или это будет ужасный беспорядок блоков if ()? Я пропускаю какой-то хорошо понятый стандартный способ сделать это? Любая помощь будет оценена.

Ответы [ 7 ]

32 голосов
/ 24 января 2013

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

    shortest_angle=((((end - start) % 360) + 540) % 360) - 180;
    return start + (shortest_angle * amount) % 360;

вот и все

пс: конечно,% означает по модулю, а shorttest_angle - переменная, которая содержит весь угол интерполяции

13 голосов
/ 25 апреля 2010

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

    public static float LerpDegrees(float start, float end, float amount)
    {
        float difference = Math.Abs(end - start);
        if (difference > 180)
        {
            // We need to add on to one of the values.
            if (end > start)
            {
                // We'll add it on to start...
                start += 360;
            }
            else
            {
                // Add it on to end.
                end += 360;
            }
        }

        // Interpolate it.
        float value = (start + ((end - start) * amount));

        // Wrap it..
        float rangeZero = 360;

        if (value >= 0 && value <= 360)
            return value;

        return (value % rangeZero);
    }

Кто-нибудь получил более оптимизированную версию?

7 голосов
/ 08 мая 2015

Я думаю, что лучший подход - это интерполировать грех и cos, поскольку они не страдают от многократного определения формы. Пусть w = «количество», так что w = 0 - это угол A, а w = 1 - это угол B. Тогда

CS = (1-w)*cos(A) + w*cos(B);
SN = (1-w)*sin(A) + w*sin(B);
C = atan2(SN,CS);

Нужно конвертировать в радианы и градусы по мере необходимости. Также нужно настроить ветку. Для atan2 C возвращается в диапазоне от -pi до pi. Если вы хотите от 0 до 2pi, просто добавьте пи к C.

3 голосов
/ 25 апреля 2010

Примечание: с использованием кода C #

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

В точке, где вы выбираете начало и конечную точку, вы выполняете следующее:

float difference = Math.Abs(end - start);
if (difference > 180)
{
    // We need to add on to one of the values.
    if (end > start)
    {
        // We'll add it on to start...
        start += 360;
    }
    else
    {
        // Add it on to end.
        end += 360;
    }
}

Это дает вам фактические начальные и конечные значения, которые могут быть за пределами 0-360 ...

У нас есть функция обтекания, которая обеспечивает значение от 0 до 360 ...

public static float Wrap(float value, float lower, float upper)
{
    float rangeZero = upper - lower;

    if (value >= lower && value <= upper)
        return value;

    return (value % rangeZero) + lower;
}

Затем в точке, в которой вы запрашиваете текущее значение из функции:

return Wrap(Lerp(start, end, amount), 0, 360);

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

2 голосов
/ 06 сентября 2014

Я хотел переписать свой ответ, чтобы лучше объяснить ответ на вопрос. Я использую EXCEL для своих формул и градусы для своих единиц.

Для простоты B - это большее из двух значений, а A - это меньшее из двух значений. Позже вы можете использовать MAX() и MIN() соответственно в своем решении.

ЧАСТЬ 1 - КУДА ПУТЬ?

Сначала мы хотим выяснить, в каком направлении мы хотим выполнить вычисления по часовой стрелке или против часовой стрелки. Мы используем IF() заявление для этого:

IF( (B-A)<=180, (Clockwise_Formula), (AntiClockwise_Formula) )

Приведенная выше формула проверяет, меньше или равно 180 градусов направление против часовой стрелки от B до A (то же самое, что по часовой стрелке от A до B) Если нет, идти в другом направлении будет короче.

Чтобы проверить это работает: 90 - 45 = 45 (что меньше или равно 180) делает оператор IF ИСТИНА, поэтому направление по часовой стрелке короче, а 315 - 45 = 270 (что больше 180) делает оператор if FALSE, поэтому формула против часовой стрелки будет короче.

ЧАСТЬ 2 - ФОРМУЛА ЧАСОВ Теперь вы хотите интерполировать N раз между A и B, по часовой стрелке или против часовой стрелки. Формула по часовой стрелке относительно проста.

Clockwise_Formula: ((B-A)/N*S)+A

Где S - это счетчик количества интерполяций, начиная с 1 и заканчивая N-1 (Если S = N, ваш ответ будет B)

Пример: A = 90, B = 270, N = 4

S=1:     ((270-90)/4*1)+90 = 135
S=2:     ((270-90)/4*2)+90 = 180
S=3:     ((270-90)/4*3)+90 = 225

ЧАСТЬ 3 - ФОРМУЛА ПРОТИВ ЧАСОВ

Формула против часовой стрелки будет немного сложнее, потому что нам нужно будет пересечь против часовой стрелки через угол 360 градусов. Самый простой метод, который я могу придумать, - это добавить 360 к A, а затем смодулировать ответ на 360 с помощью функции MOD(FORMULA,VALUE).

Вам также придется поменять местами A и B в формуле, потому что B теперь является наименьшим числом. (Это может звучать немного странно, но это работает!)

(Unmodulated) AntiClockwise_Formula: (((A+360)-B)/N*S)+B

Пример: A = 60, B = 300, N = 4

S=1:     (((60+360)-300)/4*1)+300 = 330
S=2:     (((60+360)-300)/4*2)+300 = 360
S=3:     (((60+360)-300)/4*3)+300 = 390

ЧАСТЬ 4 - ОГРАНИЧЕНИЕ ОТВЕТОВ НА ОТ 0 И 360

Видите, как иногда (но не всегда) ответы будут больше 360? Вот где начинается обертывание вашей Anticlockwise_formula в функцию MOD():

AntiClockwise_Formula: MOD((((A+360)-B)/N*S)+B,360)

Модулирование примера, использованного в части 3, даст вам:

S=1:     330
S=2:     0
S=3:     30

ЧАСТЬ 5 - ВСЕ ВМЕСТЕ

Объединяя все элементы из частей 1-4 вместе, ответ:

IF((B-A)<=180,((B-A)/N*S)+A,MOD((((A+360)-B)/N*S)+B,360))

Где:

A = Меньшее из двух значений (вы можете заменить A на MIN ())

B = большее из двух значений (вы можете заменить B на MAX ())

N = Количество интерполяций, которые вы хотите выполнить (например, 2 - половина, 3 - на трети и т. Д.)

S = Инкриментальный счет до макс. N-1 (объяснение см. В части 2)

1 голос
/ 25 апреля 2010

Мой предпочтительный способ справиться с углом - использовать единицы, имеющие степень 2 на оборот. Например, если вы используете 16-битные целые числа со знаком для представления от -180 до +180 градусов, вы можете просто взять (from-to) / num_steps, чтобы выполнить интерполяцию. Сложение и вычитание углов всегда работает, так как двоичные значения переполняются прямо в точке, где вы идете от 360 до 0.

Что вы, вероятно, хотите сделать в вашем случае - это математика по модулю 360. Таким образом, разность углов вычисляется как (от-до)% 360. Есть еще некоторые проблемы со знаком, которые были рассмотрены в других вопросах SO.

0 голосов
/ 03 декабря 2018

Мое решение для ступенек. В моем классе VarTracker

    @classmethod
def shortest_angle(cls, start: float, end: float, amount: float):
    """ Find shortest angle change around circle from start to end, the return
        fractional part by amount.
    VarTracker.shortest_angle(10, 30, 0.1) --> 2.0
    VarTracker.shortest_angle(30, 10, 0.1) --> -2.0
    VarTracker.shortest_angle(350, 30, 0.1) --> 4.0
    VarTracker.shortest_angle(350, 30, 0.8) --> 32.0
    VarTracker.shortest_angle(30, 350, 0.5) --> -20.0
    VarTracker.shortest_angle(170, 190, 0.1) --> 2.0
    VarTracker.shortest_angle(10, 310, 0.5) --> -30.0
    """
    sa = ((((end - start) % 360) + 540) % 360) - 180;
    return sa * amount;

@classmethod
def slerp(cls, current: float, target: float, amount: float):
    """ Return the new value if spherical linear interpolation from current toward target, by amount, all in degrees.
    This method uses abs(amount) so sign of amount is ignored.
    current and target determine the direction of the lerp.
    Wraps around 360 to 0 correctly.

    Lerp from 10 degrees toward 30 degrees by 3 degrees
    VarTracker.slerp(10, 30, 3.0) --> 13.0
    Ignores sign of amount
    VarTracker.slerp(10, 30, -3.0) --> 13.0
    VarTracker.slerp(30, 10, 3.0) --> 27.0
    Wraps around 360 correctly
    VarTracker.slerp(350, 30, 6) --> 356.0
    VarTracker.slerp(350, 30, 12) --> 2.0
    VarTracker.slerp(30, 350, -35) --> 355.0
    a = VarTracker.slerp(30, 3140, -35) --> 355.0
    VarTracker.slerp(170, 190, 2) --> 172.0
    VarTracker.slerp(10, 310, 12) --> 358.0
    Wraps over 0 degrees correctly
    VarTracker.slerp(-10, 10, 3) --> 353.0
    VarTracker.slerp(10, -10, 12) --> 358
    """
    a = VarTracker.shortest_angle(current, target, 1.0)
    diff = target - current
    if np.abs(amount) > np.abs(diff):
        amount = diff
    if a < 0:
        amount = -np.abs(amount)
    else:
        amount = np.abs(amount)
    ret = current + amount
    while ret < 0:
        ret = ret + 360
    ret = ret % 360
    return ret
...