与关键帧等效的加速/减速比

发布于 2024-10-13 00:37:46 字数 4665 浏览 1 评论 0原文

是否有一个公式可以翻译 AccelerationRatio 或 DecelerationRatio 至用于 SplineDoubleKeyFrame 中的 KeySpline 的贝塞尔曲线控制点?例如,“Ease Out”可能是 DecelerationRatio=0.5,但这似乎并不等同于 KeySpline="0.0,0.0 0.5,1.0"KeySpline="0.5,0 1,0.5"

这是否需要多个 SplineDoubleKeyFrame 来实现 DecelerationRatio=0.5?或者它们是一个特定的公式,使它们在单个框架中等效?

或者这不是通过 SplineDoubleKeyFrame 而是通过 EasingDoubleKeyFrame 来实现(如果是这样,EasingFunction/EasingMode/其他属性是什么)?

本质上,我试图实现 使用关键帧,因为会有多个帧针对同一属性路径并对其进行加速/减速。


更新:根据Microsoft WPF-Silverlight 比较白皮书.pdf 第 7 页:

线性插值 可以通过添加进行一些修改 加速度比和 DecelerationRatio 属性 动画片。这些属性 本质上创建三个线性 整个插值 动画以修改 启动和停止速度。为了 例如,设计师会使用这些 逐渐拥有对象的属性 加快速度或突然停止。 不幸的是,Silverlight 没有 实现这两个属性,但是 可以使用复制效果 具有线性插值的关键帧动画。

所以我想这意味着只需 3 个关键帧就可以完成,但我不知道公式是什么。


解决方案:对于其他可能需要此解决方案的人,请发布 Peter Taylor 制作的 ECMAScript 解决方案:

<html>
    <head>
        <title>Acceleration or deceleration with Bezier splines</title>
        <script type="text/javascript"><!--
            function calcBezier() {
                var res = new Array();
                var y0 = +document.getElementById("y0").value;
                var y1 = +document.getElementById("y1").value;
                var t0 = +document.getElementById("t0").value;
                var t1 = +document.getElementById("t1").value;
                var ra = +document.getElementById("ra").value;
                var rd = +document.getElementById("rd").value;

                var ta = t0 + ra * (t1 - t0);
                if (ra > 0) res.push("ta = " + ta + "<br />");
                var td = t1 - rd * (t1 - t0);
                if (rd > 0) res.push("td = " + td + "<br />");

                var vm = 2 * (y1 - y0) / (t1 + td - ta - t0);
                res.push("vm = " + vm + "<br />");

                var ya = y0 + vm * (ta - t0) / 2
                if (ra > 0) res.push("ya = " + ya + "<br />");
                var yd = y1 - vm * (t1 - td) / 2
                if (rd > 0) res.push("yd = " + yd + "<br />");

                res.push('&lt;DiscreteDoubleKeyFrame KeyTime="'+t0+'" Value="'+y0+'" /&gt;<br />');

                if (ra > 0) {
                    // y - ya = (t - ta) * vm => t = ta + (y - ya) / vm
                    var p1t = ta - (ya - y0) / vm;
                    // Scale for spline params: (t0,y0):(0,0) and (ta,ya):(1,1)
                    p1t = (p1t - t0) / (ta - t0);
                    // Lift to cubic.
                    res.push('&lt;SplineDoubleKeyFrame KeyTime="'+ta+'" Value="'+ya+'" KeySpline="'+((2*p1t)/3)+',0 '+((2*p1t+1)/3)+','+(1/3)+'" /&gt;<br />');
                }

                if (ra + rd < 1) {
                    res.push('&lt;LinearDoubleKeyFrame KeyTime="'+td+'" Value="'+yd+'" /&gt;<br />');
                }

                if (rd > 0) {
                    var q1y = 1;
                    var q1t = td + (y1 - yd) / vm;
                    q1t = (q1t - td) / (t1 - td);
                    res.push('&lt;SplineDoubleKeyFrame KeyTime="'+t1+'" Value="'+y1+'" KeySpline="'+((2*q1t)/3)+','+(2/3)+' '+((2*q1t+1)/3)+',1" /&gt;<br />');
                }

                document.getElementById("results").innerHTML = res.join("");
            }
        //-->
        </script>
    </head>
    <body>
        <p>Interpolate y from <input id="y0" /> to <input id="y1" />.</p>
        <p>Start time: <input id="t0" />; end time: <input id="t1" />.</p>
        <p>Acceleration ratio: <input id="ra" />; deceleration ratio: <input id="rd" />.</p>
        <p><input type="submit" value="Calculate" onclick="calcBezier();" /></p>
        <p id="results">&nbsp;</p>
    </body>
