Эффект демпфирования системы Spring -Mass (или это ElasticEase?)
Я пытаюсь подражать эффекту анимации в коде (почти любой язык будет делать, как представляется, математика, а не язык). По сути, это эмуляция системы массой spring. Я смотрел на WPF/Silverlight ElasticEase
, и это похоже на то, что я ищу, но не совсем.
Прежде всего, вот что я ищу - объект, путешествующий определенное количество секунд, попав в место и немедленно замедляясь, чтобы оциллировать в течение определенного количества секунд, чтобы отдохнуть в той же точке, где было применено демпфирование, Чтобы визуализировать это, скажем, у меня есть холст 600 Вт /900 ч, и у меня есть квадрат, который начинает анимироваться от 900 пикселей до 150 пикселей в TranslateTransform.Y
. Требуется 4 секунды, чтобы достичь высоты 150 пикселей (187,5 пикселей в секунду), на какой стадии он сразу же затухает и только перемещается на 35 пикселей больше на 0,4 секунды (87,5 пикселей в секунду) до высоты 115 пикселей, а затем отскакивает на 1 секунду до высоты 163 пикселей (48px и 48px в секунду), а затем восстанавливается до 146px (17px и 17px в секунду) и так далее до тех пор, пока ocillations не замедлят его до конечного места отдыха 150px. Период оциляции составляет 16 секунд.
Пример, описанный выше, - это верхний левый синий прямоугольник:
![enter image description here]()
Здесь то, что я буду знать заранее - расстояние между пикселями и количество секунд, которое требуется, чтобы добраться от точки A до точки B, количество секунд для оциляции. Такие вещи, как масса, как представляется, не имеют значения.
Я пробовал ElasticEase
, и проблема в том, что я не могу заставить объект двигаться без ослабления в течение 4 секунд, а затем "отскок" в течение следующих 16 секунд. .Springiness
также всегда слишком много, даже если я установил его как очень высокое число, например 20.
ILSpy показывает свою функцию как:
protected override double EaseInCore(double normalizedTime)
{
double num = Math.Max(0.0, (double)this.Oscillations);
double num2 = Math.Max(0.0, this.Springiness);
double num3;
if (DoubleUtil.IsZero(num2))
{
num3 = normalizedTime;
}
else
{
num3 = (Math.Exp(num2 * normalizedTime) - 1.0) / (Math.Exp(num2) - 1.0);
}
return num3 * Math.Sin((6.2831853071795862 * num + 1.5707963267948966) * normalizedTime);
}
Я включил 2 видео и файл Excel в папку с zipped на DropBox. Думаю, этот вопрос будет скорее продолжением работы, так как люди задают более разъясняющие вопросы.
(ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я не знаю, о чем говорю, когда дело доходит до многих вещей)
Ответы
Ответ 1
Пропустите физику и просто перейдите прямо к уравнению.
параметры:
"Здесь, что я буду знать заранее - расстояние между пикселями [D] и количеством секунд [T0] требуется, чтобы добраться от точки A до точки B, количество секунд для колебаний [T1]". Кроме того, я добавлю в качестве бесплатных параметров: максимальный размер колебаний, Amax, постоянную времени демпфирования, Tc и частоту кадров Rf, то есть в какое время требуется новое значение позиции. Я предполагаю, что вы не хотите вычислять это навсегда, поэтому я просто сделаю 10 секунд, Ttotal, но есть множество разумных условий остановки...
код:
Вот код (в Python). Главное - это уравнение, найденное в def Y(t)
:
from numpy import pi, arange, sin, exp
Ystart, D = 900., 900.-150. # all time units in seconds, distance in pixels, Rf in frames/second
T0, T1, Tc, Amax, Rf, Ttotal = 5., 2., 2., 90., 30., 10.
A0 = Amax*(D/T0)*(4./(900-150)) # basically a momentum... scales the size of the oscillation with the speed
def Y(t):
if t<T0: # linear part
y = Ystart-(D/T0)*t
else: # decaying oscillations
y = Ystart-D-A0*sin((2*pi/T1)*(t-T0))*exp(-abs(T0-t)/Tc)
return y
y_result = []
for t in arange(0, Ttotal, 1./Rf): # or one could do "for i in range(int(Ttotal*Rf))" to stick with ints
y = Y(t)
y_result.append(y)
Идея - линейное движение до точки, за которой следует затухающее колебание. Осцилляция обеспечивается sin
и распадом, умножая ее на exp
. Конечно, измените параметры, чтобы получить любое расстояние, размер колебаний и т.д., Которые вы хотите.
![enter image description here]()
заметки:
- Большинство людей в комментариях предлагают физические подходы. Я не использовал их, потому что, если кто-то задает определенное движение, он немного переутомляется, чтобы начать с физики, перейти к дифференциальным уравнениям, а затем вычислить движение и настроить параметры, чтобы получить последнюю вещь, Мог бы просто пойти прямо к последней вещи. Если, конечно, нет интуиции для физики, от которой они хотят работать.
- Часто в таких проблемах хочется сохранить непрерывную скорость (первая производная), но вы говорите, что "немедленно замедляется", поэтому я этого не делал.
- Обратите внимание, что период и амплитуда колебаний не будут точно такими же, как указано при подавлении демпфирования, но это, вероятно, более подробно, чем вы заботитесь.
- Если вам нужно выразить это как одно уравнение, вы можете сделать это, используя функцию "Heaviside", чтобы включить и выключить вкладки.
Рискуя сделать это слишком долго, я понял, что могу сделать gif в GIMP, так что это выглядит так:
![enter image description here]()
Я могу опубликовать полный код, чтобы сделать графики, если есть интерес, но в основном я просто вызываю Y с разными значениями D и T0 для каждого временного времени. Если бы я снова это сделал, я мог бы увеличить демпфирование (т.е. Уменьшить Tc), но это немного хлопот, поэтому я оставляю его как есть.
Ответ 2
Я думал по тем же строкам, что и @tom10. (Я также рассмотрел IEasingFunction
, который взял IList<IEasingFunction>
, но было бы сложно взломать желаемое поведение из существующих).
// Based on the example at
// http://msdn.microsoft.com/en-us/library/system.windows.media.animation.easingfunctionbase.aspx
namespace Org.CheddarMonk
{
public class OtakuEasingFunction : EasingFunctionBase
{
// The time proportion at which the cutoff from linear movement to
// bounce occurs. E.g. for a 4 second movement followed by a 16
// second bounce this would be 4 / (4 + 16) = 0.2.
private double _CutoffPoint;
public double CutoffPoint {
get { return _CutoffPoint; }
set {
if (value <= 0 || value => 1 || double.IsNaN(value)) {
throw new ArgumentException();
}
_CutoffPoint = value;
}
}
// The size of the initial bounce envelope, as a proportion of the
// animation distance. E.g. if the animation moves from 900 to 150
// and you want the maximum bounce to be no more than 35 you would
// set this to 35 / (900 - 150) ~= 0.0467.
private double _EnvelopeHeight;
public double EnvelopeHeight {
get { return _EnvelopeHeight; }
set {
if (value <= 0 || double.IsNaN(value)) {
throw new ArgumentException();
}
_EnvelopeHeight = value;
}
}
// A parameter controlling how fast the bounce height should decay.
// The higher the decay, the sooner the bounce becomes negligible.
private double _EnvelopeDecay;
public double EnvelopeDecay {
get { return _EnvelopeDecay; }
set {
if (value <= 0 || double.IsNaN(value)) {
throw new ArgumentException();
}
_EnvelopeDecay = value;
}
}
// The number of half-bounces.
private int _Oscillations;
public int Oscillations {
get { return _Oscillations; }
set {
if (value <= 0) {
throw new ArgumentException();
}
_Oscillations = value;
}
}
public OtakuEasingFunction() {
// Sensible default values.
CutoffPoint = 0.7;
EnvelopeHeight = 0.3;
EnvelopeDecay = 1;
Oscillations = 3;
}
protected override double EaseInCore(double normalizedTime) {
// If we get an out-of-bounds value, be nice.
if (normalizedTime < 0) return 0;
if (normalizedTime > 1) return 1;
if (normalizedTime < _CutoffPoint) {
return normalizedTime / _CutoffPoint;
}
// Renormalise the time.
double t = (normalizedTime - _CutoffPoint) / (1 - _CutoffPoint);
double envelope = EnvelopeHeight * Math.Exp(-t * EnvelopeDecay);
double bounce = Math.Sin(t * Oscillations * Math.PI);
return envelope * bounce;
}
protected override Freezable CreateInstanceCore() {
return new OtakuEasingFunction();
}
}
}
Это непроверенный код, но это не должно быть слишком плохо для отладки, если есть проблемы. Я не уверен, какие атрибуты (если они есть) необходимо добавить к свойствам редактора XAML для правильной обработки.