2012-09-08 14 views
6

にセットアップを使用していくつかの基本的な計算を行います。は反応バナナ

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

質問

l と思う。私がしたいのは、この積分を各eDisplayイベントのためにa = ∑ v Δtと近似することです。これは正しいことでしょうか?もしそうなら、どうすればΔtbTimeから得ることができますか?

参考:: 答えはmapAccumです。その場合は、my other questionもご覧ください。

+0

好きな場合は、ステロイド – AndrewC

+0

@AndrewC、私は[この](https://github.com/HeinrichApfelmus/reactive-banana/blob/master/reactive-banana -wx/src/Asteroids.hs)は、あなたが望むリンクですか? – huon

+0

はい。短所:低速でジャーキー、高速でグラフィックスエンジンに過負荷、醜い。私は私の提案を取り戻す:角速度を使用してはるかにエレガント。 – AndrewC

答えて

6

編集:質問に答えるには、使用している近似を使用することが適切です。これはオイラーの一次微分方程式を解く方法であり、あなたを判断するために横たわっている角速度の絶対値がありません。時間間隔を短くすると正確になりますが、それは重要ではありません。

これを行うには、以下の手順を実行することができます(下記参照)。しかし、これは私には分かりやすいようです。

なぜこの長いソリューションに気をつけますか?これはと計算されているため、eDisplayが不定期に発生した場合でも動作します。

のは、自分自身のタイムイベントを与えてみましょう:

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最初の時間間隔にその機能を-accumulateことができます。

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) = 

を書くことができますが、私はそれがひどく照明だとは思わない、と正直に言うと、私は私がしました右以来、私は私のfixitiesを持っているかわかりませんこれはghciでテストされていません。

もちろん

、私はeAngleの代わりbAngleを作っているので、あなたの代わりにあなたの元

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

また、より良い 'mapAccum'ソリューションについては、あなたの[mapAccum question](http://stackoverflow.com/questions/12327424/how-does-reactive-bananas-mapaccum-function-work/12332814#12332814)への私の答えを見てください。 'eDeltaT 'を作ることに至りました。 – AndrewC

3

reactimate $ (draw gears) <$> eAngle 

を必要とする単純なアプローチが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が多かれ少なかれ規則的である限り、それは明らかであり、滑らかでなければなりません。

関連する問題