Setup:
I am using Reactive Banana along with OpenGL and I have a gear that I want to spin. I have the following signals:
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
Ultimately, I need to calculate bAngle
which is then past to the drawing function:
reactimate $ (draw gears) <$> (bAngle <@ eDisp)
The angle is easy to calculate: a = ∫v(t) dt
Question:
I think what I want to do is to approximate this integral as a = ∑ v Δt
for each eDisplay event (or more often if I need to). Is this the correct way to go about this? If so, how do I get Δt
from bTime
?
See Also:
I suspect that answer uses the mapAccum
function. If so, please also see my other question as well.
A simple approach is to assume that
eDisplay
happens at regular time intervals, and considerbAngularVelocity
to be a relative rather than absolute measure, whch would give you the really rather short solution below. [Note that this is no good ifeDisplay
is out of your control, or if it fires visibly irregularly, or varies in regularity because it will cause your gear to rotate at different speeds as the interval of youreDisplay
changes. You'd need my other (longer) approach if that's the case.]i.e. turn the
bAngularVelocity
into an adder Event that fires when youeDisplay
, so thenand finally
Yes, approximating the integral as a sum is appropriate, and here I'm further approximating by making possibly slightly inaccurate assumtions about the step width, but it's clear and should be smooth as long as your
eDisplay
is more-or-less regular.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 calculateseDeltaT
.Let's give ourselves a time event:
To get DeltaT, we'll need to keep track of the time interval passing:
so we can convert them to deltas:
How should we update a time interval when we get a new one
t2
?So let's partially apply that to the time, to give us an interval updater:
and then we can
accumE
-accumulate that function on an initial time interval: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.Now we need to update the angle, using similar ideas.
I'll make an angle updater, by just turning the
bAngularVelocity
into a multiplier:then we can use that to make
eDeltaAngle
: (edit: changed to(+)
and converted toDouble
)and accumulate to get the angle:
If you like one-liners, you can write
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 ofbAngle
, you needinstead of your original