动画与初始速度

9

我已经尝试了几天来解决这个问题,但我一定是漏掉了什么。

已知变量:
vi = 初始速度
t = 动画持续时间
d = 距离。
末速度应始终为零

我想要创建的函数:D(0...t) = 给定时间的当前距离

利用这些信息,我希望能够创建一个具有变速(缓入/缓出)的平滑动画曲线。

动画必须能够从初始速度中缓入。

动画必须确切地持续 t 秒,并且必须恰好移动 d 个单位。

曲线应倾向于平均速度,在曲线的开始和结束部分加速。

我可以接受额外的配置变量。

我能想到的最好方法是不考虑初始速度。我希望有更聪明的人能帮助我。 ;)

谢谢!

p.s. 我正在使用 ECMAScript 变体


它会减速到零速度并停止吗? - Jason Punyon
你是在寻找一个通用的函数来完成这个任务,还是需要在特定的编程语言/环境中实现? - ysap
@ysap:虽然标签中有语言,但伪代码始终是好的。 @abustin:除了需要知道最终速度(如Jason所问),定义“平滑”的方式也很重要 - 特别是,只有三个段:恒定正加速度、零和恒定负加速度是否可以接受? - Cascabel
@Jason Punyon - 哎呀..是的,最终速度应该为0。@ysap - 一些伪代码JavaScript/ActionScript会是理想的。我最初尝试解决这个问题时使用了3个段落,但我想知道是否有更优雅的方法。 - abustin
5个回答

6
这里提供一种不同的解决方案,其中没有任何时间间隔,速度保持恒定。相反,时间的函数是二阶多项式,加速度在时间上是线性的(开始为正,结束为负)。也许你可以试试这个方法。
让我稍微改一下你的变量名:
T = 最终时间 = 动画持续时间 V = 初始速度(>0) D = 总距离(>0)
我们正在寻找一个平滑的函数v(t)(时间的速度函数),使得:
v(0) = V v(T) = 0 从0到T的v(t)dt的积分是D
通过一个(凹)二次多项式,我们可以满足所有三个约束条件。因此,让
v(t) := at^2 + bt + c
并解出a、b、c。第一个约束条件v(0) = V立即给出c = V。 第二个约束条件为
aT^2 + bT + V = 0
另一方面,v(t)的积分是d(t) = 1/3 a t^3 + 1/2 b t^2 + Vt(这是到时间t所覆盖的距离),因此第三个约束条件为
d(T) = 1/3 a T^3 + 1/2 b T^2 + VT = D
最后两个方程看起来有些混乱,但它们只是两个未知数a、b的线性方程,应该很容易解决。如果我的计算正确,最终结果是
a = 3V/T^2 - 6D/T^3,b = 6D/T^2 - 4V/T
如果你用a、b、c代替d(t)的表达式,就可以得到时间的覆盖距离。

这个答案对我有很大的帮助,但它可以更易读。你在最后一段中说“用a、b、c代替d(t)的表达式”,但实际上你并没有列出一个。也许使用latex数学公式可以使它更易读?而且值得指出的是,持续时间T可以设置为1,稍后由代码进行调整,这使得某些表达式变得更简单(因为它们将各自除以1的幂)。一些示例图也会很有启发性。你提到多项式是凹的。总是吗?为什么? - tremby

1

我相信您想要分三部分解决问题。

首先,您需要解决完成在时间T内完成距离所需的最小速度。

这很简单(D/t)= v(min)。

它假定从v(initial)到v(min)瞬间加速,然后在开始和结束时有0秒的时间段进行减速。

例如,假设您的v(i)为5px/s。您想要在10秒内完成100px的移动。

v(min) = 100px/10s = 10px/s

其次,您希望从v(initial)到v(min)平稳加速。这将需要一定的时间t(acc)。假设加速和减速是相等的,那么您只需计算其中一个,然后乘以2即可。我们可以称描述加速期间行驶距离的函数为D(accel)。

让我们先保持简单,假设我们希望加速持续1秒钟。

因此,您的总行驶距离方程式将为 D(total) = D(accel) + D(v(max))

当您知道D(accel)总共需要2秒时,您可以计算

D(accel) = (V(ini) + V(max))/2 * (2秒)

以及

D(v(max)) = V(max) * 8秒

解出 V(max) 我们得到

100像素 = D(accel) + D(v(max))

100像素 = (5像素/秒 + VMax)/2*(2秒) + VMax*8秒

100像素 = 5像素 + (Vmax*1秒) + Vmax*8秒

95像素 = 9Vmax*秒

VMax = 95像素/9秒

VMax = 10.556像素/秒

现在,您可以返回并用定义加速窗口为整个时间段的百分比或其他方式的公式替换您的1秒加速窗口。

还要注意,为了动画效果,您需要将10.556像素/秒分解为每帧像素移动,并适当计算时间。


请有人编辑我的混淆使用 VMAX 和 v(max) 以增强清晰度。我必须赶时间! - Zak
另外请注意,我猜你实际上从来不需要v(min),我只是计算它以确保我的v(max)是合理的,并且大于v(min)。 - Zak

