6

设置

我正在使用 Reactive Banana 和 OpenGL,并且我有一个想要旋转的齿轮。我有以下信号:

bTime :: Behavior t Int -- the time in ms from start of rendering
bAngularVelosity :: Behavior t Double -- the angular velocity
                                      -- which can be increase or
                                      -- decreased by the user
eDisplay :: Event t ()     -- need to redraw the screen
eKey :: Event t KeyState   -- user input

最终,我需要计算bAngle哪个是过去的绘图功能:

reactimate $ (draw gears) <$> (bAngle <@ eDisp)

角度很容易计算:a = ∫v(t) dt

问题

我想要做的是将这个积分近似a = ∑ v Δt为每个 eDisplay 事件(或者如果需要,更频繁)。这是解决这个问题的正确方法吗?如果是这样,我如何Δt得到bTime

另请参阅:我怀疑答案使用了该mapAccum功能。如果是这样,也请参阅我的其他问题

4

2 回答 2

6

编辑:回答这个问题,是的,你使用你正在使用的近似值是正确的,它是欧拉求解一阶微分方程的方法,并且对于你的目的来说足够准确,特别是因为用户没有周围的角速度的绝对值来判断你反对。减少你的时间间隔会使它更准确,但这并不重要。

你可以用更少、更大的步骤来做到这一点(见下文),但这种方式对我来说似乎是最清楚的,我希望你也是。

为什么要打扰这个更长的解决方案?即使eDisplay以不规则的时间间隔发生,这也有效,因为它计算eDeltaT.

让我们给自己一个时间事件:

eTime :: Event t Int
eTime = bTime <@ eDisplay

要获得 DeltaT,我们需要跟踪经过的时间间隔:

type TimeInterval = (Int,Int) -- (previous time, current time)

所以我们可以将它们转换为增量:

delta :: TimeInterval -> Int
delta (t0,t1) = t1 - t0

当我们得到一个新的时间间隔时,我们应该如何更新一个时间间隔t2

tick :: Int -> TimeInterval -> TimeInterval
tick t2 (t0,t1) = (t1,t2)

因此,让我们将其部分应用于时间,为我们提供一个间隔更新器:

eTicker :: Event t (TimeInterval->TimeInterval)
eTicker = tick <$> eTime

然后我们可以accumE在初始时间间隔上累积该函数:

eTimeInterval :: Event t TimeInterval
eTimeInterval = accumE (0,0) eTicker

由于 eTime 是从渲染开始开始测量的,因此初始(0,0)值是合适的。

最后,我们可以通过在时间间隔上应用 ( fmapping )来获得 DeltaT 事件。delta

eDeltaT :: Event t Int
eDeltaT = delta <$> eTimeInterval

现在我们需要更新角度,使用类似的想法。

我将制作一个角度更新器,只需将bAngularVelocity变成一个乘数:

bAngleMultiplier :: Behaviour t (Double->Double)
bAngleMultiplier = (*) <$> bAngularVelocity

然后我们可以用它来制作eDeltaAngle:(编辑:更改为(+)并转换为Double

eDeltaAngle :: Event t (Double -> Double)
eDeltaAngle = (+) <$> (bAngleMultiplier <@> ((fromInteger.toInteger) <$> eDeltaT)

并累积得到角度:

eAngle :: Event t Double
eAngle = accumE 0.0 eDeltaAngle

如果你喜欢单行,你可以写

eDeltaT = delta <$> (accumE (0,0) $ tick <$> (bTime <@ eDisplay)) where
    delta (t0,t1) = t1 - t0
    tick t2 (t0,t1) = (t1,t2)

eAngle = accumE 0.0 $ (+) <$> ((*) <$> bAngularVelocity <@> eDeltaT) = 

但我不认为这是非常有启发性的,老实说,我不确定我的修复是否正确,因为我没有在 ghci 中测试过。

当然,既然我做了eAngle而不是bAngle,你需要

reactimate $ (draw gears) <$> eAngle

而不是你原来的

reactimate $ (draw gears) <$> (bAngle <@ eDisp)
于 2012-09-08T15:05:52.423 回答
3

一种简单的方法是假设这种eDisplay情况以固定的时间间隔发生,并考虑bAngularVelocity作为相对而非绝对的衡量标准,这将为您提供下面真正相当简短的解决方案。eDisplay[请注意,如果你无法控制,或者它明显不规则地发射,或者有规律地变化,这将是不好的,因为它会导致你的齿轮随着你的eDisplay变化间隔以不同的速度旋转。如果是这样的话,你需要我的其他(更长的)方法。]

eDeltaAngle :: Event t (Double -> Double)
eDeltaAngle = (+) <$> bAngularVelocity <@ eDisplay

bAngularVelocity变成一个加法器事件,当你触发时eDisplay,那么

eAngle :: Event t Double
eAngle = accumE 0.0 eDeltaAngle

最后

reactimate $ (draw gears) <$> eAngle

是的,将积分近似为总和是合适的,在这里我通过对步宽做出可能稍微不准确的假设来进一步近似,但只要你eDisplay或多或少有规律,它就很清楚并且应该是平滑的。

于 2012-09-08T15:43:56.433 回答