x4u*_*x4u 10 math user-interface physics touchscreen
我正在实现触摸屏用户界面的滚动行为,但我现在太累了,无法将我的思想包裹在一些所谓的微不足道的数学中:
y (distance/velocity)
|********
| ******
| ****
| ***
| ***
| **
| **
| *
| *
-------------------------------- x (time)
Run Code Online (Sandbox Code Playgroud)
F(X) - >?
UI应该允许用户在任何方向上拖动和"抛出"视图,并且即使在他从屏幕上释放手指之后也保持滚动一段时间.它的动量取决于用户在取下手指之前拖动的速度.
所以我有一个起始速度(v0),每20ms我滚动一个相对于当前速度的量.每次滚动迭代时,我都会降低速度,直到它停止时它低于阈值.当我将它减去固定量(线性)时,它看起来并不正确,所以我需要建模负加速度,但未能提出一个不错的简单公式如何计算我必须降低速度的量在每次迭代中.
更新:
感谢您到目前为止的回复,但我仍然无法从反馈中获得令人满意的功能.我可能没有足够好地描述所需的解决方案,所以我将尝试给出一个真实世界的例子来说明我想要做什么样的计算:
假设在某条街道上有一辆汽车行驶,并且驾驶员踩刹车到最大值直到汽车停下来.司机多次在同一条街上用同一辆车做这件事,但开始以不同的速度制动.当汽车减速时,我希望能够仅仅根据当前的速度计算一秒钟后的速度.对于这种计算,当驾驶员开始破坏时,汽车行驶的速度无关紧要,因为所有环境因素都保持不变.当然,公式中会有一些常数,但是当汽车下降到30米/秒时,它将在下一秒内达到相同的距离,无论是否在驾驶员开始破坏时行驶100或50米/秒.因此,打中断后的时间也不是函数的参数.一定速度下的减速度总是相同的.
如果在减速,质量,摩擦或其他任何情况下假设一些任意常数并忽略复杂的影响,如空气阻力,你如何在这种情况下一秒钟后计算速度?我只是在动能之后,由于打破汽车的摩擦而消耗它.
更新2 我现在看到汽车的加速度将是liniear,这实际上不是我想要的.我会清理它并明天尝试新的建议.感谢您的投入到目前为止.
[简答(假设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);
}
Run Code Online (Sandbox Code Playgroud)
double t_for(double v)
并且double v_for(double t)
是来自v到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); }
Run Code Online (Sandbox Code Playgroud)
哪里有:
k>1
随着时间的推移,减速度会以模数递减.k<1
随着时间的推移,减速度会以模数增加.k=1
给予持续减速.[较长的一个(有理由和情节)]
所以目标必须是:
要找到一个v(t+dt)=f(v(t),dt)
获取当前速度值v
和时间增量dt
并且此时返回速度的函数t+dt
(它不需要实际指定,t
因为v(t)
它已经知道并作为参数提供,并且dt
只是时间增量).换句话说,任务是实现double next_v(curr_v, dt);
具有特定属性的例程(见下文).
[请注意]无论先前速度变化的"历史"如何,所讨论的函数都具有返回相同结果的有用(和期望)属性.这意味着,例如,如果一系列连续速度为[100,50,10,0](对于起始速度v0=100
),任何大于此的任何其他序列将具有相同的"尾部":[150,100, 50,10,0](对于起始速度v0=150
)等.换句话说,无论起始速度,所有的速度-时间曲线将有效地通过其自身的值沿时间轴的每个只是相互抵消的副本(见图表下方,请注意线之间的曲线图部分t=0.0
和t=2.0
是相同的).
此外,加速度w(t)=dv(t)/dt
必须是时间的下降函数t
(为了视觉上令人愉悦的目的和我们在此建模的移动GUI对象的"直观"行为).
提出的想法是:
首先,您选择具有所需属性的单声速度函数(在您的情况下,它会逐渐减小加速度,但如下例所示,更容易使用"加速").此函数也不能具有上边界,因此您可以将其用于任何大的速度值.此外,它必须有一个速度为零的点.一些例子是:( v(t) = k*t
不完全是你的情况,因为减速k
在这里是恒定的),v=sqrt(-t)
(这个是好的,在间隔上定义t <= 0
).
然后,对于任何给定的速度,你可以在上面的函数图中找到具有此速度值的点(由于函数没有约束,因此它将是一个点,并且因为它是单声道的,所以只有一个点),以时间增量向小速度前进价值,从而获得下一个.迭代将逐渐(并且不可避免地)带到速度为零的点.
基本上都是这样,甚至不需要产生一些"最终"公式,依赖于时间值或初始(非当前)速度消失,编程变得非常简单.
对于两个简单的情况下,这种小python脚本产生下面的图(给定的初始速度是1.0
到10.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()
Run Code Online (Sandbox Code Playgroud)
这给出了下面的图(线性减速(即恒定减速)的线性曲线,"曲线" - 对于"平方根"的情况(参见上面的代码)):
另外请注意,要运行上面的脚本,需要安装pylab,numpy和朋友(但只有绘图部分,"核心"类依赖于任何东西,当然可以单独使用).
PS使用这种方法,人们可以真正"构建"(例如,为不同的t
间隔增加不同的功能,甚至平滑手绘(记录)"人体工程学"曲线)他喜欢的"拖动":)
阅读完评论后,我想改变我的答案:将速度乘以k <1,如k = 0.955,使其以指数方式衰减.
解释(图表和可调方程!)如下......
我将原始问题中的图解释为显示速度保持在起始值附近,然后逐渐减小.但是如果你想象一本书在桌子上滑动,它会迅速远离你,然后慢下来,然后滑行停止.我同意使用@Chris Farmer
正确的模型是与速度成正比的阻力.我将采用这个模型并得出我上面建议的答案.我提前为这个问题道歉.我相信在数学上更好的人可以大大简化这一点. 另外,我直接链接到图表,SO解析器不喜欢的链接中有一些字符.
现在修复了网址.
我将使用以下定义:
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
Run Code Online (Sandbox Code Playgroud)
我们知道由于阻力引起的力与速度成正比.我们也知道力与加速度成正比.
a(x) : -u v(x) (Eqn. 1)
Run Code Online (Sandbox Code Playgroud)
减号确保加速度与当前行进方向相反.
我们知道速度是综合加速度.
v(x) : integral( -u v(x) dx ) (Eqn. 2)
Run Code Online (Sandbox Code Playgroud)
这意味着速度与其自身的积分成正比.我们知道e^x
满足这个条件.所以我们认为
v(x) : e^(-u x) (Eqn. 3)
Run Code Online (Sandbox Code Playgroud)
指数中的阻力系数是当我们求解方程中的积分时.2将u
取消,回到公式.3.
现在我们需要弄清楚它的价值u
.正如所@BlueRaja
指出的e^x
,无论x如何,永远不等于零.但是对于足够负的x,它接近于零.让我们考虑原始速度的1%被"停止" (你的阈值的想法),并且假设我们想要在x = 2秒内停止(你可以稍后调整它).然后我们需要解决
e^(-2u) = 0.01 (Eqn. 4)
Run Code Online (Sandbox Code Playgroud)
这导致我们计算
u = -ln(0.01)/2 ~= 2.3 (Eqn. 5)
Run Code Online (Sandbox Code Playgroud)
看起来它在2秒内以指数方式衰减到一个小值.到现在为止还挺好.
我们不一定要在GUI中计算指数.我们知道我们可以轻松转换指数基数,
e^(-u x) = (e^-u)^x (Eqn. 6)
Run Code Online (Sandbox Code Playgroud)
我们也不想在几秒钟内跟踪时间.我们知道我们的更新速率为20毫秒,所以让我们定义一个时间戳n
,刻度率为50刻/秒.
n = 50 x (Eqn. 7)
Run Code Online (Sandbox Code Playgroud)
用Eqn代替u的值.5成Eqn.6,与Eqn相结合.7,代入Eqn.3,我们得到
v(n) : k^n, k = e^(ln(0.01)/2/50) ~= 0.955 (Eqn. 8)
Run Code Online (Sandbox Code Playgroud)
同样,我们的速度函数与在期望的迭代次数中衰减到1%的事物成比例,并遵循"在摩擦力的影响下滑行"模型.我们现在可以将我们的初始速度乘以v0
Eqn.8获得任何时间步的实际速度n:
v(n) = v0 k^n (Eqn. 9)
Run Code Online (Sandbox Code Playgroud)
请注意,在实现中,没有必要跟踪v0!我们可以将封闭形式v0 * k^n
转换为递归以获得最终答案
v(n+1) = v(n)*k (Eqn. 10)
Run Code Online (Sandbox Code Playgroud)
这个答案满足了你不关心初始速度的约束 - 下一个速度总是只用当前速度来计算.
值得检查以确保位置行为有意义.这种速度模型之后的位置是
y(n) = y0 + sum(0..n)(v(n)) (Eqn. 11)
Run Code Online (Sandbox Code Playgroud)
Eqn中的总和.使用方程9的形式可以容易地解决图 11的问题.使用索引变量p:
sum(p = 0..n-1)(v0 k^p) = v0 (1-k^n)/(1-k) (Eqn. 12)
Run Code Online (Sandbox Code Playgroud)
所以我们有
y(n) = y0 + v0 (1-k^n)/(1-k) (Eqn. 13)
Run Code Online (Sandbox Code Playgroud)
所以我们看到一个快速离开原点,沿着优雅的海岸停下来.我相信这张图表比原始图表更加忠实地描绘了滑动.
通常,您可以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
Run Code Online (Sandbox Code Playgroud)
(感谢@poke
演示Wolfram Alpha用于绘图 - 这非常好.)
老答复
将速度乘以k <1,如k = 0.98,使其以指数方式衰减.