I'm trying to rotate a Picture in Haskell, using the current time as a value for a rotate function. I've got the following main
function:
main :: IO ()
main = do
args <- getArgs
time <- round <$> getPOSIXTime
let initial' = initial time
let (w, h, display) = chooseDisplay args
let background = black
let fps = 60
play display background fps initial' (draw w h) eventHandler timeHandler
The triangle (=player) is stored inside a 'World' data type: module Model where
data World = World {
-- all kinds of World properties --
player :: Picture
}
Then I have a function initial
which initializes the World, and a function playerBody
which, given a rotation value, returns a picture of player
:
initial :: Int -> World
initial _ = World{player = playerBody 0}
playerBody :: Float -> Picture
playerBody rot = Rotate rot $ color red $ Polygon[(-10, 100), (10, 100), (0, 125)]
The draw function is defined as follows:
draw :: Float -> Float -> World -> Picture
draw _ _ world = player world
It currently simply returns the player
Picture.
Now, in my timeHandler module, I want to use the time (given to timeHandler in the main
function) to rotate player
as follows:
timeHandler :: Float -> World -> World
timeHandler time = (\world -> world {player = playerBody time} )
This doesn't work. I replaced time
with a constant value (in the timeHandler function), and that did rotate the Picture. So it seems like time
is not being updated.. what am I doing wrong?
Naturally it doesn't work, timeHandler
receives a number which, in practice, is close to the time delta since the previous frame - the docs say: "A function to step the world one iteration. It is passed the period of time (in seconds) needing to be advanced." - and presumably frame time is about constant, so naturally one would expect the output to be about constant (and a number very close to 0).
You need to collect all the deltas and add them up. If you are just concerned with time since the start of your simulation, then you don't need getCurrentTime
in main
- just add the deltas. This means you must store the time in your state. If you do need to deal with real time, I suggest you stick with UTCTime
or another abstraction which makes it clear what quantity you are manipulating:
import Graphics.Gloss
import Graphics.Gloss.Interface.Pure.Game
import Data.Time.Clock
data World = World
{ startTime
, time :: UTCTime
}
-- make explicit what units are being used
seconds2DiffTime :: Float -> NominalDiffTime
seconds2DiffTime = realToFrac
diffTime2seconds :: NominalDiffTime -> Float
diffTime2seconds = realToFrac
displayWorld World{time,startTime} =
Rotate rot $ color red $ Polygon[(-10, 100), (10, 100), (0, 125)]
where rot = diffTime2seconds $ diffUTCTime time startTime
updateWorld dt w = w {time = addUTCTime (seconds2DiffTime dt) $ time w}
main = do
t0 <- getCurrentTime
let initialState = World { time = t0, startTime = t0 }
play (InWindow "" (400,400) (400,400))
white
30
intialState
displayWorld
(const id)
updateWorld
This gets you both the elapsed time since the start of the simulation, as well as the real clock time.
Note that you should not place picture drawing code inside of timeHandler
- this function could be called much more frequently than is needed to redraw the picture, resulting in a lot of extra work. Instead do your drawing in displayWorld
as above.