</html>

Is there a formula to translate AccelerationRatio or DecelerationRatio to Bezier control points for use in a KeySpline in a SplineDoubleKeyFrame? For example, an "Ease Out" may be DecelerationRatio=0.5, but this doesn't seem equivalent to KeySpline="0.0,0.0 0.5,1.0" or KeySpline="0.5,0 1,0.5".

Would this involve multiple SplineDoubleKeyFrame to achieve DecelerationRatio=0.5? Or is their a particular formula that makes them equivalent in a single frame?

Or is this not to be achieved via a SplineDoubleKeyFrame but a EasingDoubleKeyFrame instead (if so, what is the EasingFunction/EasingMode/Other Attributes)?

Essentially, I'm trying to achieve <DoubleAnimation Storyboard.TargetName="deceleratedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" DecelerationRatio="0.5" Duration="0:0:10" From="20" To="400" /> with KeyFrames as there will be multiple frames that target the same property path and accelerate/decelerate it.


UPDATE: According to the Microsoft WPF-Silverlight Comparison Whitepaper.pdf on page 7:

The linear interpolation
can be modified somewhat by adding
AccelerationRatio and
DecelerationRatio properties to the
animation. These attributes
essentially create three linear
interpolations for the entire
animation in order to modify the
starting and stopping speeds. For
example, a designer would use these
attributes to have an object gradually
pick up speed or stop suddenly.
Unfortunately, Silverlight does not
implement these two attributes, but
the effect can be duplicated using
keyframe animations with linear interpolation.

So I guess this means it can be done with just 3 key frames, but what the formula is I have no idea.


SOLUTION: For others that may come along that need this, posting the ECMAScript solution crafted by Peter Taylor:

<html>
    <head>
        <title>Acceleration or deceleration with Bezier splines</title>
        <script type="text/javascript"><!--
            function calcBezier() {
                var res = new Array();
                var y0 = +document.getElementById("y0").value;
                var y1 = +document.getElementById("y1").value;
                var t0 = +document.getElementById("t0").value;
                var t1 = +document.getElementById("t1").value;
                var ra = +document.getElementById("ra").value;
                var rd = +document.getElementById("rd").value;

                var ta = t0 + ra * (t1 - t0);
                if (ra > 0) res.push("ta = " + ta + "<br />");
                var td = t1 - rd * (t1 - t0);
                if (rd > 0) res.push("td = " + td + "<br />");

                var vm = 2 * (y1 - y0) / (t1 + td - ta - t0);
                res.push("vm = " + vm + "<br />");

                var ya = y0 + vm * (ta - t0) / 2
                if (ra > 0) res.push("ya = " + ya + "<br />");
                var yd = y1 - vm * (t1 - td) / 2
                if (rd > 0) res.push("yd = " + yd + "<br />");

                res.push('<DiscreteDoubleKeyFrame KeyTime="'+t0+'" Value="'+y0+'" /><br />');

                if (ra > 0) {
                    // y - ya = (t - ta) * vm => t = ta + (y - ya) / vm
                    var p1t = ta - (ya - y0) / vm;
                    // Scale for spline params: (t0,y0):(0,0) and (ta,ya):(1,1)
                    p1t = (p1t - t0) / (ta - t0);
                    // Lift to cubic.
                    res.push('<SplineDoubleKeyFrame KeyTime="'+ta+'" Value="'+ya+'" KeySpline="'+((2*p1t)/3)+',0 '+((2*p1t+1)/3)+','+(1/3)+'" /><br />');
                }

                if (ra + rd < 1) {
                    res.push('<LinearDoubleKeyFrame KeyTime="'+td+'" Value="'+yd+'" /><br />');
                }

                if (rd > 0) {
                    var q1y = 1;
                    var q1t = td + (y1 - yd) / vm;
                    q1t = (q1t - td) / (t1 - td);
                    res.push('<SplineDoubleKeyFrame KeyTime="'+t1+'" Value="'+y1+'" KeySpline="'+((2*q1t)/3)+','+(2/3)+' '+((2*q1t+1)/3)+',1" /><br />');
                }

                document.getElementById("results").innerHTML = res.join("");
            }
        //-->
        </script>
    </head>
    <body>
        <p>Interpolate y from <input id="y0" /> to <input id="y1" />.</p>
        <p>Start time: <input id="t0" />; end time: <input id="t1" />.</p>
        <p>Acceleration ratio: <input id="ra" />; deceleration ratio: <input id="rd" />.</p>
        <p><input type="submit" value="Calculate" onclick="calcBezier();" /></p>
        <p id="results"> </p>
    </body>
