2012-09-08 11 views
6

Configuración:Haciendo un poco de cálculo básico utilizando Reactivo plátano

estoy usando Reactivo de plátano junto con OpenGL y tengo un engranaje que quiero girar. Tengo las siguientes señales:

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 

En última instancia, lo que necesito para calcular bAngle que es entonces más allá de la función de dibujo:

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

El ángulo es fácil de calcular: a = ∫v(t) dt

pregunta:

I piensa Lo que quiero hacer es aproximar esta integral como a = ∑ v Δt para cada evento eDisplay (o más a menudo si es necesario). ¿Es esta la forma correcta de hacerlo? Si es así, ¿cómo obtengo Δt desde bTime?

Ver también: Sospecho que la respuesta utiliza la función mapAccum. Si es así, también vea my other question también.

+0

Si lo desea, puede evitar el cálculo dejando que el usuario vea el intervalo de su bTime, de la misma manera que el asteroids example does. – AndrewC

+0

@AndrewC, supongo que [esto] (https://github.com/HeinrichApfelmus/reactive-banana/blob/master/reactive-banana -wx/src/Asteroids.hs) es el enlace que quería? – huon

+0

Sí. . Desventajas: desigual a velocidades lentas, sobrecargando el motor de gráficos a altas velocidades, feo. Retiro mi sugerencia: usar la velocidad angular es mucho más elegante. – AndrewC

Respuesta

6

Editar: para responder a la pregunta, sí, está en lo cierto al usar la aproximación que está usando, es el método de Euler de resolver una ecuación diferencial de primer orden, y es lo suficientemente preciso para sus propósitos, especialmente dado que el usuario no No tiene un valor absoluto para la velocidad angular que se encuentra alrededor para juzgarlo en contra. Disminuir su intervalo de tiempo lo haría más preciso, pero eso no es importante.

Puede hacer esto en menos pasos, más grandes (vea a continuación), pero de esta manera me parece más claro, espero que sea para usted.

¿Por qué molestarse con esta solución más larga? Esto funciona incluso cuando eDisplay ocurre a intervalos irregulares, porque calcula eDeltaT.

Vamos a darnos un evento de tiempo:

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

Para obtener Delta T, tendremos que hacer un seguimiento del tiempo de paso de intervalo:

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

por lo que puede convertirlos en los deltas:

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

¿Cómo debemos actualizar un intervalo de tiempo cuando tenemos una nueva t2?

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

Así que vamos a aplicar parcialmente que a la vez, para darnos un actualizador intervalo:

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

y luego podemos accumE -accumulate que funcionan en un intervalo de tiempo inicial:

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

Dado que eTime se mide desde el inicio de la representación, es apropiado un (0,0) inicial.

Finalmente podemos tener nuestro evento DeltaT, simplemente aplicando (fmap ping) delta en el intervalo de tiempo.

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

Ahora tenemos que actualizar el ángulo, usando ideas similares.

Voy a hacer un actualizador ángulo, con sólo girar el bAngularVelocity en un multiplicador:

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

entonces podemos utilizar eso para hacer eDeltaAngle: (edit: cambió a (+) y se convierte en Double)

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

y se acumulan para obtener el ángulo:

eAngle :: Event t Double 
eAngle = accumE 0.0 eDeltaAngle 

Si te gusta de una sola línea, puede escribir

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

pero no creo que eso es terriblemente iluminación, y para ser honesto, no estoy seguro de que tengo mis empotramientos derecho desde que he no probado esto en ghci.

Por supuesto, desde que hice eAngle en lugar de bAngle, necesita

reactimate $ (draw gears) <$> eAngle 

en lugar de su

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

Además, vea mi respuesta a su [pregunta MapAccum] (http://stackoverflow.com/questions/12327424/how-does-reactive-bananas-mapaccum-function-work/12332814#12332814) para obtener una solución 'mapAccum' más agradable para hacer 'eDeltaT'. – AndrewC

3

Un enfoque original simple es asumir que eDisplay ocurre a intervalos de tiempo regulares, y considere bAngularVelocity como una medida relativa en lugar de absoluta, que le daría la solución más bien corta a continuación. [Tenga en cuenta que esto no es bueno si eDisplay está fuera de su control, o si se dispara visiblemente irregularmente, o varía con regularidad porque hará que su engranaje gire a diferentes velocidades a medida que cambie el intervalo de su eDisplay. Necesitarías mi otro enfoque (más largo) si ese fuera el caso.]

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

i.e.gire el bAngularVelocity en un evento víbora que se activa siempre que eDisplay, por lo que entonces

eAngle :: Event t Double 
eAngle = accumE 0.0 eDeltaAngle 

y finalmente

reactimate $ (draw gears) <$> eAngle 

Sí, la aproximación de la integral como una suma es apropiada, y aquí estoy aproximar aún más por hacer suposiciones posiblemente poco precisas sobre el ancho del paso, pero está claro y debe ser uniforme siempre que su eDisplay sea más o menos regular.