1
votes

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 playerPicture.

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?

1
Not an answer: one thing to make absolutely clear is that the time in main and the time in timeHandler have nothing to do with each other -- in fact, they don't even have the same type. The former is a local value you got by executing getPosixTime, while the latter is a parameter in an independent function definition. They just happen to have the same name. - duplode
I'm aware of this @duplode ! However, according to the play function description, timeHandler should be given the time in seconds.. - Felix
Okay -- it's just that your phrasing in "given to timeHandler in the main function" confused me a bit. (You don't give the time to ` timeHandler` yourself; rather, Gloss picks it up by itself somewhere in the play implementation.) - duplode
In any case, if I am understanding the docs correctly, the expected behaviour for your program would be rotating the picture one degree per second (i.e. 1/60 of a degree per frame), right? - duplode
Exactly, that's what I'm expecting it to do. - Felix

1 Answers

3
votes

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.