弹簧质量系统的阻尼效果(或者这是 ElasticEase?)

发布于 2024-12-03 23:12:48 字数 1611 浏览 2 评论 0原文

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

首先,这就是我要寻找的东西 - 一个物体,行进一定的秒数,到达某个位置并立即减速以振荡一定的秒数,以停留在应用阻尼的同一点。因此,为了形象化这一点,假设我有一个 600w/900h 的画布,并且有一个在 TranslateTransform.Y 中开始从 900px 动画到 150px 的正方形。需要 4 秒才能达到 150 像素高度(每秒 187.5 像素),在此阶段,它立即受到阻尼,仅在 0.4 秒(每秒 87.5 像素)内移动约 35 像素,到达 115 像素高度,然后向下反弹 1 秒至 163 像素高度(每秒 48 像素和 48 像素),然后反弹回 146 像素(每秒 17 像素和 17 像素)依此类推,直到振荡使其减慢至 150 像素的最终静止位置。振荡周期为16秒。

我上面描述的示例是左上角的蓝色矩形: 在此处输入图像描述

这是我提前知道的内容 - 从 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 文件。 DropBox 上的压缩文件夹。我想这个问题将更多地是一个正在进行的工作,因为人们会提出更多澄清的问题。

(免责声明:当涉及到这些东西时,我不知道我在说什么)

I'm trying to emulate an animation effect in code (almost any language would do as it appears to be math rather than language). Essentially, it is the emulation of a mass-spring system. I've been looking at WPF/Silverlight's ElasticEase and this appears to be pretty close to what I'm looking for, but not quite.

First of all, here's what I'm looking for - an object, travelling a certain number of seconds, hitting a location and immediately slowing down to ocsillate for a certain number of seconds to rest at the same point where damping was applied. So to visualize this, let's say I have a 600w/900h canvas and I have an square that begins to animate from 900px to 150px in a TranslateTransform.Y. It takes 4 seconds to reach 150px height (187.5px per second), at which stage it immediated gets damped and only travels about 35px more for 0.4 seconds (87.5px per second) to 115px height, then rebounds down for 1 second to 163px height (48px and 48px per second) and then rebounds back up to 146px (17px and 17px per second) and so on until the ocillations slow it to its final resting place of 150px. The ocillation period is 16 seconds.

The example I described above is the top left blue rectangle here:
enter image description here

Here's what I will know in advance - the pixel distance and number of seconds it takes to get from point A to point B, the number of seconds for ocillation. Things like mass don't seem to matter.

I've tried ElasticEase and the issue seems to be that I can't get the object to travel with no easing for 4 seconds and then "bounce" for the next 16 seconds. The .Springiness is also always way too much, even if I set it to be a really high number like 20.

ILSpy show's its function as:

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

I've included 2 videos and and an Excel file in a zipped folder on DropBox. I guess this question will be more of a work-in-progress as folks ask more clarifying questions.

(DISCLAIMER: I don't know what I'm talking about when it comes to much of this stuff)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

白日梦 2024-12-10 23:12:48

跳过物理学,直接进入方程式。

参数
“这是我提前知道的——像素距离 [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 提供。当然,可以更改参数以获得您想要的任何距离、振荡大小等。

在此处输入图像描述

注释

  1. 评论中的大多数人都在建议物理方法。我没有使用这些,因为如果指定某个运动,那就有点过头了——从物理开始,转到微分方程,然后计算运动,并调整参数以获得最终的东西。不妨直接进入最后一件事。也就是说,除非人们对他们想要研究的物理学有一种直觉。
  2. 通常在这样的问题中,人们想要保持连续的速度(一阶导数),但你说“立即减慢”,所以我在这里没有这样做。
  3. 请注意,应用阻尼时振荡的周期和幅度不会完全按照指定的那样,但这可能比您关心的更详细。
  4. 如果您需要将其表达为单个方程,则可以使用“Heaviside 函数”来打开和关闭贡献。

冒着让它太长的风险,我意识到我可以在 GIMP 中制作 gif,所以它看起来像这样:

如果有兴趣,我可以发布完整的代码来绘制图表,但基本上我只是为每个时间步使用不同的 D 和 T0 值调用 Y 如果我再这样做一次,我可以增加阻尼(即减少 Tc),但这有点麻烦,所以我保持原样。

Skip the physics and just go straight to the equation.

parameters:
“Here's what I will know in advance - the pixel distance [D] and number of seconds [T0] it takes to get from point A to point B, the number of seconds for oscillation [T1].” Also, I'll add as free parameters: the maximum size of oscillation, Amax, the damping time constant, Tc, and a frame rate, Rf, that is, at what times does one want a new position value. I assume you don't want to calculate this forever, so I'll just do 10 seconds, Ttotal, but there are a variety of reasonable stop conditions...

code:
Here's the code (in Python). The main thing is the equation, found in 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)

The idea is linear motion up to the point, followed by a decaying oscillation. The oscillation is provided by the sin and the decay by multiplying it by the exp. Of course, change the parameters to get any distance, oscillation size, etc, that you want.

enter image description here

notes:

  1. Most people in the comments are suggesting physics approaches. I didn't use these because if one specifies a certain motion, it is a bit over-doing-it to start with the physics, go to the differential equations, and then calculate the motion, and tweak the parameters to get the final thing. Might as well just go right to the final thing. Unless, that is, one has an intuition for the physics that they want to work from.
  2. Often in problems like this one wants to keep a continuous speed (first derivative), but you say “immediately slows down”, so I didn't do that here.
  3. Note that the period and amplitude of the oscillation won't be exactly as specified when the damping is applied, but that's probably more detailed than you care about.
  4. If you need to express this as a single equation, you can do so using a “Heaviside function”, to turn the contributions on and off.

At the risk of making this too long, I realized I could make a gif in GIMP, so this is what it looks like:

enter image description here

I can post the full code to make the plots if there's interest, but basically I'm just calling Y with different D and T0 values for each timestep. If I were to do this again, I could increase the damping (i.e., decrease Tc), but it's a bit of a hassle so I'm leaving it as is.

谜兔 2024-12-10 23:12:48

我的想法与@tom10 相同。 (我还考虑过采用 IListIEasingFunction,但从现有行为中破解所需的行为会很棘手)。

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

I was thinking along the same lines as @tom10. (I also considered an IEasingFunction which took an IList<IEasingFunction>, but it would be tricky to hack the desired behaviour out of the existing ones).

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

This is untested code, but it shouldn't be too bad to debug if there are problems. I'm not sure what attributes (if any) need to be added to the properties for the XAML editor to handle them properly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文