2012-09-08 11 views
6

Setup:Facendo qualche calcolo di base utilizzando reattiva Banana

Sto usando Reattivo banana con OpenGL e ho una marcia che voglio girare. Ho i seguenti segnali:

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 

In definitiva, ho bisogno di calcolare bAngle che è poi passato alla funzione di disegno:

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

L'angolo è facile da calcolare: a = ∫v(t) dt

domanda:

I think quello che voglio fare è approssimare questo integrale come a = ∑ v Δt per ogni evento eDisplay (o più spesso se ho bisogno di farlo). È questo il modo corretto per farlo? In caso affermativo, come ottengo Δt da bTime?

Vedere anche: Ho il sospetto che la risposta utilizza la funzione mapAccum. In tal caso, vedere anche my other question.

+0

Se lo desideri, puoi evitare il calcolo facendo in modo che l'utente esegua l'intervallo del tuo bTime, allo stesso modo di asteroids esempio – AndrewC

+0

@AndrewC, credo [questo] (https://github.com/HeinrichApfelmus/reactive-banana/blob/master/reactive-banana -wx/src/Asteroids.hs) è il collegamento che si desidera? – huon

+0

Sì. . Svantaggi: a scatti a bassa velocità, sovraccarico del motore grafico ad alta velocità, brutto. Riprendo il mio suggerimento: utilizzando la velocità angolare è molto più elegante – AndrewC

risposta

6

Modifica: per rispondere alla domanda, sì, hai ragione a usare l'approssimazione che stai utilizzando, è il metodo di Eulero per risolvere un'equazione differenziale del primo ordine ed è abbastanza preciso per i tuoi scopi, in particolare dal momento che l'utente non avere un valore assoluto per la velocità angolare che giace intorno per giudicarti contro. La riduzione dell'intervallo di tempo lo renderebbe più preciso, ma non è importante.

Si può fare questo in pochi passi, più grandi (vedi sotto), ma in questo modo mi sembra più chiaro, spero che sia per voi.

Perché preoccuparsi di questa soluzione più lunga? Funziona anche quando eDisplay si verifica a intervalli irregolari, poiché calcola eDeltaT.

Diamo noi stessi un evento di tempo:

eTime :: Event t Int 
eTime = bTime <@ eDisplay 

Per ottenere DeltaT, avremo bisogno di tenere traccia del tempo di intervallo che passa:

type TimeInterval = (Int,Int) -- (previous time, current time) 

in modo che possiamo convertirli in delta:

delta :: TimeInterval -> Int 
delta (t0,t1) = t1 - t0 

Come dovremmo aggiornare un intervallo di tempo in cui si ottiene una nuova t2?

tick :: Int -> TimeInterval -> TimeInterval 
tick t2 (t0,t1) = (t1,t2) 

Quindi cerchiamo di parte si applicano che al tempo, per darci un programma di aggiornamento intervallo:

eTicker :: Event t (TimeInterval->TimeInterval) 
eTicker = tick <$> eTime 

e poi possiamo accumE -accumulate che funzionano su un intervallo di tempo iniziale:

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

Poiché eTime viene misurato dall'inizio del rendering, è opportuno un (0,0) iniziale.

Finalmente possiamo avere il nostro evento DeltaT, semplicemente applicando (fmap ping) delta nell'intervallo di tempo.

eDeltaT :: Event t Int 
eDeltaT = delta <$> eTimeInterval 

Ora abbiamo bisogno di aggiornare l'angolo, usando idee simili.

Farò un programma di aggiornamento angolo, semplicemente ruotando il bAngularVelocity in un moltiplicatore:

bAngleMultiplier :: Behaviour t (Double->Double) 
bAngleMultiplier = (*) <$> bAngularVelocity 

allora possiamo utilizzare che per fare eDeltaAngle: (edit: cambiato per (+) e convertito in Double)

eDeltaAngle :: Event t (Double -> Double) 
eDeltaAngle = (+) <$> (bAngleMultiplier <@> ((fromInteger.toInteger) <$> eDeltaT) 

ed accumulare per ottenere l'angolo:

eAngle :: Event t Double 
eAngle = accumE 0.0 eDeltaAngle 

Se ti piace one-liners, è possibile scrivere

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

ma non credo che sia terribilmente illuminante, e ad essere onesti, non sono sicuro che ho ottenuto il mio diritto fissità da quando ho non testato questo in ghci.

Naturalmente, da quando ho fatto eAngle invece di bAngle, è necessario

reactimate $ (draw gears) <$> eAngle 

al posto dell'originale

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

Inoltre, vedere la mia risposta alla [domanda mapAccum] (http://stackoverflow.com/questions/12327424/how-does-reactive-bananas-mapaccum-function-work/12332814#12332814) per una migliore soluzione 'mapAccum' fare 'eDeltaT'. – AndrewC

3

Un approccio semplice è quello di ritenere che eDisplay accade a intervalli di tempo regolari, e considera bAngularVelocity una misura relativa piuttosto che assoluta, che ti darebbe la soluzione piuttosto breve qui sotto. [Tieni presente che questo non va bene se eDisplay è fuori dal tuo controllo, o se spara visibilmente in modo irregolare, o varia in regolarità perché farà ruotare la tua marcia a velocità diverse a seconda dell'intervallo delle tue eDisplay modifiche. Avresti bisogno del mio altro (più lungo) approccio se questo è il caso.]

eDeltaAngle :: Event t (Double -> Double) 
eDeltaAngle = (+) <$> bAngularVelocity <@ eDisplay 

i.e.girare la bAngularVelocity in un evento vipera che viene generato quando si eDisplay, così poi

eAngle :: Event t Double 
eAngle = accumE 0.0 eDeltaAngle 

e infine

reactimate $ (draw gears) <$> eAngle 

Sì, approssimando l'integrale come somma è appropriato, e qui sto ulteriormente approssimando da assumendo supposizioni leggermente imprecise riguardo alla larghezza del passo, ma è chiaro e dovrebbe essere scorrevole finché il tuo eDisplay è più o meno regolare.

Problemi correlati