module Vis.Vis ( Options(..)
, Antialiasing(..)
, vis
, visMovie
, visMovieImmediately
, FullState
) where
import Codec.BMP ( BMP, packRGBA32ToBMP32, writeBMP )
import Control.Concurrent ( MVar, readMVar, swapMVar, newMVar, takeMVar, putMVar, forkIO, threadDelay )
import Control.Monad ( unless, forever )
import qualified Data.ByteString.Unsafe as BS
import Data.Maybe ( fromMaybe )
import Data.IORef ( newIORef, readIORef, writeIORef )
import Data.Time.Clock ( getCurrentTime, diffUTCTime, addUTCTime )
import Data.Word ( Word8 )
import Foreign.Marshal.Alloc ( free )
import Foreign.Marshal.Array ( mallocArray )
import Foreign.Ptr ( Ptr, castPtr )
import Foreign.Storable ( sizeOf )
import qualified Graphics.UI.GLUT as GLUT
import Graphics.UI.GLUT ( Capability(..), ClearBuffer(..), Color4(..), ColorMaterialParameter(..)
, ComparisonFunction(..), Cursor(..), DisplayMode(..), Face(..)
, Key(..), KeyState(..), Light(..), Modifiers(..), Position(..)
, ShadingModel(..), Size(..)
, DisplayCallback, ReshapeCallback
, ($=)
)
import Graphics.GL
import Text.Printf ( printf )
import System.Exit ( exitSuccess )
import Vis.Camera ( Camera, Camera0(..), setCamera, makeCamera, cameraKeyboardMouse, cameraMotion )
import Vis.VisObject ( VisObject(..), drawObjects, setPerspectiveMode )
import qualified Vis.GlossColor as GC
type FullState a = (a, Float)
data Antialiasing =
Aliased
| Smoothed
| Multisampled Int
deriving (Eq, Show, Ord)
data Options =
Options
{ optBackgroundColor :: Maybe GC.Color
, optWindowSize :: Maybe (Int,Int)
, optWindowPosition :: Maybe (Int,Int)
, optWindowName :: String
, optInitialCamera :: Maybe Camera0
, optAntialiasing :: Antialiasing
} deriving Show
myGlInit :: Options -> IO ()
myGlInit opts = do
let displayMode = [ DoubleBuffered, RGBAMode, WithDepthBuffer ] ++
case optAntialiasing opts of
Multisampled numSamples -> [ GLUT.Multisampling
, GLUT.WithSamplesPerPixel numSamples
]
_ -> []
GLUT.initialDisplayMode $= displayMode
Size x y <- GLUT.get GLUT.screenSize
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)
case optAntialiasing opts of
Aliased -> do
GLUT.lineSmooth $= Disabled
GLUT.pointSmooth $= Disabled
GLUT.multisample $= Disabled
Smoothed -> do
GLUT.hint GLUT.LineSmooth $= GLUT.Nicest
GLUT.hint GLUT.PointSmooth $= GLUT.Nicest
GLUT.lineSmooth $= Enabled
GLUT.pointSmooth $= Enabled
GLUT.multisample $= Disabled
Multisampled _ -> do
GLUT.lineSmooth $= Disabled
GLUT.pointSmooth $= Disabled
GLUT.multisample $= Enabled
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 ]
GLUT.preservingMatrix $ do
setCameraFun
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
-> Double
-> a
-> (FullState a -> IO a)
-> (FullState a -> IO (VisObject b, Maybe Cursor))
-> (a -> IO ())
-> Maybe (a -> Key -> KeyState -> Modifiers -> Position -> a)
-> Maybe (a -> Position -> a)
-> Maybe (a -> Position -> a)
-> IO ()
vis opts ts x0 userSimFun userDraw userSetCamera
userKeyMouseCallback userMotionCallback userPassiveMotionCallback = do
_ <- GLUT.getArgsAndInitialize
myGlInit opts
let fullState0 = (x0, 0)
stateMVar <- newMVar fullState0
visReadyMVar <- newMVar False
_ <- forkIO $ simThread stateMVar visReadyMVar userSimFun ts
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
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'
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
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
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
else threadDelay usRemaining
movieSimThread :: [VisObject a] -> MVar ([VisObject a], Camera) -> MVar Bool -> Double -> IO ()
movieSimThread objects0 stateMVar visReadyMVar ts = do
let waitUntilDisplayIsReady :: IO ()
waitUntilDisplayIsReady = do
visReady <- readMVar visReadyMVar
unless visReady $ do
threadDelay 10000
waitUntilDisplayIsReady
waitUntilDisplayIsReady
t0 <- getCurrentTime
lastTimeRef <- newIORef t0
forever $ do
currentTime <- getCurrentTime
lastTime <- GLUT.get lastTimeRef
let usRemaining :: Int
usRemaining = round $ 1e6*(ts realToFrac (diffUTCTime currentTime lastTime))
if usRemaining <= 0
then do
lastTimeRef $= addUTCTime (realToFrac ts) lastTime
let getNextState = do
state <- readMVar stateMVar
let next = case state of
(_:xs, cs) -> (xs, cs)
([], cs) -> (objects0, cs)
return next
putState x = swapMVar stateMVar x
nextState <- getNextState
_ <- nextState `seq` putState nextState
GLUT.postRedisplay Nothing
else threadDelay usRemaining
visMovie
:: forall b
. Real b
=> Options
-> (Int -> FilePath)
-> Double
-> [VisObject b]
-> Maybe Cursor
-> IO ()
visMovie = visMovie' False
visMovieImmediately
:: forall b
. Real b
=> Options
-> (Int -> FilePath)
-> Double
-> [VisObject b]
-> Maybe Cursor
-> IO ()
visMovieImmediately = visMovie' True
visMovie'
:: forall b
. Real b
=> Bool
-> Options
-> (Int -> FilePath)
-> Double
-> [VisObject b]
-> Maybe Cursor
-> IO ()
visMovie' startImmediately opts toFilename ts objectsToDraw maybeCursor = do
_ <- GLUT.getArgsAndInitialize
myGlInit opts
let defaultCam =
Camera0 { phi0 = 60
, theta0 = 20
, rho0 = 7}
cameraState0 = makeCamera $ fromMaybe defaultCam (optInitialCamera opts)
areWeDrawingRef <- newIORef False
stateMVar <- newMVar (objectsToDraw, cameraState0)
visReadyMVar <- newMVar startImmediately
_ <- forkIO $ movieSimThread objectsToDraw stateMVar visReadyMVar ts
let makePictures :: VisObject b -> Camera -> IO ()
makePictures visobj cam = do
GLUT.clear [ ColorBuffer, DepthBuffer ]
GLUT.preservingMatrix $ do
setCamera cam
drawObjects $ (fmap realToFrac) visobj
case maybeCursor of
Nothing -> return ()
Just cursor -> GLUT.cursor $= cursor
GLUT.flush
GLUT.swapBuffers
_ <- swapMVar visReadyMVar True
GLUT.postRedisplay Nothing
screenShot :: Int -> Camera -> (VisObject b, Int) -> IO ()
screenShot n camera (visobj, imageNumber) = do
size@(Size width height) <- GLUT.get GLUT.windowSize
let pos = Position 0 0
ubytePtr <- mallocArray (fromIntegral (4*width*height)) :: IO (Ptr GLubyte)
let pixelData = GLUT.PixelData GLUT.RGBA GLUT.UnsignedByte ubytePtr
makePictures visobj camera
GLUT.finish
GLUT.readPixels pos size pixelData
let wordPtr :: Ptr Word8
wordPtr
| sizeOf (0 :: GLubyte) == sizeOf (0 :: Word8) = castPtr ubytePtr
| otherwise = error "GLubyte size /= Word8 size"
bs <- BS.unsafePackCStringFinalizer
wordPtr (fromIntegral (4*width*height)) (free ubytePtr)
let bmp :: BMP
bmp = packRGBA32ToBMP32 (fromIntegral width) (fromIntegral height) bs
let filename = toFilename imageNumber
percent :: Double
percent = 100 * fromIntegral imageNumber / fromIntegral n
printf "writing \"%s\" (%d / %d == %6.2f %%) ...\n" filename imageNumber n percent
writeBMP filename bmp
drawFun = do
areWeDrawing <- readIORef areWeDrawingRef
(state,cam) <- readMVar stateMVar
if areWeDrawing
then do let n = length objectsToDraw
state' <- takeMVar stateMVar
mapM_ (screenShot n cam) (zip objectsToDraw [0..])
putStrLn "finished writing files"
putStrLn "you might want to try some command like:"
putStrLn "\"ffmpeg -framerate 50 -i data/movie.%03d.bmp -c:v libx264 -r 30 -pix_fmt yuv420p out.mp4\""
putMVar stateMVar state'
writeIORef areWeDrawingRef False
else do let visobj = case (state, objectsToDraw) of
(y:_, _) -> y
([], y:_) -> y
([], []) -> VisObjects []
makePictures visobj cam
exitOverride k0 k1 _k2 _k3 = case (k0,k1) of
(Char '\27', Down) -> exitSuccess
(Char ' ', Down) -> writeIORef areWeDrawingRef True
_ -> do
(state0', cs) <- takeMVar stateMVar
putMVar stateMVar (state0', cameraKeyboardMouse cs k0 k1)
GLUT.postRedisplay Nothing
motionCallback' pos = do
(state0', cs) <- takeMVar stateMVar
putMVar stateMVar (state0', cameraMotion cs pos)
GLUT.postRedisplay Nothing
GLUT.displayCallback $= drawFun
GLUT.reshapeCallback $= Just reshape
GLUT.keyboardMouseCallback $= Just exitOverride
GLUT.motionCallback $= Just motionCallback'
GLUT.mainLoop