如何计算负加速度?

10

我正在实现触摸屏UI的滚动行为,但目前太累了,无法理解一些被认为是微不足道的数学问题:

y (distance/velocity)
|********
|        ******
|              ****
|                  ***
|                     ***
|                        **
|                          **
|                            *
|                             *
-------------------------------- x (time)

f(x)->?

此UI允许用户在任何方向上拖拽和“投掷”视图,并在释放手指后使其持续滚动一段时间。它有一种动量,取决于用户在抬起手指之前拖动的速度。

因此,我有一个起始速度(v0),并且每20ms,我按照与当前速度相关的数量滚动。随着每次滚动迭代,我会稍微降低速度,直到速度低于阈值时停止。当我通过固定量(线性)递减它时,它看起来不对劲,因此我需要建立一个负加速度模型,但无法想出一个合理的简单公式来计算必须在每次迭代中降低速度的数量。

更新:

谢谢你们到目前为止的回复,但是我仍然没有从反馈中得出一个令人满意的函数。我可能没有描述所需的解决方案足够好,因此我将尝试给出一个现实世界的例子,应该说明我想要进行的计算类型:

假设有一辆汽车在某条街道上行驶,驾驶员将刹车踩到最大,直到汽车停下来。驾驶员会在同一辆汽车上多次这样做,但以不同的速度开始刹车。当汽车减速时,我希望能够仅基于其当前速度精确地计算它将在一秒钟后拥有的速度。对于这个计算,汽车在刹车时的速度并不重要,因为所有环境因素保持不变。当然,公式中会有一些常数,但当汽车降至例如30 m/s时,在下一秒中它将走相同的距离,无论驾驶员开始刹车时它是100m/s还是50m/s驾驶。因此,自撞击刹车以来的时间也不会成为函数的参数。在某种速度下的减速度总是相同的。

在忽略空气阻力等复杂影响的情况下,如何在这种情况下计算一秒钟后的速度,假设某些任意常数用于减速、质量、摩擦或其他?我只关心动能及其由刹车产生的摩擦消散。

更新2:

现在我看到汽车的加速度将是线性的,这实际上不是我所要寻找的内容。我会清理并尝试明天的新建议。感谢到目前为止的建议。


我认为维基百科上有关阻力的文章可能是一个很好的资源:http://en.wikipedia.org/wiki/Drag_(physics)。其中“低雷诺数”部分似乎比较合适,而且相对简单。 - Chris Farmer
每次滚动迭代时,我都会稍微降低速度,直到它降至低于阈值时我才停止它。如果我按固定量(线性)递减它,它看起来就不对劲了,所以我需要建模一个负加速度——这确实是一个负加速度,只是一个恒定的负加速度。 - mlvljr
是的,你说得对mlvljr。我已经注意到了。显然这并不是我正在寻找的模型,但是我继续尝试我的实现后似乎发现了一些看起来相当不错并且具有我所需特征(即不依赖于时间)的东西。 - x4u
只是一个建议:您可能想尝试常数时间解决方案,从用户界面的角度来看更好。例如,您可能希望滚动始终在一秒半后停止,其中一秒是线性减速(到初始速度的90%),只有最后半秒是指数减速。这会给人一种不错的“滑行”感觉。 - Timo
15个回答

8

[简短回答(假设使用C语言语法)]

double v(double old_v, double dt) {
    t = t_for(old_v); 
    new_t = t - dt; 
    return (new_t <= 0)?0:v_for(t);
} 

double t_for(double v)double v_for(double t)是一个从速度到时间的双向映射函数(在数学意义上),该函数是任意的,但有一个限制条件,即它是单调的,并且定义为v >=0(因此有一个点在v=0处)。以下是一个示例:

double v_for(double t) { return pow(t, k); }
double t_for(double v) { return pow(v, 1.0/k); }

