Подтвердить что ты не робот

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

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

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

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

Unit Circle

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

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

4b9b3361

Ответ 1

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

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

что он

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

Ответ 2

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

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

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

Ответ 3

Я думаю, что лучший подход заключается в интерполяции греха и 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, просто добавьте pi в C.

Ответ 4

NB: использование кода С#

После некоторого безумного рыться в моем мозгу, вот что я придумал. В принципе, предпосылка состоит в том, чтобы выполнить обертывание 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);

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

Ответ 5

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

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

Ответ 6

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

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

ЧАСТЬ 1 - ЧТО ПУТЬ ИДЕТ?

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

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

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

Для проверки этих работ: 90 - 45 = 45 (что меньше или равно 180) делает утверждение IF TRUE, поэтому направление по часовой стрелке короче, но 315 - 45 = 270 (что больше 180) делает оператор if FALSE, поэтому формула против часовой стрелки будет короче.

ЧАСТЬ 2 - ФОРМУЛА ЧАСОВОЙ ЧАСТИ

Теперь вы хотите интерполировать N раз между A и B, либо по часовой стрелке, либо против часовой стрелки. Формула по часовой стрелке относительно проста.

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

Где S - счетчик числа интерполяций, начиная с 1 и заканчивая на N-1 (If 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 - ФОРМУЛА ANITCLOCKWISE

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

Ответ 7

Мое решение для slerp градусов. В моем классе 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