2012-09-08 35 views
6

設置一些基本演算:做使用無香蕉

我使用的無功香蕉用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。這是正確的方式去做這件事嗎?如果是這樣,我怎麼得到bTimeΔt

請參閱: 我懷疑答案使用mapAccum函數。如果是這樣,請另見my other question爲好。

+0

如果願意,可以通過使用戶chnge您BTIME的間隔,以同樣的方式作爲 aster example。 – AndrewC

+0

@AndrewC,我猜[this](https://github.com/HeinrichApfelmus/reactive-banana/blob/master/reactive-banana -wx/src/Asteroids.hs)是你想要的鏈接嗎 – huon

+0

是的0123.缺點:在低速時生澀,在高速下超載圖形引擎,醜陋我的建議回來了:使用角速度更多 – AndrewC

回答

6

編輯:爲了回答這個問題,是的,你正確地使用你正在使用的近似值,它是歐拉的一階微分方程求解方法,並且對於你的目的來說足夠準確,特別是因爲用戶不會對角速度的絕對值來判斷你的反應。減少你的時間間隔將使其更加準確,但是這並不重要。

你可以用更少,更大的步驟做到這一點(見下文),但這種方式對我來說似乎最清晰,我希望它對你有用。

爲什麼要用這個更長的解決方案呢?即使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 -accumulate上的初始時間間隔功能:

eTimeInterval :: Event t TimeInterval 
eTimeInterval = accumE (0,0) eTicker 

由於eTime是自渲染開始以來測量的,因此初始(0,0)是適當的。

最後,我們可以有我們的DeltaT事件,只把它運用(fmap平)delta的時間間隔。

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

此外,請參閱我對您的[mapAccum問題]的答案(http://stackoverflow.com/questions/12327424/how-does-reactive-bananas-mapaccum-function-work/12332814#12332814),以獲得更好的'mapAccum'解決方案製作'eDeltaT'。 – AndrewC

3

一個簡單的方法是假設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或多或少是正常的,就應該平滑。