elerea-examples 1.0.2 → 1.2.0
raw patch · 9 files changed
+446/−5 lines, 9 filesdep ~elereanew-component:exe:elerea-bounce
Dependency ranges changed: elerea
Files
- README +23/−0
- doc/Breakout.html +5/−2
- doc/Chase.html +5/−2
- elerea-examples.cabal +20/−1
- src/Bounce/Event.hs +46/−0
- src/Bounce/Main.hs +213/−0
- src/Bounce/Math.hs +60/−0
- src/Bounce/Util.hs +21/−0
- src/Bounce/Vector.hs +53/−0
+ README view
@@ -0,0 +1,23 @@+This package provides some simple examples in order to help getting+started with the Elerea FRP library. Check out the doc directory for+the colourful literate sources of the Chase and Breakout examples. All+the executables created start with the elerea- prefix.++The programs included are the following:++ * Chase: a minimal example that demonstrates reactivity and mutually+ recursive signals; you can give a thrust to the square following+ the mouse with a left click.++ * Breakout: a not too fancy breakout clone that also demonstrates+ Graphviz output; you can get an SVG rendition of the signal+ structure with the following command if Graphviz is installed:++ elerea-breakout --dump-dot | dot -Tsvg -o breakout.svg++ You can simply use the mouse to move the paddle.++ * Bounce: an example for creating dynamic collections of signals+ with the experimental interface; left click to create balls, drag+ existing balls with left button to propel them and drag with right+ to select balls for deletion (upon release)
doc/Breakout.html view
@@ -1,5 +1,6 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><title></title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><meta name="generator" content="pandoc" /><link rel="stylesheet" href="hscolour.css" type="text/css" media="all" />-</head><body><div id="elerea-breakout-example"><h1>Elerea Breakout example</h1><p>Breakout is one of the simplest test applications that brings up some problems with non-trivial solutions. In particular, there is a circular dependency between the position and the velocity of the ball. The position is the integral of the velocity, while velocity can have instantaneous changes due to collisions, which is a function of the position of the ball and the current state of the field. The field is dynamic, and it also forms a mutual dependency loop with the ball position. On top of these, part of the field (at least if we regard the paddle as part of it) depends on user input, and its effects start propagating in the game state as soon as the ball reaches the level of the paddle.</p><p>Elerea lets us express all these circular dependencies in a natural way, by simply referring to the respective signals by their names. User input is also seen as a signal from inside the reactive part, while the imperative framework is given a sink fuction to update it as needed. Type safety is ensured all the way.</p><img src="Breakout.png" alt="Elerea Breakout in action" /><p>You can also have the program output the signal structure in <a href="http://www.graphviz.org/">Graphviz</a> dot format with the <code>--dump-dot</code> switch after the game is over. For instance, if Graphviz is installed, you can get an SVG rendition of the graph using the following command:</p><p><code>elerea-breakout --dump-dot | dot -Tsvg -o breakout.svg</code></p><p>Below follows the full source of the example.</p><pre><span class='hs-varop'>></span> <span class='hs-comment'>{-# LANGUAGE RecursiveDo #-}</span>+</head><body><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">+<html><head><!-- Generated by HsColour, http://www.cs.york.ac.uk/fp/darcs/hscolour/ --><title>Haskell code</title> <link type='text/css' rel='stylesheet' href='hscolour.css' /> </head><body><div id="elerea-breakout-example"><h1>Elerea Breakout example</h1><p>Breakout is one of the simplest test applications that brings up some problems with non-trivial solutions. In particular, there is a circular dependency between the position and the velocity of the ball. The position is the integral of the velocity, while velocity can have instantaneous changes due to collisions, which is a function of the position of the ball and the current state of the field. The field is dynamic, and it also forms a mutual dependency loop with the ball position. On top of these, part of the field (at least if we regard the paddle as part of it) depends on user input, and its effects start propagating in the game state as soon as the ball reaches the level of the paddle.</p><p>Elerea lets us express all these circular dependencies in a natural way, by simply referring to the respective signals by their names. User input is also seen as a signal from inside the reactive part, while the imperative framework is given a sink fuction to update it as needed. Type safety is ensured all the way.</p><img src="Breakout.png" alt="Elerea Breakout in action" /><p>You can also have the program output the signal structure in <a href="http://www.graphviz.org/">Graphviz</a> dot format with the <code>--dump-dot</code> switch after the game is over. For instance, if Graphviz is installed, you can get an SVG rendition of the graph using the following command:</p><p><code>elerea-breakout --dump-dot | dot -Tsvg -o breakout.svg</code></p><p>Below follows the full source of the example.</p><pre><span class='hs-varop'>></span> <span class='hs-comment'>{-# LANGUAGE RecursiveDo #-}</span> <span class='hs-varop'>></span> <span class='hs-varop'>></span> <span class='hs-keyword'>module</span> <span class='hs-conid'>Main</span> <span class='hs-keyword'>where</span> <span class='hs-varop'>></span> @@ -230,5 +231,7 @@ <span class='hs-varop'>></span> <span class='hs-varid'>vnull</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>pure</span> <span class='hs-varid'>vnull</span> <span class='hs-varop'>></span> <span class='hs-varid'>dot</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>liftA2</span> <span class='hs-varid'>dot</span> <span class='hs-varop'>></span> <span class='hs-varid'>cross</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>liftA2</span> <span class='hs-varid'>cross</span>-</pre>+</pre></body>+</html>+ </div></body></html>
doc/Chase.html view
@@ -1,5 +1,6 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><title></title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><meta name="generator" content="pandoc" /><link rel="stylesheet" href="hscolour.css" type="text/css" media="all" />-</head><body><div id="elerea-chase-example"><h1>Elerea Chase example</h1><p>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.</p><p>For a slightly more complex example check out <code>Breakout.lhs</code>.</p><pre><span class='hs-varop'>></span> <span class='hs-comment'>{-# LANGUAGE RecursiveDo #-}</span>+</head><body><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">+<html><head><!-- Generated by HsColour, http://www.cs.york.ac.uk/fp/darcs/hscolour/ --><title>Haskell code</title> <link type='text/css' rel='stylesheet' href='hscolour.css' /> </head><body><div id="elerea-chase-example"><h1>Elerea Chase example</h1><p>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.</p><p>For a slightly more complex example check out <code>Breakout.lhs</code>.</p><pre><span class='hs-varop'>></span> <span class='hs-comment'>{-# LANGUAGE RecursiveDo #-}</span> <span class='hs-varop'>></span> <span class='hs-varop'>></span> <span class='hs-keyword'>module</span> <span class='hs-conid'>Main</span> <span class='hs-keyword'>where</span> <span class='hs-varop'>></span> @@ -141,5 +142,7 @@ <span class='hs-varop'>></span> <span class='hs-varid'>vnull</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>pure</span> <span class='hs-varid'>vnull</span> <span class='hs-varop'>></span> <span class='hs-varid'>dot</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>liftA2</span> <span class='hs-varid'>dot</span> <span class='hs-varop'>></span> <span class='hs-varid'>cross</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>liftA2</span> <span class='hs-varid'>cross</span>-</pre>+</pre></body>+</html>+ </div></body></html>
elerea-examples.cabal view
@@ -1,5 +1,5 @@ Name: elerea-examples-Version: 1.0.2+Version: 1.2.0 Cabal-Version: >= 1.2 Synopsis: Example applications for Elerea Category: reactivity, FRP@@ -19,6 +19,11 @@ Graphviz output; you can get an SVG rendition of the signal structure with the following command if Graphviz is installed: @elerea-breakout --dump-dot | dot -Tsvg -o breakout.svg@+ .+ * bounce: an example for creating dynamic collections of signals+ with the experimental interface; left click to create balls, drag+ existing balls with left button to propel them and drag with right+ to select balls for deletion (upon release) Author: Patai Gergely Maintainer: Patai Gergely (patai@iit.bme.hu)@@ -31,11 +36,25 @@ src/Common/Vector.lhs src/Common/Utils.lhs + src/Bounce/Event.hs+ src/Bounce/Math.hs+ src/Bounce/Util.hs+ src/Bounce/Vector.hs+ doc/Breakout.html doc/Breakout.png doc/Chase.html doc/hscolour.css doc/Makefile++ README++Executable elerea-bounce+ Executable: elerea-bounce+ HS-Source-Dirs: src/Bounce+ Main-IS: Main.hs+ Build-Depends: base >= 3 && < 5, elerea >= 1.2, OpenGL, GLFW >= 0.4+ GHC-Options: -O2 Executable elerea-breakout Executable: elerea-breakout
+ src/Bounce/Event.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE RecursiveDo #-}++module Event where++import Control.Applicative+import Control.Monad+import Control.Monad.Fix+import Data.Maybe+import FRP.Elerea.Experimental++import Util++{-| Events are signals with an option type. -}+type Event p a = Signal p (Maybe a)++{-| Sample a signal whenever a changing condition is true. -}+ifE :: Signal p Bool -> Signal p a -> Event p a+ifE c s = c >>= \b -> if b then Just <$> s else return Nothing++{-| Left-biased merge. -}+leftE :: Event p a -> Event p a -> Event p a+leftE e1 e2 = e1 >>= maybeE e2++{-| Right-biased merge. -}+rightE :: Event p a -> Event p a -> Event p a+rightE e1 e2 = e2 >>= maybeE e1++{-| Left-biased merge of several events. -}+mergeE :: [Event p a] -> Event p a+mergeE [] = return Nothing+mergeE (e:es) = e >>= maybeE (mergeE es)++{-| Maintain a changing list of entities fed in as events along with a+function to derive the corresponding signal of the removal condition+(the signal is removed when the associated removal signal is 'True' for+the first time). -}+collectE :: Event p a -> (a -> Signal p Bool) -> SignalGen p (Signal p [a])+collectE e f = mdo+ col <- delay [] col'+ col' <- dmemo [] $ filterM (fmap not . f) =<< liftM2 ((++).maybeToList) e col+ return col'++{-| A helper function equivalent to @flip maybe (return.Just)@. -}+maybeE :: Event p a -> Maybe a -> Event p a+maybeE e Nothing = e+maybeE _ jx = return jx
+ src/Bounce/Main.hs view
@@ -0,0 +1,213 @@+{-# LANGUAGE RecursiveDo, NoMonomorphismRestriction #-}++module Main where++import Control.Applicative+import Control.Monad+import Data.IORef+import Data.List+import Data.Maybe+import FRP.Elerea.Experimental+import Graphics.UI.GLFW as GLFW+import Graphics.Rendering.OpenGL++import Event+import Math+import Util+import Vector++frameThickness = 0.05++ballSize = 0.05++ballFade = 2++data Ball = Ball { ballPos :: Vec+ , ballCol :: Maybe GLfloat+ , ballDrag :: Bool+ }++-- A box with bouncing balls inside: +-- * left click creates a new ball,+-- * existing balls can be dragged and propelled with the left button,+-- * dragging with the right button creates a rectangle; every ball+-- within the rectangle is deleted when the button is released.+bounceDemo renderFun mousePos mousePress = mdo+ leftPress <- memo $ fst <$> mousePress+ rightPress <- memo $ snd <$> mousePress++ (killDrag,killNow) <- dragRectangle mousePos rightPress+ killRect <- (vnull,vnull) --> killDrag+ let within (V tlx tly,V brx bry) (V x y) = x > min tlx brx && x < max tlx brx &&+ y > min tly bry && y < max tly bry++ ballList <- collectE newBallE $ \bs -> killNow &&@ (within <$> killRect <*> (ballPos <$> bs))+ ballData <- memo $ sequence =<< ballList+ -- Change edge to memo below to be able to add lots of balls with ease!+ newBallCond <- edge $ leftPress &&@ (all (not.ballDrag) <$> ballData)+ newBallE <- generator $ newBall <$> mousePos <*> newBallCond+ let newBall pos cond = if cond+ then Just <$> ball pos mousePos leftPress+ else return Nothing++ frameCount <- stateful 0 (const (+1))+ fps <- derivT 2 frameCount+ + return $ renderFun <$> ballData <*> killDrag <*> fps++-- Flipflop signal: turns true when the first event fires, turns false+-- when the second fires.+flipflop te fe = False --> leftE (ifE te (pure True)) (ifE fe (pure False))++-- A rectangle created by dragging. When active, the coordinates of+-- its opposing corners are wrapped in a Just, otherwise the data is+-- Nothing. Also returns the event ending the drag.+dragRectangle mousePos mousePress = do+ dragBegin <- edge mousePress+ dragEnd <- edge (not <$> mousePress)+ drag <- flipflop dragBegin dragEnd+ topLeft <- vnull --> ifE dragBegin mousePos+ return (ifE drag ((,) <$> topLeft <*> mousePos), dragEnd)++-- A ball that bounces within the box and can be dragged. It flashes+-- every time its velocity changes.+ball initPos mousePos mousePress = mdo+ mouseDown <- edge mousePress+ let dragBegin = mouseDown &&@ (vlen (pos^-^mousePos) <@ ballSize/2)+ dragEnd <- edge (not <$> mousePress)+ drag <- flipflop dragBegin dragEnd+ dragVel <- derivTV 0.05 mousePos+ + let collHorz (V vx _) (V px _) = collFrame vx px+ collVert (V _ vy) (V _ py) = collFrame vy py+ collFrame v p = abs (p-0.5) > 0.5-(frameThickness+ballSize/2) && (p-0.5)*v > 0+ pos <- integralV initPos vel+ vel <- vnull --> velE+ velE <- memo $ mergeE [ifE drag dragVel,+ ifE (collHorz <$> vel <*> pos) (mul (V (-1) 1) <$> vel),+ ifE (collVert <$> vel <*> pos) (mul (V 1 (-1)) <$> vel)]++ colour <- ballColour (isJust <$> velE)++ return $ Ball <$> pos <*> colour <*> drag++-- A signal describing the colour of a ball. Nothing means normal+-- state, Just denotes flashing.+ballColour hit = transfer Nothing update hit+ where update dt True _ = Just 1+ update dt False prev = do t <- prev+ guard (t > 0)+ return (t-dt*ballFade)++driveNetwork network driver = do+ dt <- driver+ case dt of+ Just dt -> do join $ network dt+ driveNetwork network driver+ Nothing -> return ()++main = do+ initialize+ openWindow (Size 640 480) [DisplayRGBBits 8 8 8, DisplayAlphaBits 8, DisplayDepthBits 24] Window+ windowTitle $= "Elerea Bounce"++ (mousePosition,mousePositionSink) <- external vnull+ (mousePress,mousePressSink) <- external (False,False)++ closed <- newIORef False+ windowSizeCallback $= resizeGLScene+ windowCloseCallback $= writeIORef closed True+ initGL 800 800++ unitCircle <- defineNewList Compile $ renderPrimitive TriangleStrip $ forM_ [0..20] $ \i -> do+ let a = 2*pi*i/20+ vertex $ Vertex3 (0.5*sin a) (0.5*cos a) (0 :: GLfloat)+ vertex $ Vertex3 0 0 (0 :: GLfloat)++ demo <- start $ bounceDemo (render unitCircle) mousePosition mousePress+ time $= 0+ driveNetwork demo (readInput mousePositionSink mousePressSink closed)++ closeWindow++initGL width height = do+ clearColor $= Color4 0 0 0 1+ blend $= Enabled+ blendFunc $= (SrcAlpha,OneMinusSrcAlpha)++resizeGLScene size@(Size w h) = do+ let r = 2*fromIntegral w/fromIntegral h+ r' = 2*fromIntegral h/fromIntegral w++ viewport $= (Position 0 0,size)++ matrixMode $= Projection+ loadIdentity+ scale (min 2 r') (min 2 r) (1 :: GLfloat)+ translate $ Vector3 (-0.5) (-0.5) (0 :: GLfloat)+ + matrixMode $= Modelview 0++render unitCircle bs rect fps = do+ let drawRectangle x y sx sy = do+ renderPrimitive Quads $ do+ vertex $ Vertex3 (x) (y) (0 :: GLfloat)+ vertex $ Vertex3 (x+sx) (y) (0 :: GLfloat)+ vertex $ Vertex3 (x+sx) (y+sy) (0 :: GLfloat)+ vertex $ Vertex3 (x) (y+sy) (0 :: GLfloat)+ drawEllipse xc yc xs ys n = preservingMatrix $ do+ translate $ Vector3 xc yc (0 :: GLfloat)+ scale xs ys (1 :: GLfloat)+ callList unitCircle++ clear [ColorBuffer]+ loadIdentity+ + color $ Color4 0.6 0.6 0.6 (1 :: GLfloat)+ drawRectangle 0 0 1 frameThickness+ drawRectangle 0 (1-frameThickness) 1 frameThickness+ drawRectangle 0 0 frameThickness 1+ drawRectangle (1-frameThickness) 0 frameThickness 1+ + forM_ bs $ \b -> do+ case ballCol b of+ Nothing -> color $ Color4 0 0 0.7 (1 :: GLfloat)+ Just t -> color $ Color4 (0.9*t) (0.9*t) (0.7*(1-t)) (1 :: GLfloat)++ let V x y = ballPos b+ drawEllipse x y ballSize ballSize 20++ case rect of+ Nothing -> return ()+ Just (V tlx tly,V brx bry) -> do+ color $ Color4 1 0 0 (0.4 :: GLfloat)+ drawRectangle tlx tly (brx-tlx) (bry-tly)++ scale 0.003 0.003 (1 :: GLfloat)+ color $ Color4 1 1 1 (1 :: GLfloat)+ renderString Fixed8x16 $ " FPS: " ++ show fps ++ " balls: " ++ show (length bs)++ flush+ swapBuffers++readInput mousePos mouseBut closed = do+ t <- get time+ time $= 0+ + Position x y <- get GLFW.mousePos+ Size w h <- get windowSize+ let x' = fromIntegral x+ y' = fromIntegral y+ w' = fromIntegral w+ h' = fromIntegral h+ mx = (x'-max 0 (w'-h')/2)/min w' h'+ my = (y'-max 0 (h'-w')/2)/min w' h'+ mousePos (V mx (1-my))+ + bl <- getMouseButton ButtonLeft+ br <- getMouseButton ButtonRight+ mouseBut (bl == Press, br == Press)+ k <- getKey ESC+ c <- readIORef closed++ return (if c || k == Press then Nothing else Just (realToFrac t))
+ src/Bounce/Math.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE RecursiveDo, NoMonomorphismRestriction #-}++module Math where++import Control.Applicative+import Data.List+import FRP.Elerea.Experimental++import Vector+import Util++{-| Scalar integral. -}+integral :: (Num a) => a -> Signal a a -> SignalGen a (Signal a a)+integral v0 s = transfer v0 (\dt v v0 -> v0+v*dt) s++{-| Vector integral. -}+integralV :: (Vector2D v c) => v -> Signal c v -> SignalGen c (Signal c v)+integralV v0 s = transfer v0 (\dt v v0 -> v0^+^(v^*.dt)) s++{-| Scalar derivative by the last two values. -}+deriv :: (Fractional a) => Signal a a -> SignalGen a (Signal a a)+deriv s = do+ sig <- transfer (0,0,1) (\dt v (v0,_,_) -> (v,v0,dt)) s+ initSignal 0 (d <$> sig)+ where d (x',x,dt) = (x'-x)/dt++{-| Vector derivative by the last two values. -}+derivV :: (Vector2D v c, Num c) => Signal c v -> SignalGen c (Signal c v)+derivV s = do+ sig <- transfer (vnull,vnull,1) (\dt v (v0,_,_) -> (v,v0,dt)) s+ initSignal vnull (d <$> sig)+ where d (x',x,dt) = (x'^-^x)^/.dt++{-| Scalar derivative by a given time period. -}+derivT :: (Fractional a, Ord a) => a -> Signal a a -> SignalGen a (Signal a a)+derivT wt s = do+ sig <- transfer (0,(0,0)) d s+ return (fst <$> sig)+ where d dt v (x,(v0,t)) = if t' > wt then ((v-v0)/t',(v,0)) else (x,(v0,t'))+ where t' = dt+t++{-| Vector derivative by a given time period. -}+derivTV :: (Vector2D v c, Ord c, Num c) => c -> Signal c v -> SignalGen c (Signal c v)+derivTV wt s = do+ sig <- transfer (vnull,(vnull,0)) d s+ return (fst <$> sig)+ where d dt v (x,(v0,t)) = if t' > wt then ((v^-^v0)^/.t',(v,0)) else (x,(v0,t'))+ where t' = dt+t++{-| Scalar moving average of a given number of recent samples. -}+movingAvg :: (Fractional a) => a -> Signal p a -> SignalGen p (Signal p a)+movingAvg n s = do+ sig <- scanM n (delay 0) s+ return $ ((/n).sum) <$> sequence sig++{-| Vector moving average of a given number of recent samples. -}+movingAvgV :: (Vector2D v c, Num c) => c -> Signal p v -> SignalGen p (Signal p v)+movingAvgV n s = do+ sig <- scanM n (delay vnull) s+ return $ ((^/.n).foldl1' (^+^)) <$> sequence sig
+ src/Bounce/Util.hs view
@@ -0,0 +1,21 @@+module Util where++import Control.Applicative+import FRP.Elerea.Experimental++-- Lifted conditional.+ifS c s1 s2 = c >>= \b -> if b then s1 else s2++-- Override the first output of a signal.+initSignal x s = do+ c <- stateful True (const (const False))+ return $ ifS c (pure x) s++scanM 0 _ _ = return []+scanM n f m = do+ x <- f m+ xs <- scanM (n-1) f x+ return (x:xs)++-- Optional delay. Should memo behave like this?+dmemo = flip transfer (const const)
+ src/Bounce/Vector.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}++module Vector where++import Control.Applicative+import FRP.Elerea.Experimental+import Graphics.Rendering.OpenGL++data Vec = V { getX :: {-# UNPACK #-} !GLfloat, getY :: {-# UNPACK #-} !GLfloat }++infixl 7 ^*.+infixl 7 .*^+infixl 7 ^/.+infixl 7 `dot`+infixl 7 `cross`+infixl 6 ^+^+infixl 6 ^-^++class Vector2D v c | v -> c where+ (^+^) :: v -> v -> v+ (^-^) :: v -> v -> v+ (^*.) :: v -> c -> v+ (.*^) :: c -> v -> v+ (^/.) :: v -> c -> v+ vnull :: v+ dot :: v -> v -> c+ cross :: v -> v -> c+ vlen :: v -> c+ mul :: v -> v -> v++instance Vector2D Vec GLfloat where+ V x1 y1 ^+^ V x2 y2 = V (x1+x2) (y1+y2)+ V x1 y1 ^-^ V x2 y2 = V (x1-x2) (y1-y2)+ V x y ^*. t = V (x*t) (y*t)+ t .*^ V x y = V (x*t) (y*t)+ V x y ^/. t = V (x/t) (y/t)+ vnull = V 0 0+ V x1 y1 `dot` V x2 y2 = x1*x2+y1*y2+ V x1 y1 `cross` V x2 y2 = x1*y2-x2*y1+ vlen (V x y) = sqrt (x*x+y*y)+ V x1 y1 `mul` V x2 y2 = V (x1*x2) (y1*y2)++instance Vector2D (Signal p Vec) (Signal p GLfloat) where+ (^+^) = liftA2 (^+^)+ (^-^) = liftA2 (^-^)+ (^*.) = liftA2 (^*.)+ (.*^) = liftA2 (.*^)+ (^/.) = liftA2 (^/.)+ vnull = pure vnull+ dot = liftA2 dot+ cross = liftA2 cross+ vlen = fmap vlen+ mul = liftA2 mul