Я пытаюсь заставить сустав вращаться плавно вокруг центра холста, к углу указателя мыши. То, что у меня есть, работает, но я хочу, чтобы оно анимировало кратчайшее расстояние, чтобы добраться до угла мыши. Проблема возникает, когда значение зацикливается на горизонтальной линии ( 3.14
и -3.14
). Наведите указатель мыши на эту область, чтобы увидеть, как меняется направление, и он проходит долгий путь назад.
Соответствующий код
// ease the current angle to the target angle
joint.angle += ( joint.targetAngle - joint.angle ) * 0.1;
// get angle from joint to mouse
var dx = e.clientX - joint.x,
dy = e.clientY - joint.y;
joint.targetAngle = Math.atan2( dy, dx );
Как я могу заставить его вращаться на кратчайшее расстояние, даже «через зазор»?
mathematics
javascript
animation
lerp
easing
jackrugile
источник
источник
Ответы:
Это не всегда лучший метод, и он может быть более затратным в вычислительном отношении (хотя в конечном итоге это зависит от того, как вы храните ваши данные), но я приведу аргумент, что в большинстве случаев разумное использование 2D-значений работает достаточно хорошо. Вместо того, чтобы отклонять желаемый угол, вы можете привязать желаемый нормализованный вектор направления.
Одно из преимуществ этого метода перед методом «выбрать кратчайший путь к углу» состоит в том, что он работает, когда вам нужно интерполировать между более чем двумя значениями.
При переносе значений оттенков вы можете заменить
hue
их[cos(hue), sin(hue)]
вектором.В вашем случае, нормализованное направление суставов:
Код может быть короче, если вы можете использовать 2D векторный класс. Например:
источник
dx /= len
...len ? len : 1.0
Деталь просто избегает деления на ноль, в редком случае, когда мышь находится точно в положении соединения. Это могло быть написано:if (len != 0) dx /= len;
.0°
и180°
? В векторном виде:[1, 0]
а[-1, 0]
. Интерполирующие векторы дадут вам либо0°
,180°
или ошибку деления на 0, в случаеt=0.5
.Хитрость заключается в том, чтобы помнить, что углы (по крайней мере, в евклидовом пространстве) периодичны на 2 * пи. Если разница между текущим углом и целевым углом слишком велика (т. Е. Курсор пересек границу), просто отрегулируйте текущий угол, добавив или вычтя 2 * пи соответственно.
В этом случае вы можете попробовать следующее: (Я никогда раньше не программировал в Javascript, так что простите мой стиль кодирования.)
РЕДАКТИРОВАТЬ : В этой реализации слишком быстрое перемещение курсора вокруг центра соединения приводит к его рывку. Это предполагаемое поведение, так как угловая скорость сустава всегда пропорциональна
dtheta
. Если это поведение нежелательно, проблему можно легко решить, установив крышку на угловое ускорение сустава.Для этого нам нужно отслеживать скорость соединения и наложить максимальное ускорение:
Затем для нашего удобства введем функцию отсечения:
Теперь наш код движения выглядит следующим образом. Сначала мы рассчитываем,
dtheta
как и прежде, корректируяjoint.angle
при необходимости:Затем, вместо того, чтобы немедленно перемещать соединение, мы вычисляем целевую скорость и используем ее,
clip
чтобы заставить ее в пределах нашего приемлемого диапазона.Это обеспечивает плавное движение даже при переключении направлений при выполнении расчетов только в одном измерении. Кроме того, это позволяет независимо регулировать скорость и ускорение соединения. Смотрите демо здесь: http://codepen.io/anon/pen/HGnDF/
источник
dtheta
. Вы намеревались, чтобы у сустава был некоторый импульс?Я люблю другие ответы. Очень технично!
Если вы хотите, у меня есть очень простой способ сделать это. Мы примем углы для этих примеров. Концепция может быть экстраполирована на другие типы значений, такие как цвета.
Я только что создал это в браузере и никогда не проверял. Надеюсь, я правильно понял логику с первой попытки.
[Редактировать] 2017/06/02 - немного прояснил логику.
Начните с вычисления distanceForward и distanceBackwards, и позвольте результатам выйти за пределы диапазона (0-360).
Нормализация углов возвращает эти значения в диапазон (0-360). Чтобы сделать это, вы добавляете 360, пока значение не станет выше нуля, и вычтите 360, пока значение не превысит 360. Результирующие начальный / конечный углы будут эквивалентны (-285 равно 75).
Затем вы найдете наименьший нормализованный угол либо distanceForward, либо distanceBackward. distanceForward в примере становится 75, что меньше нормализованного значения distanceBackward (300).
Если distanceForward является наименьшим AND endAngle <startAngle, увеличьте endAngle за пределы 360, добавив 360. (в примере это 375).
Если distanceBackward является наименьшим AND endAngle> startAngle, увеличьте endAngle до значения ниже 0, вычитая 360.
Теперь вы переходите от startAngle (300) к новому endAngle (375). Двигатель должен автоматически отрегулировать значения выше 360, вычитая 360 для вас. В противном случае вам пришлось бы подскочить с 300 до 360, ТО - с 0 до 15, если двигатель не нормализует значения для вас.
источник
atan2
основанная идея проста, она может сэкономить немного места и немного производительности (нет необходимости хранить x и y, а также слишком часто вычислять sin, cos и atan2). Я думаю, что стоит немного углубиться в вопрос о том, почему это решение является правильным (то есть, выбирая кратчайший путь по кругу или сфере, как это делает SLERP для кватерионов). Этот вопрос и ответы должны быть помещены в вики сообщества, поскольку это действительно распространенная проблема, с которой сталкиваются большинство программистов геймплея.