Демпфирующий эффект системы Spring-Mass (или это ElasticEase?) - PullRequest
12 голосов
/ 09 сентября 2011

Я пытаюсь эмулировать эффект анимации в коде (почти любой язык будет делать то, что кажется математикой, а не языком).По сути, это эмуляция системы масса-пружина.Я смотрел на WPF / Silverlight ElasticEase, и это, кажется, довольно близко к тому, что я ищу, но не совсем.

Прежде всего, вот что я ищу -объект, путешествуя определенное количество секунд, попадая в определенное место и сразу замедляясь, чтобы колебаться в течение определенного количества секунд, останавливается в той же точке, где было применено демпфирование.Итак, чтобы визуализировать это, скажем, у меня есть холст 600 Вт / 900 ч, и у меня есть квадрат, который начинает анимироваться от 900 до 150 пикселей в TranslateTransform.Y.Чтобы достичь высоты 150px (187.5px в секунду), требуется 4 секунды, на этом этапе непосредственное затухание затухает и увеличивается на приблизительно 35px в течение 0,4 секунды (87.5px в секунду) до высоты 115px, а затем восстанавливается на 1 секунду до высоты 163px(48px и 48px в секунду), а затем восстанавливается до 146px (17px и 17px в секунду) и так далее до тех пор, пока колебания не замедлят его до последнего места отдыха 150px.Период колебания составляет 16 секунд.

Пример, который я описал выше, - это верхний левый синий прямоугольник: enter image description here

Вот что я буду знать заранее - расстояние в пикселях и количество секундтребуется, чтобы добраться из точки А в точку Б - количество секунд для колебания.Такие вещи, как масса, кажется, не имеют значения.

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

(ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: я не знаю, о чем говорю, когда речь заходит о большей части этого материала)

Ответы [ 2 ]

9 голосов
/ 13 сентября 2011

Пропустите физику и просто перейдите к уравнению.

параметры : «Вот что я буду знать заранее - расстояние в пикселях [D] и количество секунд [T0]требуется, чтобы из точки A в точку B было получено количество секунд для колебаний [T1] ». Также я добавлю в качестве свободных параметров: максимальный размер колебаний, Amax, постоянную времени демпфирования, Tc и кадрскорость, Rf, то есть, в какое время нужно новое значение позиции.Я предполагаю, что вы не хотите рассчитывать это навсегда, поэтому я просто сделаю 10 секунд, Ttotal, но есть множество разумных условий остановки ...

code : Воткод (в 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

примечания :

  1. Большинство людей в комментариях предлагают физические подходы.Я не использовал их, потому что, если кто-то указывает определенное движение, это немного переутомление - начать с физики, перейти к дифференциальным уравнениям, а затем вычислить движение и настроить параметры, чтобы получить окончательную вещь,Может быть, просто пойти прямо к последней вещи.Если только у человека нет интуиции к физике, с которой он хочет работать.
  2. Часто в таких задачах, как эта, нужно поддерживать постоянную скорость (первую производную), но вы говорите: «немедленно замедляется», поэтому я не делал этого здесь.
  3. Обратите внимание, что период и амплитуда колебаний не будут точно такими, как указано при применении демпфирования, но это, вероятно, более подробно, чем вы заботитесь.
  4. Если вам нужно выразить это как одно уравнение, вы можете сделать это, используя «функцию Хевисайда», чтобы включать и выключать вклады.

С риском сделать это тожедолго, я понял, что мог бы сделать gif в GIMP, вот как это выглядит:

enter image description here

Я могу опубликовать полный код, чтобы сделать графики, если есть интерес, нов основном я просто вызываю Y с разными значениями D и T0 для каждого временного шага.Если бы я сделал это снова, я мог бы увеличить демпфирование (то есть уменьшить Tc), но это немного хлопотно, поэтому я оставляю это как есть.

5 голосов
/ 14 сентября 2011

Я думал так же, как @ 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, чтобы правильно их обрабатывать.

...