具体目标如下:

  1. 找到一个函数 v(t+dt)=f(v(t),dt),它接受当前速度值 v 和时间步长 dt,并返回在时刻 t+dt 的速度(由于已知参数 v(t) 且作为参数提供,因此不需要实际指定 t,而 dt 只是时间差)。换句话说,任务就是要实现一个具有特定属性的例程 double next_v(curr_v, dt);(见下文)。

  2. [请注意] 所涉及的函数具有一个有用的(且期望的)特性,即不考虑之前的速度变化“历史”,它将返回相同的结果。这意味着,例如,如果连续速度系列为 [100、50、10、0](对于起始速度 v0=100),任何其他大于该序列的序列都将有相同的“尾部”:[150、100、50、10、0](对于起始速度 v0=150),等等。换句话说,无论起始速度如何,所有速度-时间图形实际上都是彼此的副本,只是沿时间轴各自偏移其自身的值(请参见下面的图形,注意在 t=0.0t=2.0 之间的部分绘图是相同的)

  3. 此外,加速度 w(t)=dv(t)/dt 必须是时间 t 的降序函数(为了实现视觉上令人愉悦和“直观”的移动 GUI 对象的行为模拟)。

提出的想法是:

  1. k>1 会使随着时间的推移减速度按模减小。
  2. k<1 会使随着时间的推移减速度按模增加。
  3. k=1 将产生恒定的减速度。
  1. 首先,您需要选择一个具有所需属性的单调速度函数(在您的情况下,它是逐渐减小的加速度,尽管如下例所示,使用“加速”的函数更容易)。该函数不得具有上限,以便您可以将其用于任何大的速度值。此外,它必须有一个速度为零的点。一些例子是:v(t) = k*t(这里加速度k是恒定的,与您的情况不完全相同),v=sqrt(-t)(这个没问题,在区间t <= 0上定义)。

  2. 然后,对于任何给定的速度,您可以在上述函数的图形上找到具有该速度值的点(由于函数没有边界,并且只有一个单调的点,因此会有一个点),向速度值较小的方向前进时间增量,从而获得下一个速度。迭代将逐渐(不可避免地)将您带到速度为零的点。

  3. 基本上就是这样了,甚至不需要产生一些“最终”公式,时间值或初始(而不是当前)速度的依赖关系消失了,编程变得非常简单

对于两种简单情况,这个小的Python脚本生成了下面的图形(给定的初始速度为1.010.0),正如您所看到的,从任何给定的速度“水平线”和“向下”,图形的“行为”是相同的,这当然是因为无论您从哪个速度开始减速,相对于速度为(变成)零的点,您都沿着相同的曲线移动

import numpy
import pylab

import math


class VelocityCurve(object):
    """
    An interface for the velocity 'curve'.
    Must represent a _monotonically_ _growing_
        (i.e. with one-to-one correspondence
        between argument and value) function
        (think of a deceleration reverse-played)
    Must be defined for all larger-than-zero 'v' and 't'
    """
    def v(self, t):
        raise NotImplementedError

    def t(self, v):
        raise NotImplementedError



class VelocityValues(object):

    def __init__(self, v0, velocity_curve):
        assert v0 >= 0
        assert velocity_curve

        self._v = v0
        self._vc = velocity_curve

    def next_v(self, dt):
        t = self._vc.t(self._v)
        new_t = t - dt

        if new_t <= 0:
            self._v = 0
        else:
            self._v = self._vc.v(new_t)

        return self._v


class LinearVelocityCurve(VelocityCurve):

    def __init__(self, k):
        """k is for 'v(t)=k*t'"""
        super(LinearVelocityCurve, self).__init__()

        self._k = k

    def v(self, t):
        assert t >= 0
        return self._k*t

    def t(self, v):
        assert v >= 0
        return v/self._k


class RootVelocityCurve(VelocityCurve):

    def __init__(self, k):
        """k is for 'v(t)=t^(1/k)'"""
        super(RootVelocityCurve, self).__init__()

        self._k = k

    def v(self, t):
        assert t >= 0
        return math.pow(t, 1.0/self._k)

    def t(self, v):
        assert v >= 0
        return math.pow(v, self._k)


def plot_v_arr(v0, velocity_curve, dt):
    vel = VelocityValues(v0, velocity_curve)
    v_list = [v0]

    while True:
        v = vel.next_v(dt)
        v_list.append(v)

        if v <= 0:
            break

    v_arr = numpy.array(list(v_list))
    t_arr = numpy.array(xrange(len(v_list)))*dt

    pylab.plot(t_arr, v_arr)


dt = 0.1

for v0 in range(1, 11):
    plot_v_arr(v0, LinearVelocityCurve(1), dt)

