设置:
我正在使用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。
答案 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
更多,它就很清楚并且应该是平滑的或者不那么规律。