【问题标题】:Doing some basic calculus using Reactive Banana使用 Reactive Banana 做一些基本的微积分
【发布时间】:2012-09-08 02:15:41
【问题描述】:

设置

我正在使用 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(或者如果需要,可以更频繁地)。这是解决这个问题的正确方法吗?如果是这样,我如何从bTime 获得Δt

另见: 我怀疑答案使用了mapAccum 函数。如果是这样,也请参阅my other question

【问题讨论】:

  • 如果您愿意,您可以通过让用户更改 bTime 的间隔来避免计算,就像 github.com/HeinrichApfelmus/reactive-banana/blob/master/…> 示例一样。
  • @AndrewC,我猜this是你想要的链接?
  • 是的。 。缺点:低速时抖动,高速时图形引擎过载,丑陋。我收回我的建议:使用角速度要优雅得多。
  • 我应该明确指出bTime 是挂钟时间。 AndrewC,你的第一个解决方案是原始 OpenGL 作者所做的,它确实不流畅,尤其是在处理键盘/鼠标输入时。

标签: haskell reactive-banana


【解决方案1】:

编辑:回答这个问题,是的,你使用你正在使用的近似值是正确的,它是欧拉求解一阶微分方程的方法,并且对于你的目的来说足够准确,特别是因为用户没有' 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) 是合适的。

最后我们可以通过在时间间隔上应用 (fmapping) 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)

【讨论】:

  • 另外,请参阅我对您的mapAccum question 的回答以获得更好的mapAccum 解决方案来制作eDeltaT
【解决方案2】:

一种简单的方法是假设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 更多或- 不规则。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-25
    • 2018-11-02
    • 2012-06-03
    • 2015-12-31
    • 2013-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多