Интерполяция вращения
NB: Я представляю этот вопрос в градусах исключительно для простоты, радиан, градусов, разных нулевых значений, проблема в основном одна и та же.
Есть ли у кого-нибудь идеи относительно кода, лежащего в основе вращательной интерполяции? Для функции линейной интерполяции: Lerp (from, to, amount), где сумма равна 0... 1, которая возвращает значение между от и до, по сумме. Как я могу применить эту же функцию к вращательной интерполяции между 0 и 360 градусами? Учитывая, что градусы не должны возвращаться за пределами 0 и 360.
Учитывая этот единичный круг для градусов:
![Unit Circle]()
где от = 45 и до = 315, алгоритм должен принимать кратчайший путь к углу, т.е. он должен пройти через ноль, до 360, а затем до 315 - и не весь путь 90, 180, от 270 до 315.
Есть ли хороший способ достичь этого? Или это будет просто ужасный беспорядок if() блоков? Я пропустил какой-то хорошо понятый стандартный способ сделать это?
Любая помощь будет оценена.
Ответы
Ответ 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