for v0 in range(1, 11):
    plot_v_arr(v0, RootVelocityCurve(2), dt)


pylab.xlabel('Time ')
pylab.ylabel('Velocity')

pylab.grid(True)

pylab.show()

这将产生以下图表(线性图表用于线性减速(即恒定减速),“曲线”用于“平方根”情况(见上面的代码)): 请注意,要运行上述脚本,需要安装pylab、numpy和相关软件(但仅针对绘图部分,核心类不依赖任何软件,当然可以单独使用)。
附言:通过这种方法,一个人确实可以“构建”(例如,增加不同的函数以适应不同的t间隔,甚至平滑手绘(记录的)“人体工学”曲线)他喜欢的“拖拽”效果 :)

感谢您的努力。我明天会试一下这个。 - x4u
哇,没想到有自动维基百科的功能,但要小心:http://meta.stackexchange.com/questions/8654/so-is-too-eager-to-turn-my-edited-answers-into-community-wiki :)) - mlvljr

5
阅读评论后,我想改变我的答案:将速度乘以k < 1,例如k = 0.955,使其呈指数衰减。
解释(包括图表和可调节方程)如下...
我理解你原问题中的图表显示速度保持在起始值附近,然后迅速减小。但是如果你想象一本书在桌子上滑动,它会快速地远离你,然后减慢速度,最后停止。我同意@Chris Farmer的观点,正确的模型应该是与速度成比例的阻力。我将采用这个模型并推导出我上面提到的答案。提前为我的篇幅道歉,我相信更擅长数学的人可以大大简化这个过程。另外,我直接放了图表链接,链接中有一些SO解析器不喜欢的字符。现在已经修复了URL。 我将使用以下定义:
x -> time
a(x) -> acceleration as a function of time
v(x) -> velocity as a function of time
y(x) -> position as a function of time
u -> constant coefficient of drag
colon : denotes proportionality

我们知道由于阻力产生的力与速度成正比。我们也知道力与加速度成正比。

a(x) : -u v(x)        (Eqn. 1)

减号确保加速度与当前行进方向相反。
我们知道速度是加速度的积分。
v(x) : integral( -u v(x) dx )        (Eqn. 2)

这意味着速度与自身的积分成比例关系。我们知道 e^x 满足这个条件。因此,我们假设
v(x) : e^(-u x)        (Eqn. 3)

指数中的阻力系数是为了在求解方程2中的积分时,u被消除,从而回到方程3。

现在我们需要确定u的值。正如@BlueRaja所指出的那样,无论x取何值,e^x永远不会等于零。但对于足够负的x,它会趋近于零。假设我们认为原始速度的1%为“停止”(您设定的阈值),并且我们想在x=2秒内停下来(稍后可以调整)。那么我们需要求解以下方程:

e^(-2u) = 0.01        (Eqn. 4)

这导致我们进行计算

u = -ln(0.01)/2 ~= 2.3        (Eqn. 5)

让我们绘制v(x)

看起来在2秒钟内呈指数衰减到一个很小的值。 目前为止,一切都好。

我们并不一定想在GUI中计算指数。 我们知道可以轻松地转换指数基数,

e^(-u x) = (e^-u)^x        (Eqn. 6)

我们也不想用秒来跟踪时间。我们知道更新速率为20毫秒,因此让我们定义一个时间戳n,其时基为每秒50个tick。
n = 50 x        (Eqn. 7)

将方程5中的u值代入方程6,与方程7相结合,再代入方程3,我们得到

v(n) : k^n, k = e^(ln(0.01)/2/50) ~= 0.955        (Eqn. 8)

让我们使用新的时间刻度轴绘制此图表。

再次提醒,我们的速度函数与某些东西成比例,这些东西在所需的迭代次数内衰减至1%,并遵循“在摩擦力影响下滑行”的模型。现在,我们可以使用公式8将初始速度v0乘以得到任何时间步长n的实际速度:

v(n) = v0 k^n        (Eqn. 9)

请注意,在实现时,不需要跟踪v0!我们可以将闭式形式的v0 * k^n转换为递归以获得最终答案
v(n+1) = v(n)*k        (Eqn. 10)

