module FWGL (
module FWGL.Input,
module FRP.Yampa,
FWGL(..),
fwgl,
mapIO,
Output,
run,
run',
runTo,
draw,
loadOBJ,
loadOBJAsync,
loadTextFileAsync,
Effect,
eff,
drawEff,
drawMEff,
fastStep,
liftIO,
liftDraw,
setSize,
setTitle,
Draw,
drawM,
drawLayer,
drawGroup,
drawObject,
setProgram,
renderLayer,
resizeViewport,
gl,
textureUniform,
textureSize,
removeGeometry,
removeTexture,
removeProgram,
runIO,
runToIO
) where
import Data.IORef
import qualified Data.HashMap.Strict as H
import Control.Applicative
import Control.Concurrent
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import Data.Vect.Float
import FWGL.Backend hiding (Texture, Program)
import FWGL.Input
import FWGL.Internal.GL (evalGL)
import FWGL.Geometry (Geometry3D)
import FWGL.Geometry.OBJ
import FWGL.Graphics.Draw
import FWGL.Graphics.Types
import FWGL.Shader.Program (Program)
import FRP.Yampa
data Output = forall a. Output Bool (Either (Effect ())
(Draw a, a -> Effect ()))
newtype Effect a = Effect (ReaderT (Canvas, BackendState) Draw a)
deriving (Functor, Applicative, Monad, MonadIO)
draw :: BackendIO => [Layer] -> Output
draw = drawM . mapM_ drawLayer
drawM :: Draw () -> Output
drawM d = Output False $ Right (d, \_ -> return ())
eff :: Effect () -> Output
eff = Output False . Left
drawEff :: BackendIO => [Layer] -> Effect () -> Output
drawEff layers eff = drawMEff (mapM_ drawLayer layers) $ const eff
drawMEff :: Draw a -> (a -> Effect ()) -> Output
drawMEff = curry $ Output False . Right
fastStep :: Effect () -> Output
fastStep = Output True . Left
liftDraw :: Draw a -> Effect a
liftDraw c = Effect . ReaderT $ const c
setSize :: BackendIO
=> Int
-> Int
-> Effect ()
setSize w h = Effect $ ask >>= \(canvas, bs) ->
liftIO $ setCanvasSize w h canvas bs
setTitle :: BackendIO => String -> Effect ()
setTitle title = Effect $ ask >>= \(canvas, bs) ->
liftIO $ setCanvasTitle title canvas bs
newtype FWGL a = FWGL (ReaderT BackendState IO a)
deriving (Functor, Applicative, Monad, MonadIO)
fwgl :: BackendIO => FWGL () -> IO ()
fwgl (FWGL a) = initBackend >>= \bs -> runReaderT a bs >> terminateBackend bs
mapIO :: (IO a -> IO b) -> FWGL a -> FWGL b
mapIO f (FWGL a) = FWGL ask >>= liftIO . f . runReaderT a
run :: BackendIO
=> SF (Input ()) Output
-> FWGL ()
run = run' $ return ()
run' :: BackendIO
=> IO inp
-> SF (Input inp) Output
-> FWGL ()
run' = runTo "canvas"
runTo :: BackendIO
=> String
-> IO inp
-> SF (Input inp) Output
-> FWGL ()
runTo dest customInput sigf =
do initCustom <- liftIO customInput
outputRef <- liftIO . newIORef . eff $ return ()
reactHandleRef <- liftIO . newIORef $ error "react before reactInit."
runToIO dest
(\w h -> writeIORef reactHandleRef =<<
reactInit (return $ initInput w h initCustom)
(\_ _ -> (>> return False) .
writeIORef outputRef)
sigf)
$ \tmdiff inp ->
do custom <- customInput
reactStateRef <- readIORef reactHandleRef
react reactStateRef
(tmdiff, Just inp { inputCustom = custom })
readIORef outputRef
where initInput w h = Input $ H.singleton Resize [
emptyEventData {
dataFramebufferSize = Just (w, h)
}]
emptyEventData = EventData {
dataFramebufferSize = Nothing,
dataPointer = Nothing,
dataButton = Nothing,
dataKey = Nothing }
runIO :: BackendIO
=> (Double -> Input () -> IO Output)
-> FWGL ()
runIO = runToIO "canvas" $ \_ _ -> return ()
runToIO :: BackendIO
=> String
-> (Int -> Int -> IO ())
-> (Double -> Input () -> IO Output)
-> FWGL ()
runToIO dest init fun = FWGL $ ask >>= \bs -> liftIO $
do (canvas, w, h) <- createCanvas dest bs
init w h
lastTimeRef <- getTime bs >>= newIORef
newSizeRef <- newIORef Nothing
drawStateVar <- drawCanvas (initState w h canvas) False canvas bs
>>= newMVar
setCanvasResizeCallback (resizeCb newSizeRef) canvas bs
setCanvasRefreshCallback (refreshCb lastTimeRef newSizeRef canvas
bs drawStateVar)
canvas bs
refreshLoop 60 canvas bs
where initState w h canvas = evalGL $ drawInit w h canvas
resizeCb newSizeRef w h = writeIORef newSizeRef $ Just (w, h)
refreshCb lastTimeRef newSizeRef canvas bs drawStateVar =
do tm <- readIORef lastTimeRef
tm' <- getTime bs
inp <- popInput () canvas bs
out <- fun ((tm' tm) * 1000) inp
writeIORef lastTimeRef tm'
cycle lastTimeRef newSizeRef canvas
bs drawStateVar out
cycle lastTimeRef newSizeRef canvas bs drawStateVar
(Output re edrawEff) =
do mNewSize <- readIORef newSizeRef
case edrawEff of
Right (drawAct, effFun) ->
do r <- drawCanvas (drawTo $
do case mNewSize of
Just (w, h) ->
do resizeViewport w h
liftIO $ writeIORef
newSizeRef
Nothing
Nothing -> return ()
drawBegin
r <- drawAct
drawEnd
return r) True canvas bs
runEffect $ effFun r
Left eff -> runEffect eff
when re $ refreshCb lastTimeRef newSizeRef
canvas bs drawStateVar
where drawTo drawAct ctx = modifyMVar drawStateVar $ \s ->
flip evalGL ctx . fmap swap $
runDraw drawAct s
runEffect (Effect e) =
drawCanvas (drawTo $ runReaderT e (canvas, bs))
False canvas bs
swap (a, b) = (b, a)
loadOBJAsync :: BackendIO
=> FilePath
-> (Either String (Geometry Geometry3D) -> IO ())
-> IO ()
loadOBJAsync fp k = loadTextFile fp $
\e -> case e of
Left err -> k $ Left err
Right str -> k . Right . geometryOBJ
. parseOBJ $ str
loadOBJ :: BackendIO => FilePath -> IO (Either String (Geometry Geometry3D))
loadOBJ fp = do var <- newEmptyMVar
loadOBJAsync fp $ putMVar var
takeMVar var
loadTextFileAsync :: BackendIO
=> FilePath
-> (Either String String -> IO ())
-> IO ()
loadTextFileAsync = loadTextFile