Elerea Chase example
====================
This is a minimal example to show how to define signals that can be
mutually recursive and can optionally depend on user input too. The
grey square accelerates towards the red square at a rate proportional
to their relative position, and it can be given a momentary impulse
with the left mouse button.
For a slightly more complex example check out `Breakout.lhs`.
> module Main where
>
> import Control.Applicative
> import Data.IORef
> import FRP.Elerea
> import Graphics.UI.GLFW as GLFW
> import Graphics.Rendering.OpenGL
>
> import Common.Utils
> import Common.Vector
The `main` function contains the whole reactive logic. Note that
`driveNetwork` is just a wrapper around the `superstep` function of
the core library, and you can see its source below in the `Utils`
module.
> main = do
> initialize
> openWindow (Size 640 480) [DisplayRGBBits 8 8 8, DisplayAlphaBits 8, DisplayDepthBits 24] Window
> windowTitle $= "Elerea Chase"
>
> (windowSize,windowSizeSink) <- external vnull
> (mousePosition,mousePositionSink) <- external vnull
> (mousePress,mousePressSink) <- external False
>
> closed <- newIORef False
> windowSizeCallback $= resizeGLScene windowSizeSink
> windowCloseCallback $= writeIORef closed True
> initGL 640 480
>
> let ballPos = integralVec vnull ballVel
> ballVel = latcher (integralVec vnull ballAcc)
> (edge mousePress)
> (integralVec <$> ballVel^+^ballPos^-^mousePosition <*> pure ballAcc)
> ballAcc = (mousePosition^-^ballPos)^*.0.3
>
> driveNetwork (render <$> windowSize <*> mousePosition <*> ballPos)
> (readInput mousePositionSink mousePressSink closed)
>
> closeWindow
The `render` function takes a snapshot of the system (window size and
the positions of the squares) and turns it into OpenGL calls. The
signal executed by the `driveNetwork` function is the time-varying
version of the IO action returned here.
> render (V w h) (V cx cy) (V ox oy) = do
> let drawSquare x y s = do
> loadIdentity
> translate $ Vector3 (x/w*2-1) (h/w-y/w*2) 0
> renderPrimitive Quads $ do
> vertex $ Vertex3 (-s) (-s) (0 :: GLfloat)
> vertex $ Vertex3 ( s) (-s) (0 :: GLfloat)
> vertex $ Vertex3 ( s) ( s) (0 :: GLfloat)
> vertex $ Vertex3 (-s) ( s) (0 :: GLfloat)
>
> clear [ColorBuffer]
>
> color $ Color4 1 0 0 (0.5 :: GLfloat)
> drawSquare cx cy 0.05
> color $ Color4 1 1 1 (0.6 :: GLfloat)
> drawSquare ox oy 0.03
>
> flush
> swapBuffers
The `readInput` function provides the driver layer. It feeds the
peripheral-bound signals and also decides when to stop execution by
returning `Nothing` instead of the time elapsed since its last call.
> readInput mousePos mouseBut closed = do
> t <- get GLFW.time
> GLFW.time $= 0
> Position x y <- get GLFW.mousePos
> mousePos (V (fromIntegral x) (fromIntegral y))
> b <- GLFW.getMouseButton GLFW.ButtonLeft
> mouseBut (b == GLFW.Press)
> k <- getKey ESC
> c <- readIORef closed
> return (if c || k == Press then Nothing else Just t)
OpenGL is initialised with practically everything turned off. Only
alpha blending is needed to be able to use translucent colours.
> initGL width height = do
> clearColor $= Color4 0 0 0 1
> blend $= Enabled
> blendFunc $= (SrcAlpha,OneMinusSrcAlpha)
The window size callback takes care of the `windowSize` signal and the
projection matrix.
> resizeGLScene winSize size@(Size w h) = do
> winSize (V (fromIntegral w) (fromIntegral h))
>
> viewport $= (Position 0 0,size)
>
> matrixMode $= Projection
> loadIdentity
> scale 1 (fromIntegral w/fromIntegral h) (1 :: GLfloat)
>
> matrixMode $= Modelview 0