使用Reactive Banana做一些基本的微积分

时间:2012-09-08 02:15:41

标签: haskell reactive-banana

设置

我正在使用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

问题

我想要做的是将每个eDisplay事件的整数近似为a = ∑ v Δt(如果需要,可以更频繁地)。这是正确的方法吗?如果是,我如何从Δt获得bTime

另见: 我怀疑答案使用mapAccum函数。如果是这样,请同时查看my other question

2 个答案:

答案 0 :(得分:6)

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

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

为什么要为这个更长的解决方案而烦恼?即使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)是合适的。

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

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)

答案 1 :(得分:3)

一种简单的方法是假设eDisplay以固定的时间间隔发生, 并认为bAngularVelocity是一个相对而非绝对的衡量标准,下面会给你一个非常简短的解决方案。 [请注意,如果eDisplay超出您的控制范围,或者它明显不规则地发射,或者有规律地变化,这是不好的,因为它会导致您的装备以{{1}的间隔以不同的速度旋转} 变化。如果是这种情况,你需要我的其他(更长)方法。]

eDisplay

即。将eDeltaAngle :: Event t (Double -> Double) eDeltaAngle = (+) <$> bAngularVelocity <@ eDisplay 转换为加法器bAngularVelocity时触发的事件,然后

eDisplay

最后

eAngle :: Event t Double
eAngle = accumE 0.0 eDeltaAngle

是的,将积分近似为和是合适的,在这里我通过对步长进行可能稍微不准确的假设来进一步逼近,但是只要你的reactimate $ (draw gears) <$> eAngle 更多,它就很清楚并且应该是平滑的或者不那么规律。