这个答案满足了您不关心初始速度的限制——下一个速度总是可以仅使用当前速度计算得出。
值得检查一下位置行为是否合理。遵循这样的速度模型后的位置为:
y(n) = y0 + sum(0..n)(v(n))        (Eqn. 11)

方程11中的求和可以轻松使用方程9的形式进行求解。使用索引变量p:

sum(p = 0..n-1)(v0 k^p) = v0 (1-k^n)/(1-k)        (Eqn. 12)

所以我们有

y(n) = y0 + v0 (1-k^n)/(1-k)        (Eqn. 13)

让我们以 y0 = 0v0 = 1 来绘制这个图表。

因此,我们看到一个迅速远离原点的移动,然后优美地滑行到停止。我认为这张图更真实地描绘了滑行过程。

通常情况下,您可以使用以下方程来调整k

k = e^(ln(Threshold)/Time/Tickrate)        (Eqn. 14)
where:
Threshold is the fraction of starting velocity at which static friction kicks in
Time is the time in seconds at which the speed should drop to Threshold
Tickrate is your discrete sampling interval

感谢@poke演示使用Wolfram Alpha进行绘图 - 这非常棒。

旧回答

将速度乘以k<1,例如k = 0.98,使其呈指数衰减。


1
这不是我想做的相反吗?使用x*0.98,我在开始时得到更大的减少,而朝着结束时变得越来越小,但我希望它随着速度下降而更快地停止,而不是更慢。 - x4u
2
这是绝对不正确的 - 如果加速度是这样工作的,那么什么都永远不会停止! - BlueRaja - Danny Pflughoeft
1
@x4u:那么汽车类比可能是错误的。随着速度降低,汽车并不会更快地停止。它们只是感觉上如此,因为它们以百分比更快地停止。从25米/秒到5米/秒在1秒钟内减速似乎比从120米/秒到90米/秒在1秒钟内减速更快,但是20米/秒的速度下降比30米/秒的速度下降要小。 - Emilio M Bumachar
@mtrv: 最后一个图的链接有点问题。"multiple states(多个状态)"是什么意思?另外,为什么不使用一个任意但满足某些条件(例如,物理上真实的滑动)的 v(t) 函数,因为这样会简化很多事情呢? - mlvljr
此外,如果我们就适当的例程的编程接口达成一致(我个人认为这实际上是实现一个函数double velocity(double old_velocity, double time_delta)的问题),并满足某些条件,那么对我们所有人(包括OP)来说都可能会更好。 - mlvljr
显示剩余8条评论

4
当汽车减速时,我希望能够仅根据其当前速度计算出一秒后它将具有的速度。
这就是加速度的定义。例如,如果加速度为a = -9米/秒/秒,当前速度为20米/秒,则从现在起1秒后速度将为11米/秒。
换句话说,假设加速度恒定,现在与t秒后之间的速度变化Δv为
Δv = a*t
这意味着(经典物理学)给定t = 0时刻的初始速度(该速度称为v0)的任何时间t的速度公式为
v(t) = v0 + a*t

使用你在前两周微积分课上所学的知识,你也可以从上述方程得出 x(t)(汽车在时间 t 时的距离)的方程式;这将给你

x(t) = x0+ v0*t + 0.5*a*t2

