Edit: to answer the question, yes, you're right to use the approximation you're using, it's Euler's method of solving a first order differential equation, and is accurate enough for your purposes, particularly since the user doesn't have an absolute value for the angular velocity lying around to judge you against. Decreasing your time interval would make it more accurate, but that's not important.
You can do this in fewer, larger steps (see below), but this way seems clearest to me, I hope it is to you.
Why bother with this longer solution? This works even when eDisplay
happens at irregular intervals, because it calculates eDeltaT
.
Let's give ourselves a time event:
eTime :: Event t Int
eTime = bTime <@ eDisplay
To get DeltaT, we'll need to keep track of the time interval passing:
type TimeInterval = (Int,Int) -- (previous time, current time)
so we can convert them to deltas:
delta :: TimeInterval -> Int
delta (t0,t1) = t1 - t0
How should we update a time interval when we get a new one t2
?
tick :: Int -> TimeInterval -> TimeInterval
tick t2 (t0,t1) = (t1,t2)
So let's partially apply that to the time, to give us an interval updater:
eTicker :: Event t (TimeInterval->TimeInterval)
eTicker = tick <$> eTime
and then we can accumE
-accumulate that function on an initial time interval:
eTimeInterval :: Event t TimeInterval
eTimeInterval = accumE (0,0) eTicker
Since eTime is measured since the start of rendering, an initial (0,0)
is appropriate.
Finally we can have our DeltaT event, by just applying (fmap
ping) delta
on the time interval.
eDeltaT :: Event t Int
eDeltaT = delta <$> eTimeInterval
Now we need to update the angle, using similar ideas.
I'll make an angle updater, by just turning the bAngularVelocity
into a multiplier:
bAngleMultiplier :: Behaviour t (Double->Double)
bAngleMultiplier = (*) <$> bAngularVelocity
then we can use that to make eDeltaAngle
: (edit: changed to (+)
and converted to Double
)
eDeltaAngle :: Event t (Double -> Double)
eDeltaAngle = (+) <$> (bAngleMultiplier <@> ((fromInteger.toInteger) <$> eDeltaT)
and accumulate to get the angle:
eAngle :: Event t Double
eAngle = accumE 0.0 eDeltaAngle
If you like one-liners, you can write
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) =
but I don't think that's terribly illuminating, and to be honest, I'm not sure I've got my fixities right since I've not tested this in ghci.
Of course, since I made eAngle
instead of bAngle
, you need
reactimate $ (draw gears) <$> eAngle
instead of your original
reactimate $ (draw gears) <$> (bAngle <@ eDisp)
bTime
is wall clock time. AndrewC your first solution is what the original OpenGL author did and it was indeed not smooth, especially when processing Keyborad/Mouse inputs. – Torosian