not-gloss-0.7.0.1: src/Vis/Vis.hs
{-# OPTIONS_GHC -Wall #-}
module Vis.Vis ( Options(..)
, vis
, FullState
) where
import Data.Maybe ( fromMaybe )
import Data.IORef ( newIORef )
import System.Exit ( exitSuccess )
import Data.Time.Clock ( getCurrentTime, diffUTCTime, addUTCTime )
import Control.Concurrent ( MVar, readMVar, swapMVar, newMVar, takeMVar, putMVar, forkIO, threadDelay )
import Control.Monad ( unless, forever )
import Graphics.UI.GLUT ( Capability(..), ClearBuffer(..), Color4(..), ColorMaterialParameter(..)
, ComparisonFunction(..), Cursor(..), DisplayMode(..), Face(..)
, Key(..), KeyState(..), Light(..), Modifiers(..), Position(..)
, ShadingModel(..), Size(..)
, DisplayCallback, ReshapeCallback
, ($=)
)
import qualified Graphics.UI.GLUT as GLUT
import Graphics.Rendering.OpenGL.Raw
import Vis.VisObject ( VisObject(..), drawObjects, setPerspectiveMode )
import qualified Vis.GlossColor as GC
-- | user state and internal states
type FullState a = (a, Float)
data Options =
Options
{ -- ^ optional background color
optBackgroundColor :: Maybe GC.Color
-- ^ optional (x,y) window size in pixels
, optWindowSize :: Maybe (Int,Int)
-- ^ optional (x,y) window origin in pixels
, optWindowPosition :: Maybe (Int,Int)
-- ^ window name
, optWindowName :: String
} deriving Show
myGlInit :: Options -> IO ()
myGlInit opts = do
GLUT.initialDisplayMode $= [ DoubleBuffered, RGBAMode, WithDepthBuffer ]
Size x y <- GLUT.get GLUT.screenSize
putStrLn $ "screen resolution " ++ show x ++ "x" ++ show y
let intScale d i = round $ d*(realToFrac i :: Double)
x0 = intScale 0.3 x
xf = intScale 0.95 x
y0 = intScale 0.05 y
yf = intScale 0.95 y
(xsize, ysize) = fromMaybe (xf - x0, yf - y0) (optWindowSize opts)
(xpos, ypos) = fromMaybe (x0,y0) (optWindowPosition opts)
GLUT.initialWindowSize $= Size (fromIntegral xsize) (fromIntegral ysize)
GLUT.initialWindowPosition $= Position (fromIntegral xpos) (fromIntegral ypos)
_ <- GLUT.createWindow (optWindowName opts)
case optBackgroundColor opts of
Nothing -> GLUT.clearColor $= Color4 0 0 0 0
Just col -> GLUT.clearColor $= Color4 (realToFrac r) (realToFrac g) (realToFrac b) (realToFrac a)
where
(r,g,b,a) = GC.rgbaOfColor col
GLUT.shadeModel $= Smooth
GLUT.depthFunc $= Just Less
GLUT.lighting $= Enabled
GLUT.light (Light 0) $= Enabled
GLUT.ambient (Light 0) $= Color4 1 1 1 1
GLUT.materialDiffuse Front $= Color4 0.5 0.5 0.5 1
GLUT.materialSpecular Front $= Color4 1 1 1 1
GLUT.materialShininess Front $= 100
GLUT.colorMaterial $= Just (Front, Diffuse)
glEnable gl_BLEND
glBlendFunc gl_SRC_ALPHA gl_ONE_MINUS_SRC_ALPHA
drawScene :: MVar (FullState a) -> MVar Bool -> IO () -> (FullState a -> IO ()) -> DisplayCallback
drawScene stateMVar visReadyMVar setCameraFun userDrawFun = do
GLUT.clear [ ColorBuffer, DepthBuffer ]
-- draw the scene
GLUT.preservingMatrix $ do
-- set the camera's position and orientation
setCameraFun
-- call user function
state <- readMVar stateMVar
userDrawFun state
GLUT.flush
GLUT.swapBuffers
_ <- swapMVar visReadyMVar True
GLUT.postRedisplay Nothing
reshape :: ReshapeCallback
reshape size@(Size _ _) = do
GLUT.viewport $= (Position 0 0, size)
setPerspectiveMode
GLUT.loadIdentity
GLUT.postRedisplay Nothing
vis :: Real b =>
Options -- ^ user options
-> Double -- ^ sample time
-> a -- ^ initial state
-> (FullState a -> IO a) -- ^ sim function
-> (FullState a -> IO (VisObject b, Maybe Cursor)) -- ^ draw function, can give a different cursor
-> (a -> IO ()) -- ^ set camera function
-> Maybe (a -> Key -> KeyState -> Modifiers -> Position -> a) -- ^ keyboard/mouse callback
-> Maybe (a -> Position -> a) -- ^ motion callback
-> Maybe (a -> Position -> a) -- ^ passive motion callback
-> IO ()
vis opts ts x0 userSimFun userDraw userSetCamera
userKeyMouseCallback userMotionCallback userPassiveMotionCallback = do
-- init glut/scene
_ <- GLUT.getArgsAndInitialize
myGlInit opts
-- create internal state
let fullState0 = (x0, 0)
stateMVar <- newMVar fullState0
visReadyMVar <- newMVar False
-- start sim thread
_ <- forkIO $ simThread stateMVar visReadyMVar userSimFun ts
-- setup the callbacks
let makePictures x = do
(visobs,cursor') <- userDraw x
drawObjects $ (fmap realToFrac) visobs
case cursor' of Nothing -> return ()
Just cursor'' -> GLUT.cursor $= cursor''
setCamera = do
(state,_) <- readMVar stateMVar
userSetCamera state
-- kill sim thread when someone hits ESC
exitOverride k0 k1 k2 k3 = case (k0,k1) of
(Char '\27', Down) -> exitSuccess
_ -> case userKeyMouseCallback of
Nothing -> return ()
Just cb -> do
(state0',time) <- takeMVar stateMVar
putMVar stateMVar (cb state0' k0 k1 k2 k3, time)
GLUT.postRedisplay Nothing
motionCallback' pos = case userMotionCallback of
Nothing -> return ()
Just cb -> do
(state0',ts') <- takeMVar stateMVar
putMVar stateMVar (cb state0' pos, ts')
GLUT.postRedisplay Nothing
passiveMotionCallback' pos = case userPassiveMotionCallback of
Nothing -> return ()
Just cb -> do
(state0',ts') <- takeMVar stateMVar
putMVar stateMVar (cb state0' pos, ts')
GLUT.postRedisplay Nothing
GLUT.displayCallback $= drawScene stateMVar visReadyMVar setCamera makePictures
GLUT.reshapeCallback $= Just reshape
GLUT.keyboardMouseCallback $= Just exitOverride
GLUT.motionCallback $= Just motionCallback'
GLUT.passiveMotionCallback $= Just passiveMotionCallback'
-- start main loop
GLUT.mainLoop
simThread :: MVar (FullState a) -> MVar Bool -> (FullState a -> IO a) -> Double -> IO ()
simThread stateMVar visReadyMVar userSimFun ts = do
let waitUntilDisplayIsReady :: IO ()
waitUntilDisplayIsReady = do
visReady <- readMVar visReadyMVar
unless visReady $ do
threadDelay 10000
waitUntilDisplayIsReady
waitUntilDisplayIsReady
t0 <- getCurrentTime
lastTimeRef <- newIORef t0
forever $ do
-- calculate how much longer to sleep before taking a timestep
currentTime <- getCurrentTime
lastTime <- GLUT.get lastTimeRef
let usRemaining :: Int
usRemaining = round $ 1e6*(ts - realToFrac (diffUTCTime currentTime lastTime))
secondsSinceStart = realToFrac (diffUTCTime currentTime t0)
if usRemaining <= 0
-- slept for long enough, do a sim iteration
then do
lastTimeRef $= addUTCTime (realToFrac ts) lastTime
let getNextState = do
state <- readMVar stateMVar
userSimFun state
putState x = swapMVar stateMVar (x, secondsSinceStart)
nextState <- getNextState
_ <- nextState `seq` putState nextState
GLUT.postRedisplay Nothing
-- need to sleep longer
else threadDelay usRemaining