(也有可能在没有微积分的情况下推导出此公式,请参见 此处


最后,如果您是为游戏而不是物理模拟(意味着您不需要完全精确的结果)而进行此操作,则希望每帧仅更改位置和速度,而不是每帧重新计算位置。为此,假设速度(和加速度)以像素每秒(每秒)为单位测量,您将希望在每个帧上执行以下操作:
velocity_new = velocity_old + acceleration/frames_per_second
position_new = position_old + velocity_old/frames_per_second

1
+1 对于最后两行代码的赞赏,它们能够干净利落地解决大多数实时集成中的速度相关问题。 - Martin Wickman

2
我也想补充一点。看起来你不想要一个恒定的(负)加速度。这将导致一个像这样的方程:
v(t) = v(0) + a*t,

其中a是负加速度,t是时间,v(t)是时间t时的速度。这样就得到了:

v(t2) - v(t1) = a(t2-t1),

这意味着对于给定的Δt,速度差等于aΔt,是一个常数。

您可能正在寻找的是“摩擦”项,它取决于当前速度。在这种情况下,速度变化率与当前速度成比例:

d v(t) / d t = -b*v(t).

解决上述问题很容易,您可以得到:v(t) = v(0) e−b t
将该方程积分,我们得到 x(t) = v(0)(1−e−b t) / b,其中 x 是位置。当 v(0) = 1,b = 0.1 时,位置图像1看起来可以使用。尝试调整 b 的值,并为方程添加比例因子可能是您想要做的事情。

1 http://www.wolframalpha.com/input/?i=plot+%281+-+1+e^%28-0.1+x%29+%29+%2F+0.1+for+x+%3D+0+to+100


+1:我也认为摩擦力是楼主所寻找的。 - Hannes Ovrén

2

看起来你正在寻找随着时间增加而增加的减速度。

尝试计算

Delta_v = -(A*t + B),选择合适的常数A和B。

t是到达那一点的总时间。

通过添加Delta_v来改变你的速度。

这基本上对应于线性负加速度。

你基本上可以选择任何随着时间增加而增加的函数(比如f(t))

然后计算

Delta_v = -f(t)

选择合适的 f(t) 可以达到您期望的效果。

以下是一些您可以使用的示例:

f(t) = At + B.
f(t) = A*exp(Bt)

当然,你需要尝试一下并找出正确的常量。

这是我尝试过的,看起来还不错(v'=v-t*0.1),但是让我困扰的是我必须跟踪时间,并且它的行为取决于我启动的速度。我认为应该可以计算出v'=f(v),以获得恒定加速度和恒定时间差。 - x4u
你不必追踪时间。DeltaV(t+dt) - Deltav(t) = -A(dt)。因此,您可以跟踪上一个DeltaV而不是时间。此外,它可能会表现得不同,因为您有v - t*0.1,而v是初始速度?(只是猜测)。 - Aryabhatta

2
您可以在每次迭代中将速度减少一个固定量。例如:您从速度为50开始,下一次迭代它是40,然后是30、20、10,最终停止。这将表示一个恒定的“摩擦力”,与速度无关,实际上非常接近现实情况(请参见维基百科上的摩擦力)。
如果您不喜欢这种外观,您需要使摩擦力与速度有关。我会假设一个线性关系friction = base-friction + (coefficient * velocity),其中系数相当小,就足够了。

+1:感谢使用摩擦力。但我认为,您需要将摩擦力与物体速度的变化(对物体施加力)联系起来,才能得出完整的答案。 - Hannes Ovrén
物体速度的变化只是“时间”乘以“摩擦力”。 - Svante

2
如果你想要在mtrw的回答中所说的增加减速度,并且对物理真实性不是非常挑剔,那么下面的方程式可能是你要找的:
V(t + dt)= V(t) - K1 + K2 x V(t)
其中V(t)为当前速度,V(t + dt)为下一个时间间隔的速度,K1和K2是你校准的常数。只需确保(K2 x Vmax)<K1,否则你会在高速时加速。
如果感觉还不对,可以尝试
V(t + dt)= V(t) - K1 + K2 x f(V(t))
其中f(x)是你选择的单调递增函数,可能是平方或平方根,取决于你想要的感觉。只需确保每个可能的V(t)都满足(K2 x f(V(t)))<K1。
(单调递增函数意味着当x增加时,f(x)始终增加)

1

非线性速度变化意味着加速度不是恒定的。非恒定加速度意味着系统受到jerk的影响。将所有加速度方程式加上“(1/6)jt3”。固定a,并给j一个小的负值,直到v达到0。


0

我会将速度降低到类似于 v=v*0.9 的程度,这样我就会有一个被认为是停止的速度。这样,物体最终会停下来,而不会像移动时那样继续消耗资源。

所以代码应该是这样的:

for(v=startingVelocity;v<1.0;v*=0.9)
{
    x+=v;
}

这将渐近地趋向于0,函数将描述一个左曲线。我们需要一个右曲线。 - Svante

0

我尝试过这个方法(在Ruby中),它有效。不确定数学是否正确,但输出看起来是对的,意味着你越接近中心,速度越快:

velocity=100;
(100.downto(0)).each { |distance_from_black_hole |  velocity=velocity+9.8/distance_from_black_hole; puts velocity; }

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