scenegraph-0.2.0.0: src/Graphics/SceneGraph/Basic.hs
module Graphics.SceneGraph.Basic where
import Control.Lens ((^.))
import Control.Monad.Identity (Identity)
import Control.Monad.State (lift)
import qualified Control.Monad.State as ST
import Data.Default (Default (..))
import Data.Graph.Inductive (Node, (&))
import qualified Data.Graph.Inductive as G
import qualified Data.Text as T
import Graphics.SceneGraph.Matrix (rotateM, rotatePostM, scaleM,
translateM, translatePostM)
import Graphics.SceneGraph.Types (ClickHandler, Color, DragHandler,
KeyState (Down), Phong, Scene (..),
SceneData (..), SceneEdge (..),
SceneGraph, SceneNode (..),
colorToPhong, llab, nullNode)
import Linear (M44, R1 (..), R3 (..), V3 (..),
(!*!))
import qualified Linear as L
-- | Holds state of graph as it is built.
data OSGState g = OSGState
{ graph :: SceneGraph g
, context :: [SceneNode g]
, startNode :: Int
, root :: Int
}
deriving (Eq, Show)
instance Default (OSGState g) where
def = OSGState emptyOSG [] 0 0
emptyState :: OSGState g
emptyState = def
-- | The OSG monad within which construction of scene graphs occur.
-- was 'type OSGT m = ErrorT Throwable (ST.StateT OSGState m)'
type OSGT m g = ST.StateT (OSGState g) m
type OSGSceneT m g = OSGT m g (SceneNode g)
type OSG g = OSGT Identity g
-- | Create and run a OSG monad to return a scene graph and root node.
runOSG :: Monad m => OSGState g -> OSGSceneT m g -> m (SceneNode g, OSGState g, Node)
runOSG state f = do
(ret, state') <- ST.runStateT f state
return (ret, state', root state')
runOSGShow :: OSGSceneT IO g -> IO ()
runOSGShow f = do
(ret, state, i) <- runOSG emptyState f
print (ret, state, i)
-- | Wrapper for running the OSG monad to return a scene graph and root node.
osg :: Monad m => OSGSceneT m g -> m (Scene g)
osg f = do
(n, state, _) <- runOSG emptyState f
return $ Scene (graph state) (idd n)
idd :: SceneNode g -> Node
idd (SceneNode i _ _) = i
-- | Basic add node
addNodeBasic :: Monad m => SceneNode g -> OSGSceneT m g
addNodeBasic nde = addNode nde []
-- | Add node with scene data
addBasicNode :: Monad m => SceneData g -> OSGSceneT m g
addBasicNode g = addNode (SceneNode 0 "" g) []
-- | Add node with scene data
addBasicNamedNode :: Monad m => String -> SceneData g -> OSGSceneT m g
addBasicNamedNode name g = addNode (SceneNode 0 name g) []
-- | Add empty node
addNullNode :: Monad m => OSGSceneT m g
addNullNode = addNodeBasic $ nullNode 0
-- | Add a node to a scene graph with supplied children
addNode :: Monad m => SceneNode g -> [(SceneEdge, Node)] -> OSGSceneT m g
addNode nde children = do
s <- ST.get
let (sn, s') = addNode' s nde children
ST.put s'
return sn
-- | Non-monadic form of addNode
addNode' :: OSGState g -> SceneNode g -> [(SceneEdge, Node)] -> (SceneNode g, OSGState g)
addNode' s (SceneNode m l d) children =
let n = if m == 0 then startNode s + 2 else m
sn = SceneNode n l d
g' = ([], n, sn, children) & graph s
s' = s { graph = g', startNode = n, root = n }
in (sn, s')
-- | Replace a Scene Node
replaceNode :: Monad m => SceneNode g -> OSGSceneT m g
replaceNode n = do
s <- ST.get
g' <- lift $ replaceNode' (graph s) n
ST.put s{ graph = g' }
return n
-- | Inner monad version of replace node
replaceNode' :: Monad md => SceneGraph g -> SceneNode g -> md (SceneGraph g)
replaceNode' gr nn = return $ replaceNode'' gr nn
-- | Actually does the job of replacing node in a scene graph
replaceNode'' :: SceneGraph g -> SceneNode g -> SceneGraph g
replaceNode'' gr nn =
let (m, gr') = G.match (idd nn) gr in
case m of
Nothing -> gr
Just (i, n, _, o) -> (i, n, nn, o) & gr'
-- | Run the monad but keep it in the family.
runOSGL :: Monad m => OSGState g -> OSGSceneT m g -> OSGT m g (SceneNode g, OSGState g, Node)
runOSGL s n = lift $ runOSG s n
-- | Run the monad but keep it in the family.
runOSGL' :: Monad m => OSGSceneT m g -> OSGT m g (SceneNode g, Node)
runOSGL' n = do
s <- ST.get
(n1, s', i) <- runOSGL s n
ST.put s'
return (n1, i)
-- | Perform a function on a scene node
doOnNode :: Monad m => OSGSceneT m g -> (SceneNode g -> SceneNode g) -> OSGSceneT m g
doOnNode n f = do
s <- ST.get
(anode, s', _) <- runOSGL s n
ST.put s'
replaceNode (f anode)
-- | Create a light
light :: Monad m => OSGSceneT m g
light = addBasicNode Light
-- | Create a camera
camera :: Monad m => OSGSceneT m g
camera = addBasicNode Camera
-- | Create a camera
mesh :: Monad m => T.Text -> g -> OSGSceneT m g
mesh name geom = addBasicNode $ Geode name geom
fi :: (Integral a, Integral b) => a -> b
fi = fromIntegral
-- plane' :: Int -> ([(PrimitiveMode, Int, Int)],[VectorD],[VectorD])
-- plane' w = foldr (\ (a1,a2,a3) (b1,b2,b3) -> (a1:b1,a2++b2,a3++b3)) ([],[],[]) [ up w xs | xs <- [0..(w-1)]]
-- up :: Int -> Int -> ( (PrimitiveMode,Int,Int), [VectorD],[VectorD])
-- up w xs = ( (TriangleStrip, (xs*(w*2+2))+1, w*2+2), [ fromList [(fi x),(fi y),0] | y <- [0..w],x <-[xs..(xs+1)]], [ fromList [0,0,1] | x <-[1..2], y <- [0..w]])
-- -- | Create a plane
-- planeT :: Monad m => Int -> OSGSceneT m g
-- planeT w = addBasicNode (Geode $ Mesh1 a b c) where (a,b,c) = plane' w
-- -- | Create a quad mesh
-- quad :: (Int,Int) -> ([(PrimitiveMode, Int, Int)],[VectorD],[VectorD])
-- quad (x,y) = ( [(Quads,1,100) ], [ fromList[x',y',0], fromList[(x'+1),y',0], fromList[(x'+1),(y'+1),0],
-- fromList [x',(y'+1),0]], [fromList [0,0,1] | i <- [0..3]])
-- where x' = fi x
-- y' = fi y
-- planeq' w = foldr (\ (a1,a2,a3) (b1,b2,b3) -> (a1,a2++b2,a3++b3)) ([],[],[]) [ quad (x,y) | x <- [0..(w-1)], y <- [0..(w-1)]]
-- plane :: Monad m => Int -> OSGSceneT m g
-- plane w = addBasicNode (Geode $ Mesh1 a b c) where (a,b,c) = planeq' w
-- planeQ :: Monad m => Int -> OSGSceneT m g
-- planeQ = plane
-- -- | Create a node containing a torus.
-- torus :: Monad m => Float -> OSGSceneT m g
-- torus i = addBasicNode (Geode $ GLObj $ GL.Torus (realToFrac i) (realToFrac (r*2)) 50 50)
-- -- | Create a node containing a sphere
-- sphere :: Monad m => Float -> OSGSceneT m g
-- sphere r = addBasicNode (Geode $ GLObj $ GL.Sphere' (realToFrac r) 50 50)
-- -- | Create a node containing a tetrahedron
-- tetra :: Monad m => OSGSceneT m g
-- tetra = addBasicNode (Geode $ GLObj $ GL.Tetrahedron)
-- -- | Create a node containing a line
-- line :: Monad m => VectorD -> VectorD -> OSGSceneT m g
-- line p q = addBasicNode (Geode $ Mesh1 [(Lines,1,2)] [p,q] [v1,v1] )
-- -- | Create a node containing a cube.
-- -- Fixme: Faces are not orientated same way.
-- cube :: Monad m => GLdouble -> OSGSceneT m g
-- cube i = addBasicNode (Geode $ Mesh1 [ (Quads,1,6) ] (map fromList [
-- [md,md,md], [d,md,md], [d,d,md], [md,d,md], -- Z
-- [md,d,d], [d,d,d], [d,md,d], [md,md,d],
-- [d,md,md], [d,d,md], [d,d,d],[d,md,d], -- X
-- [md,md,d], [md,d,d], [md,d,md],[md,md,md],
-- [md,d,md], [d,d,md], [d,d,d], [md,d,d], -- Y
-- [md,md,md], [d,md,md], [d,md,d], [md,md,d]
-- ])
-- (map fromList [
-- [0,0,1], [0,0,1], [0,0,1], [0,0,1],
-- [0,0,mu], [0,0,mu], [0,0,mu], [0,0,mu],
-- [mu,0,0], [mu,0,0], [mu,0,0], [mu,0,0],
-- [1,0,0], [1,0,0], [1,0,0], [1,0,0],
-- [0,mu,0], [0,mu,0],[0,mu,0],[0,mu,0],
-- [0,1,0],[0,1,0],[0,1,0],[0,1,0]
-- ])) where (d,md,mu) = (i/2,(-i/2),(-1))
-- -- | Create cylinder as a BezierMesh
-- cylinder :: Monad m => GLfloat -> GLfloat -> OSGSceneT m g
-- cylinder r h = addBasicNode $ Geode $ BezierMesh $ [
-- [ (let z=z'*h in [Vertex3 0 (-r) z, Vertex3 (-d) (-r) z, Vertex3 (-r) (-d) z, Vertex3 (-r) 0 z ]) | z' <- [0..1]],
-- [ (let z=z'*h in[Vertex3 (-r) 0 z, Vertex3 (-r) d z , Vertex3 (-d) r z , Vertex3 0 r z ]) | z' <- [0..1]] ,
-- [ (let z=z'*h in[Vertex3 0 r z , Vertex3 d r z , Vertex3 r d z , Vertex3 r 0 z ]) | z' <- [0..1]],
-- [ (let z=z'*h in[Vertex3 r 0 z , Vertex3 r (-d) z , Vertex3 d (-r) z , Vertex3 0 (-r) z ]) | z' <- [0..1]]]
-- where d = 0.66 * r
-- | Scale a node by equal amounts in all directions
scaleS :: Monad m => Float -> OSGSceneT m g -> OSGSceneT m g
scaleS f = scale (pure f)
-- | Scale a node
scale :: Monad m => V3 Float -> OSGSceneT m g -> OSGSceneT m g
scale v = transformSG (scaleM v) (scale v)
-- | Translate a node
translate :: Monad m => V3 Float -> OSGSceneT m g -> OSGSceneT m g
translate v = transformSG (translateM v) (translate v)
-- | Rotate a node by an angle around a vector.
rotate :: Monad m => (Float, V3 Float) -> OSGSceneT m g -> OSGSceneT m g
rotate a@(theta, v) = transformSG (rotateM theta v) (rotate a)
rad :: Float -> Float
rad x = x * pi / 180
-- | Rotate a node around X axis
rotateX :: Monad m => Float -> OSGSceneT m g -> OSGSceneT m g
rotateX theta = rotate (rad theta, V3 1 0 0 )
-- | Rotate a node around Y axis
rotateY :: Monad m => Float -> OSGSceneT m g -> OSGSceneT m g
rotateY theta = rotate (rad theta, V3 0 1 0)
-- | Rotate a node around Z axis
rotateZ :: Monad m => Float -> OSGSceneT m g -> OSGSceneT m g
rotateZ theta = rotate (rad theta, V3 0 0 1)
-- | Apply colour to the node
colourSG :: Monad m => OSGSceneT m g -> (Phong -> Phong) -> (OSGSceneT m g -> OSGSceneT m g) -> OSGSceneT m g
colourSG sn action self = do
(n1, i) <- runOSGL' sn
case n1 of
SceneNode n lbl (Material p) -> do
let p' = action p
replaceNode (SceneNode n lbl (Material p'))
_ -> do
let n'' = addNode (SceneNode 0 "" (Material def)) [(DefaultEdge, i)]
self n''
-- | Transform the node of a scene graph within the Monad with the supplied matrix transform
transformSG :: Monad m => (M44 Float -> M44 Float) -> (OSGSceneT m g -> OSGSceneT m g) -> OSGSceneT m g -> OSGSceneT m g
transformSG action self n = do
(n1, i) <- runOSGL' n
case n1 of
SceneNode num lbl (MatrixTransform m) -> do
let m' = action m
replaceNode (SceneNode num lbl (MatrixTransform m'))
_ -> do
let n'' = addNode (SceneNode 0 "" (MatrixTransform L.identity)) [(DefaultEdge, i)]
self n''
-- | Transform the node of a scene graph with the supplied matrix transform
transformSG' :: SceneGraph g -> Node -> (M44 Float -> M44 Float) -> SceneGraph g
transformSG' sg nde mf =
case llab sg nde of
SceneNode _ _ (MatrixTransform m) -> replaceNode'' sg (SceneNode nde (show nde) (MatrixTransform (mf m)))
_ -> error "FIXME: Not a transform node"
translateSG' :: SceneGraph g -> Node -> V3 Float -> SceneGraph g
translateSG' sg nde v = transformSG' sg nde (translateM v)
translatePostSG' :: SceneGraph g -> Node -> V3 Float -> SceneGraph g
translatePostSG' sg nde v = transformSG' sg nde (translatePostM v)
rotatePostSG' :: SceneGraph g -> Node -> V3 Float -> Float -> SceneGraph g
rotatePostSG' sg nde v theta = transformSG' sg nde (rotatePostM theta v)
-- | Add color to a node
color :: Monad m => Color -> OSGSceneT m g -> OSGSceneT m g
color c n = colourSG n (const $ colorToPhong c) (color c)
-- | Label a node
label :: Monad m => OSGSceneT m g -> String -> OSGSceneT m g
label anode lbl = do
(SceneNode nde _ dte, _) <- runOSGL' anode
replaceNode (SceneNode nde lbl dte)
-- | Add texture
texture :: Monad m => OSGSceneT m g -> String -> OSGSceneT m g
texture n texName = do
i <- snd <$> runOSGL' n
addNode (SceneNode 0 "" (Texture texName)) [(DefaultEdge, i)]
-- -- | Add Text
text :: Monad m => T.Text -> OSGSceneT m g
text str = addBasicNode (Text str)
infixr 5 <+>
infixl 9 <->
infixl 9 </>
-- | Join two graphs together
(<+>) :: Monad m => OSGSceneT m g -> OSGSceneT m g -> OSGSceneT m g
(<+>) a b = do
s <- ST.get
(_, s', i) <- runOSGL s a
(_, s'', j) <- runOSGL s' b
ST.put s''
addNode (SceneNode 0 "" Group) [(DefaultEdge, i), (DefaultEdge, j)]
-- | Translate a node
(<->) :: Monad m => OSGSceneT m g -> V3 Float -> OSGSceneT m g
(<->) = flip translate
-- | Scale a node
(</>) :: Monad m => OSGSceneT m g -> V3 Float -> OSGSceneT m g
(</>) = flip scale
doNothing :: Monad m => p -> m ()
doNothing _ = return ()
-- | Add an handler node
handler :: Monad m => OSGSceneT m g -> ClickHandler g -> OSGSceneT m g
handler n f = do
(_, i) <- runOSGL' n
addNode (SceneNode 0 "" (Handler (Just (f, doNothing)) Nothing)) [(DefaultEdge, i)]
handler2 :: Monad m => OSGSceneT m g -> (ClickHandler g, DragHandler g) -> OSGSceneT m g
handler2 n (f,g) = do
(_, i) <- runOSGL' n
addNode (SceneNode 0 "" (Handler (Just (f, doNothing)) (Just (g, doNothing)))) [(DefaultEdge, i)]
-- | Create a DragHandler
dragHandler :: DragHandler g
dragHandler (Scene sg nde) vec = do
let tnde = head $ G.pre sg nde
sg' = translateSG' sg tnde vec
SceneNode _ _ (MatrixTransform m) = llab sg' tnde
posx = m^.(_x._z)
return (if abs posx < 1 then sg' else sg,posx)
-- | Create a ClickHandler
switchHandler :: ClickHandler g
switchHandler (Scene sg nde) ev = do
let sn = head $ G.suc sg nde
sn' = llab sg sn
let sg' = switchNode sn' (if ev == Down then 1 else 0) sg
return sg'
switchNode' :: Node -> Int -> SceneGraph g -> SceneGraph g
switchNode' nde n gr = replaceNode'' gr (SceneNode nde (show nde) (Switch n))
switchNode :: SceneNode g -> Int -> SceneGraph g -> SceneGraph g
switchNode (SceneNode nde lbl (Switch _)) n gr =
replaceNode'' gr newNode
where newNode = SceneNode nde lbl (Switch n)
switchNode _ _ _ = error "no Switch"
-- | Create a switch node
switch :: Monad m => OSGSceneT m g -> OSGSceneT m g -> OSGSceneT m g
switch = switch' 0
switch':: Monad m => Int -> OSGSceneT m g -> OSGSceneT m g -> OSGSceneT m g
switch' nde a b = do
s <- ST.get
(_, s', i) <- runOSGL s a
(_, s'', j) <- runOSGL s' b
ST.put s''
addNode (SceneNode nde (show nde) (Switch 0)) [(DefaultEdge, i), (DefaultEdge, j)]
-- -- | Get a strip mesh
-- strip :: Monad m => OSGSceneT m g
-- strip = do
-- let n = SceneNode 0 "" (Geode $ Mesh1 [(TriangleStrip,0,3)] [
-- vector3 (-2) 0 (-2),
-- vector3 (2) 0 (-2),
-- vector3 0 0 0 ] [
-- vector3 0 (-1) 0,
-- vector3 0 (-1) 0,
-- vector3 0 (-1) 0 ] )
-- addNode n []
-- | Make a group node from list of nodes
group :: Monad m => [SceneNode g] -> OSGSceneT m g
group [] = error "empty"
group [n] = addNode n []
group (n:ns) =
let n' = group ns in
addNode n [] <+> n'
emptyScene :: Scene g
emptyScene = Scene G.empty 0
getHitAction :: Scene g -> (Int -> IO ())
getHitAction = const $ const $ return ()
-- | Work up the tree from indicated no to find the first handler scene node.
findHandler :: SceneGraph g -> Int -> Maybe (SceneNode g)
findHandler gr num =
let start = fromEnum num
findUp num' =
case llab gr num' of
SceneNode n _ (Handler _ _) -> [llab gr n]
_ -> concatMap findUp (G.pre gr num')
in
case findUp start of
[] -> Nothing
(a:_) -> Just a
-- | Work down the tree from indicated no to find the first handler scene node.
findHandlerDown :: SceneGraph g -> Int -> Int
findHandlerDown gr num =
let findDown num' =
case llab gr num' of
SceneNode n _ (Handler _ _) -> [n]
_ -> concatMap findDown (G.suc gr num')
in
case findDown num of
[] -> error "findHandlerDown failed"
(a:_) -> a
findTextDown :: SceneGraph g -> Int -> Int
findTextDown gr num =
let findDown num' =
case llab gr num' of
SceneNode n _ (Text _ ) -> [n]
_ -> concatMap findDown (G.suc gr num')
in
case findDown num of
[] -> error "findHandlerDown failed"
(a:_) -> a
-- {--
-- -- Buttons are always switch nodes but selected geometry will not be so we need to search
-- -- up to find the owning widget.
-- -- FIXME use switchNode?
-- --}
-- | Handle some event
handleClickEvent :: Scene g -> Int -> KeyState -> IO (Scene g, Maybe (Scene g), Maybe (SceneGraph g -> SceneGraph g))
handleClickEvent (Scene gr start) n ks = do
-- putStrLn $ "handle event" ++ show ks
case findHandler gr n of
Just (SceneNode nid _ (Handler (Just (fn, snk)) _ )) -> do
sg <- fn (Scene gr nid) ks
case ks of
Down -> snk ()
_ -> return ()
return (Scene sg start, Just (Scene sg nid), Nothing)
_ -> return (Scene gr start, Nothing, Nothing)
emptyOSG :: SceneGraph g
emptyOSG = G.empty
findCamera :: Scene g -> Int -> Node
findCamera (Scene gr _) _ = head . filter (\x ->
case llab gr x of
SceneNode _ _ Camera -> True
_ -> False) . G.nodes $ gr
findCameraPath :: Scene g -> Int -> G.Path
findCameraPath (Scene gr nde) i =
let nde2 = findCamera (Scene gr nde) i in
G.esp nde nde2 gr
-- | Return the matrix got by traversing down the Node
getTransformTo :: Scene g -> Node -> M44 Float
getTransformTo (Scene gr start) nde =
foldr trans L.identity $ G.esp start nde gr
where
trans n mat1 =
case llab gr n of
SceneNode _ _ (MatrixTransform mat2) -> mat1 !*! mat2
_ -> mat1
getByLabel :: SceneGraph g -> String -> Node
getByLabel gr lbl =
head
. filter (\n -> let (SceneNode _ lbl' _) = llab gr n in lbl == lbl')
. G.nodes
$ gr