弹簧 - 质量系统的阻尼效应(或者是弹性系统?)

时间:2011-09-09 17:08:26

标签: wpf silverlight animation physics

我正在尝试在代码中模拟动画效果(几乎任何语言都可以,因为它似乎是数学而不是语言)。从本质上讲,它是质量弹簧系统的仿真。我一直在关注WPF / Silverlight的ElasticEase,这看起来非常接近我正在寻找的,但并不完全。

首先,这就是我正在寻找的东西 - 一个物体,行进一定的秒数,撞到一个位置并立即减速到ocsillate一定的秒数,停留在应用阻尼的同一点。所以为了想象这个,让我说我有一个600w / 900h的画布,我有一个正方形,开始在TranslateTransform.Y中从900px动画到150px。它需要4秒才能达到150px的高度(187.5px每秒),在这个阶段,它立即受到阻尼,仅在0.4秒(87.5px每秒)到115px高度后再行进约35px,然后反弹1秒到163px高度(48px和48px每秒),然后回升到146px(17px和17px每秒),依此类推,直到ocillations减慢到最后150px的休息位置。 ocirlation周期为16秒。

我上面描述的例子是左上角的蓝色矩形: enter image description here

这是我事先知道的 - 像素距离和从A点到B点所需的秒数,即ocirlation的秒数。像质量这样的东西似乎并不重要。

我已经尝试了ElasticEase,问题似乎是我无法让对象在没有缓和4秒的情况下旅行,然后在接下来的16秒内“反弹”。 .Springiness总是太过分了,即使我把它设置为像20这样的高数字。

ILSpy show的功能如下:

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

我在DropBox的压缩文件夹中添加了2个视频和一个Excel文件。我想这个问题将更多的是正在进行的工作,因为人们会提出更多澄清问题。

(免责声明:当涉及到大部分内容时,我不知道我在说什么)

2 个答案:

答案 0 :(得分:9)

略过物理学,然后直接进入等式。

参数: “这是我事先知道的 - 像素距离[D]和从点A到点B的秒数[T0],振荡的秒数[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

注释

  1. 评论中的大多数人都在提出物理方法。我没有使用这些,因为如果一个人指定某个动作,那么从物理学开始,转到微分方程,然后计算运动,并调整参数以获得最终结果。不妨直接去做最后的事情。除非,即一个人对他们想要工作的物理学有直觉。
  2. 通常在这样的问题中,人们希望保持连续的速度(一阶导数),但是你说“立即减速”,所以我没有这样做。
  3. 请注意,振动的周期和幅度不会与施加阻尼时的指定完全一致,但这可能比您关心的更详细。
  4. 如果您需要将此表达为单个等式,您可以使用“Heaviside函数”来执行此操作,以打开和关闭贡献。
  5. 冒这么长的风险,我意识到我可以在GIMP中制作一个gif,所以这就是它的样子:

    enter image description here

    如果感兴趣的话,我可以发布完整的代码来制作情节,但基本上我只是为每个时间步用不同的D和T0值调用Y.如果我再次这样做,我可以增加阻尼(即减少Tc),但这有点麻烦,所以我按原样离开。

答案 1 :(得分:5)

我和@ 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编辑器的属性中才能正确处理它们。