packages feed

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