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

Joh*_*ler 6 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
Run Code Online (Sandbox Code Playgroud)

最终,我需要计算bAngle哪个过去到绘图函数:

reactimate $ (draw gears) <$> (bAngle <@ eDisp)
Run Code Online (Sandbox Code Playgroud)

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

问题:

我想要做的是a = ? v ?t为每个eDisplay事件近似这个积分(或者如果需要的话,更常见).这是正确的方法吗?如果是这样,我怎么?tbTime

另请参阅:我怀疑答案使用该mapAccum功能.如果是这样,请同时查看我的其他问题.

And*_*ewC 6

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

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

为什么要用这个更长的解决方案?当这个工程即使eDisplay不定期发生,因为它计算eDeltaT.

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

eTime :: Event t Int
eTime = bTime <@ eDisplay
Run Code Online (Sandbox Code Playgroud)

要获得DeltaT,我们需要跟踪传递的时间间隔:

type TimeInterval = (Int,Int) -- (previous time, current time)
Run Code Online (Sandbox Code Playgroud)

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

delta :: TimeInterval -> Int
delta (t0,t1) = t1 - t0
Run Code Online (Sandbox Code Playgroud)

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

tick :: Int -> TimeInterval -> TimeInterval
tick t2 (t0,t1) = (t1,t2)
Run Code Online (Sandbox Code Playgroud)

所以让我们部分地应用它,给我们一个间隔更新器:

eTicker :: Event t (TimeInterval->TimeInterval)
eTicker = tick <$> eTime
Run Code Online (Sandbox Code Playgroud)

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

eTimeInterval :: Event t TimeInterval
eTimeInterval = accumE (0,0) eTicker
Run Code Online (Sandbox Code Playgroud)

由于自渲染开始以来测量eTime,因此初始值(0,0)是合适的.

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

eDeltaT :: Event t Int
eDeltaT = delta <$> eTimeInterval
Run Code Online (Sandbox Code Playgroud)

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

我将制作一个角度更新器,只需将其bAngularVelocity转换为乘数:

bAngleMultiplier :: Behaviour t (Double->Double)
bAngleMultiplier = (*) <$> bAngularVelocity
Run Code Online (Sandbox Code Playgroud)

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

eDeltaAngle :: Event t (Double -> Double)
eDeltaAngle = (+) <$> (bAngleMultiplier <@> ((fromInteger.toInteger) <$> eDeltaT)
Run Code Online (Sandbox Code Playgroud)

并积累以获得角度:

eAngle :: Event t Double
eAngle = accumE 0.0 eDeltaAngle
Run Code Online (Sandbox Code Playgroud)

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

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) = 
Run Code Online (Sandbox Code Playgroud)

但我不认为这非常有启发性,说实话,我不确定我是否已经得到了我的固定,因为我没有在ghci中测试过.

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

reactimate $ (draw gears) <$> eAngle
Run Code Online (Sandbox Code Playgroud)

而不是你原来的

reactimate $ (draw gears) <$> (bAngle <@ eDisp)
Run Code Online (Sandbox Code Playgroud)