</html>

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

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

发布评论

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

评论(2

指尖微凉心微凉 2024-10-20 00:37:46

一般解决方案

更新:使用微积分的几何解释作为线下面积,我已经弄清楚如何简化推导很多。

因此,我们使用加速比 ra 和减速比 rd 从时间 t0 的 y0 到时间 t1 的 y1 进行插值。比率的定义给出了我们停止加速的时间,ta = t0 + ra * (t1 - t0),以及我们开始减速的时间,td = t1 - rd * (t1 - t0)。

我理解您引用的文档意味着它是从 t0 到 ta 的恒定加速,以及从 td 到 t1 的恒定减速。我们将达到的最大速度设为 vm。

 Speed
   |        _____________________________________
vm +       /|                                   |\
   |      /                                       \
   |     /  |                                   |  \
   |    /                                           \
   |   /    |                                   |    \
   |  /                                               \
   | /      |                                   |      \
   |/                                                   \
   +--------+-----------------------------------+--------+---- Time
   t0       ta                                  td       t1

那么平行四边形的面积就是从 t0 到 t1 所经过的距离,即 y1 - y0。平行四边形的面积是高度与平行边的平均值的乘积。因此

y1 - y0 = vm * ((t1 - t0) + (td - ta)) / 2

vm = 2 * (y1 - y0) / (t1 + td - ta - t0)

仅使用三角形的面积最后,我们可以找到当我们停止加速时,我们走了多远,ya = y(ta),当我们开始减速时,yd = y(td)。

ya = y0 + vm * (ta - t0) / 2

yd = y1 - vm * (t1 - td) / 2

最后我们为 [t0, ta] 生成一个二次贝塞尔曲线,一条直线 (ta, ya) - (td , yd) 和 [td, t1] 的二次贝塞尔曲线。

对于第一个贝塞尔曲线,我们有明显的控制点 P0 = (t0, y0) 和 P2 = (ta, ya)。为了找到 P1,我们使用 P0-P1 与曲线相切并且 P1-P2 与曲线相切的性质(一般来说,对于 n 阶曲线 P0-P1 和 P(n-1)-Pn 是相切的) 。所以 P1 位于 y=y0 与中段直线的交点处。对于另一个贝塞尔曲线也是如此:Q0 = (td, yd),Q2 = (t1, y1),Q1 位于 y=y1 与中段直线的交点处。


工作示例:

无淡入(加速比 = 0),减速比 = 0.5,t0 = 0,t1 = 10(秒),y0 = 20,y1 = 400。我认为这对应于您的具体问题。

ta = 0(我们可以省略第一个二次贝塞尔曲线); td = t1 - 0.5 * (t1 - t0) = 5。vm

= 2 * (y1 - y0) / (t1 + td - ta - t0) = 2 * (400 - 20) / (10 + 5 - 0 - 0 ) = 2 * 380 / 15 = 152 / 3 ~= 50.67。

忽略你,因为我们正在跳过贝塞尔曲线。

yd = y1 - vm * (t1 - td) / 2 = 400 - 152/3 * (10-5)/2 = 400 - 380/3 = 820/3 ~= 273.3

所以直线从 (t=0 ,y=20)至(t=5,y=273.3)。减速度贝塞尔曲线有 Q0 = (5, 273.3),Q2 = (10, 400)。

为了找到 Q1,我们将直线延伸至 y=400。该线的方程为 y - 20 = (t - 0) * (273.3 - 20) / (5 - 0),因此 t = 5 * (400 - 20) / (273.3 - 20) = 7.5。

因此,我们有直线 (0,20)-(5,273.3) 和带有控制点 (5,273.3)、(7.5,400) 和 (10,400) 的二次贝塞尔曲线。

将其转换为关键帧

但是,有一个小问题,即 Microsoft 并没有屈尊为我们提供二次样条曲线。我们必须将二次 Q0、Q1、Q2 提升为三次 Q0、(Q0 + 2 Q1) /3、(2 Q1 + Q2) / 3、Q2。

我们还必须将控制点重新调整为 0-1。如果我们首先应用重新缩放,我们就有 (0,0)-(0.5,1)-(1,1)。所以三次方是(0,0)-(0.333,0.667)-(0.667,1)-(1,1)。

我知道样条曲线,但不知道 WPF。我认为以下内容将满足您的要求:

<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="20" />
<LinearDoubleKeyFrame KeyTime="0:0:5" Value="273.333" />
<SplineDoubleKeyFrame KeyTime="0:0:10" Value="400" KeySpline="0.333,0.667,0.667,1"/>

变换的普遍性

重新缩放第一个贝塞尔曲线,我们将 (t0, y0) 映射到 (0, 0) 并将 (ta, ya) 映射到 (1, 1)。因此,我们将 (P1.t, P1.y) 映射到 ((P1.t - t0) / (ta - t0), (P1.y - y0) / (ya - y0))。但 P1 位于 y = y0 与通过 (ta, ya) 的梯度 vm 直线的交点,因此有方程 (y - ya) = (t - ta) * vm。所以 P1.y = y0 且 P1.t = ta + (y0 - ya) / vm = ta - (ya - y0) / vm。代入恒等式 ya = y0 + vm * (ta - t0) / 2,我们得到 P1.t = ta - (vm * (ta - t0) / 2) / vm = ta - (ta - t0) / 2 = (ta + t0) / 2。

因此,重新缩放后,我们将 P1 映射到 (0.5, 0)。因此,当我们将其提升为三次贝塞尔曲线时,中间控制点始终位于(1/3,0)和(2/3,1/3)。

类似地,减速样条线始终在 (1/3, 2/3) 和 (2/3, 1) 处具有缩放中间点。

General solution

Update: using the geometric interpretation of calculus as the area under a line, I've worked out how to simplify the derivation a lot.

So we're interpolating from y0 at time t0 to y1 at time t1 with acceleration ratio ra and deceleration ratio rd. The definition of the ratios give us the time at which we stop accelerating, ta = t0 + ra * (t1 - t0), and at which we start decelerating, td = t1 - rd * (t1 - t0).

I understand the documentation you quote to mean that it's constant acceleration from t0 to ta, and constant deceleration from td to t1. We'll take the maximum speed reached to be vm.

 Speed
   |        _____________________________________
vm +       /|                                   |\
   |      /                                       \
   |     /  |                                   |  \
   |    /                                           \
   |   /    |                                   |    \
   |  /                                               \
   | /      |                                   |      \
   |/                                                   \
   +--------+-----------------------------------+--------+---- Time
   t0       ta                                  td       t1

Then the area of the parallelogram is the distance travelled from t0 to t1, which is y1 - y0. The area of a parallelogram is the product of the height with the average of the parallel sides. So

y1 - y0 = vm * ((t1 - t0) + (td - ta)) / 2

or

vm = 2 * (y1 - y0) / (t1 + td - ta - t0)

Using just the area of the triangles at the end, we can find how far we've travelled when we stop accelerating, ya = y(ta), and when we start decelerating, yd = y(td).

ya = y0 + vm * (ta - t0) / 2

yd = y1 - vm * (t1 - td) / 2

Finally we produce a quadratic Bezier for [t0, ta], a straight line (ta, ya) - (td, yd), and a quadratic Bezier for [td, t1].

For the first Bezier, we have the obvious control points P0 = (t0, y0) and P2 = (ta, ya). To find P1 we use the property that P0-P1 is tangent to the curve and P1-P2 is tangent to the curve (in general, for an order-n curve P0-P1 and P(n-1)-Pn are tangent). So P1 is at the intersection of y=y0 and the straight line of the middle segment. Similarly for the other Bezier: Q0 = (td, yd), Q2 = (t1, y1), and Q1 is at the intersection of y=y1 and the straight line of the middle segment.


Worked example:

No fade in (acceleration ratio = 0), deceleration ratio = 0.5, t0 = 0, t1 = 10 (seconds), y0 = 20, y1 = 400. I think this corresponds to your specific question.

ta = 0 (and we can omit the first quadratic Bezier); td = t1 - 0.5 * (t1 - t0) = 5.

vm = 2 * (y1 - y0) / (t1 + td - ta - t0) = 2 * (400 - 20) / (10 + 5 - 0 - 0) = 2 * 380 / 15 = 152 / 3 ~= 50.67.

Ignore ya because we're skipping that Bezier.

yd = y1 - vm * (t1 - td) / 2 = 400 - 152/3 * (10-5)/2 = 400 - 380/3 = 820/3 ~= 273.3

So the straight line goes from (t=0, y=20) to (t=5, y=273.3). The deceleration Bezier has Q0 = (5, 273.3), Q2 = (10, 400).

To find Q1 we extend the straight line to y=400. The line has equation y - 20 = (t - 0) * (273.3 - 20) / (5 - 0), so t = 5 * (400 - 20) / (273.3 - 20) = 7.5.

So we have straight line (0,20)-(5,273.3) and a quadratic Bezier with control points (5,273.3), (7.5,400) and (10,400).

Translating this into your keyframe

However, there's a slight hitch, which is that Microsoft hasn't deigned to give us quadratic splines. We have to lift the quadratic Q0, Q1, Q2, to the cubic Q0, (Q0 + 2 Q1) /3, (2 Q1 + Q2) / 3, Q2.

We also have to rescale the control points to 0-1. If we apply that rescaling first, we have (0,0)-(0.5,1)-(1,1). So the cubic is (0,0)-(0.333,0.667)-(0.667,1)-(1,1).

I know splines, but not WPF. I think that the following will do what you want:

<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="20" />
<LinearDoubleKeyFrame KeyTime="0:0:5" Value="273.333" />
<SplineDoubleKeyFrame KeyTime="0:0:10" Value="400" KeySpline="0.333,0.667,0.667,1"/>

Universality of the transformation

Rescaling the first Bezier, we map (t0, y0) to (0, 0) and (ta, ya) to (1, 1). Therefore we map (P1.t, P1.y) to ((P1.t - t0) / (ta - t0), (P1.y - y0) / (ya - y0)). But P1 is at the intersection of y = y0 with the straight line of gradient vm through (ta, ya), which therefore has equation (y - ya) = (t - ta) * vm. So P1.y = y0 and P1.t = ta + (y0 - ya) / vm = ta - (ya - y0) / vm. Plugging in our identity ya = y0 + vm * (ta - t0) / 2, we get P1.t = ta - (vm * (ta - t0) / 2) / vm = ta - (ta - t0) / 2 = (ta + t0) / 2.

So rescaling we map P1 to (0.5, 0). Therefore when we lift it to a cubic Bezier, the intermediate control points are always at (1/3, 0) and (2/3, 1/3).

Similarly the deceleration spline always has its scaled intermediate points at (1/3, 2/3) and (2/3, 1).

盗琴音 2024-10-20 00:37:46

我希望我答对了你的问题。

我在 WPF 项目中测试了您的示例,并使用 DoubleAnimationUsingKeyFrames 和 SplineDoubleKeyFrame 创建了类似的效果。这是我在 Storyboard 中所做的(这是为了减速):

<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="rectangle">
    <SplineDoubleKeyFrame KeyTime="0" Value="1"/>
    <SplineDoubleKeyFrame KeyTime="0:0:3" Value="3.45" KeySpline="0,0.52,0.51,1"/>
</DoubleAnimationUsingKeyFrames>

技巧是为 SplineDoubleKeyFrame 设置贝塞尔曲线。如果您想要平滑的加速/减速,请尝试使用更接近线性形式的贝塞尔曲线。

另外:Acceleration/DecelerationRatio是针对整个动画的,而KeySpline只对KeyFrame有效。因此,如果您有一个由多个关键帧组成的动画,请考虑这一点。

I hope I got your question right.

I tested your example in a WPF project, and I created a similar effect using DoubleAnimationUsingKeyFrames and SplineDoubleKeyFrame. Here is what I did in a Storyboard (this is for deceleration):

<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="rectangle">
    <SplineDoubleKeyFrame KeyTime="0" Value="1"/>
    <SplineDoubleKeyFrame KeyTime="0:0:3" Value="3.45" KeySpline="0,0.52,0.51,1"/>
</DoubleAnimationUsingKeyFrames>

The trick is about setting the Bezier for the SplineDoubleKeyFrame. If you want a smooth acceleration/deceleration, try using a Bezier closer to a linear form.

In addition: the Acceleration/DecelerationRatio is for a whole animation, but the KeySpline is valid for only a KeyFrame. So consider this, if you have an animation consisting of multiple KeyFrames.

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