0
Federico的解决方案很好,但我发现线性加速度解决方案的加速度有点突然,所以我最终采用了双抛物线解决方案,其中加速度是恒定的,先向一个方向,然后向另一个方向,直到物体以0速度结束。 (我尝试过使用可变的起点和终点来解决它,但这太困难了。相反,我的实现只是在通过函数之前缩放输入和输出。)
数学是高中的东西,但为了完整起见,我会详细介绍一下。
给定v,初始速度,我们有两个抛物线,左边和右边 ly = m(t - ax) ^ 2 + ay,其中t是时间输入,范围从0到1,ax,ay和m是我们需要找到的常数,给定v。
ry = -m(t - 1) ^ 2 + 1
请注意,它们都将m作为其陡度,因为它们以相同的速率加速。 ry使用-m,因为它朝另一个方向加速。
我们有许多约束条件要解决
  • ly(0) = 0,t = 0时的值为0
  • ly'(0) = v,方程的微分(速度)在t = 0时为v(给定的初始速度)
  • ly(h) = ry(h),两个抛物线在某个给定的中点连接(实际上不是中点,除非v = 0)
  • ly'(h) = ry'(h),速度在那个中点相同,没有突然的抖动

我尝试了很多方法,但最终似乎唯一的方法是解出关于v的m的公式。我们得到公式mm + m(v - 2) - (vv)/4。应用二次公式,我们得到m = ((2 - v) ± sqrt(2vv - 4v + 4))/2 这意味着要么

m = ((2 - v) + sqrt(2v*v - 4v + 4))/2

或者

m = ((2 - v) - sqrt(2v*v - 4v + 4))/2

我们发现可以通过查看初始速度来决定它是哪一个。

let sqrt_part = Math.sqrt(2*sq(initial_velocity) - 4*initial_velocity + 4)
let m = (2 - initial_velocity + (initial_velocity < 2 ? sqrt_part : -sqrt_part))/2

从那里开始,其余的变量(ax、ay和h)很容易计算出来。

这里有一个公式的 Rust 实现 https://gist.github.com/makoConstruct/df173c3a4e0854b535869fdc2acdeeb1

Rust 的语法非常普通,因此您不会在将其转换为 JS 方面遇到太多麻烦。欢迎在评论中发布您的端口。


0

使用恒定加速度:

定义:

Vi - Initial velocity
Va - Average velocity
Vo - Ending velocity
D  - Total distance to be traveled
T  - Total time for travel
t1 - Acceleration time from beginning to Va
t2 - Acceleration time from Va to Vo

需要解决的方程式为:

(Vi+Va)/2*t1 + Va*(T-t2-t1) + (Va+Vo)/2*t2 = D

你应该决定初始加速度(t1)和最终加速度(t2)所需的时间,然后只剩下一个未知量 -> Va,这可以很容易地解决。
编辑:根据时间找到距离的函数:
嗯,现在你知道了速度,很容易算出行驶的距离:
D(t) = Vi*t + 0.5*t^2*(Va-Vi)/t1                {0<t<t1}
D(t) = Va*(t-t1) + D1                           {t1<t<t3}
D(t) = Va*(t-t3)+0.5*(t-t3)^2*(Vo-Va)/t2 + D2   {t3<t<T}

其中t3=(T-t2),D1和D2是第一段和第二段结束时行驶的距离,可以从相应的函数中找到:

D1 = 0.5*(Va+Vi)*t1
D2 = D1 + Va*(t3-t1)

编辑2: 解决Va的问题:

(Vi+Va)/2*t1 + Va*(T-t2-t1) + (Va+Vo)/2*t2 = D

记住,t1和t2是您选择的问题参数。您决定物体加速和减速的运动部分比例。假设t1=t2=0.1*T。代入公式得:

(Vi+Va)/2*(0.1T) + Va*(T-(0.1T)-(0.1T)) + (Va+Vo)/2*(0.1T) = D
Va*(0.1T/2 + T-0.1T-0.1T + 0.1T/2) + Vi*(0.1T)/2 + Vo*(0.1T)/2 = D
Va*(0.9T) + Vi*(0.05T) + Vo*(0.05T) = D
Va*(0.9T) = D - Vi*(0.05T) + Vo*(0.05T)
Va = (D - (Vi + Vo)*(0.05T)) / (0.9T)

明白了吗?


我需要能够知道D在0到T的取值范围内。现在这只让我得到了一个一开始就已知的值。谢谢您的回复。 - abustin
如何防止注释破坏文本的格式?现在,您已经知道速度,很容易计算出行驶距离:
D(t) = Vi*t + 0.5*t^2*(Va-Vi)/t1 {t
D(t) = Va * (t-t1) + D1 {t1
D(t) = Va*(t-t3)+0.5*(t-t3)^2*(Vo-Va)/t2 + D2 {t2
其中t3=(T-t2),D1和D2是第一段和第二段结束时行驶的距离,可以从相应的函数中找到:
D1 = 0.5*(Va+Vi)*t1
D2 = D1 + Va*(t3-t1)
- ysap
@ysap - 感谢您的更新,我会在接下来的24小时内尝试并告诉您结果 :)。 - abustin
@ysap - “Va,这个问题很容易解决”- 嗯,有什么提示吗? ;) 我在解决它的过程中遇到了问题。 - abustin

0

abustin,既然您不喜欢三段式的解决方案,您可以尝试查看贝塞尔曲线来解决这个问题。贝塞尔曲线可以用于插值时间和距离,因此您可以在运动结束附近定义一些控制点以生成加速度,其中中间的“段”将被定义为速度接近恒定。使用样条曲线也是一种可能性。


我也在考虑同样的事情。我以前用过三次贝塞尔路径来进行非线性动画,使用它来解决这个问题会很有趣。 - abustin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接