packages feed

tcod-haskell-0.2.0.0: src/Game/TCOD/BSP.hsc

{-# LANGUAGE CPP                      #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE QuasiQuotes              #-}
-- | This toolkit allows one to create and manipulate 2D Binary Space Partition
-- trees. They can be used to split a rectangular region into non overlapping
-- sub-regions.
module Game.TCOD.BSP(
    BSP(..)
  , BSPCallback
  , bspNew
  , bspNewWithSize
  , bspDelete
  , bspLeft
  , bspRight
  , bspFather
  , bspIsLeaf
  , bspTraversePreOrder
  , bspTraverseInOrder
  , bspTraversePostOrder
  , bspTraverseLevelOrder
  , bspTraverseInvertedLevelOrder
  , bspContains
  , bspFindNode
  , bspResize
  , bspSplitOnce
  , bspSplitRecursive
  , bspRemoveSons
  ) where

import Data.Monoid
import Foreign
import Foreign.C
import Game.TCOD.Context as C
import Game.TCOD.MersenneTypes
import Game.TCOD.Tree
import GHC.Generics

context (tcodContext <> funCtx)
verbatim "#define TCOD_SDL2"
include "libtcod/bsp.h"
#include "libtcod/bsp.h"

-- | Data for BSP of map
data BSP = BSP {
  bspTree                :: !(TCODTree BSP) -- ^ BSP inherit tree
, bspX, bspY, bspW, bspH :: {-# UNPACK #-} !Int -- ^ Node position & size
, bspPosition            :: {-# UNPACK #-} !Int -- ^ Position of splitting
, bspLevel               :: {-# UNPACK #-} !Word8 -- ^ level int the tree
, bspHorizontal          :: !Bool -- ^ horizontal splitting?
} deriving (Eq, Show, Generic)

instance Storable BSP where
  sizeOf _ = #{size TCOD_bsp_t}
  alignment _ = #{alignment TCOD_bsp_t}
  poke p BSP{..} = do
    #{poke TCOD_bsp_t, tree} p bspTree
    #{poke TCOD_bsp_t, x} p bspX
    #{poke TCOD_bsp_t, y} p bspY
    #{poke TCOD_bsp_t, w} p bspW
    #{poke TCOD_bsp_t, h} p bspH
    #{poke TCOD_bsp_t, position} p bspPosition
    #{poke TCOD_bsp_t, level} p bspLevel
    #{poke TCOD_bsp_t, horizontal} p bspHorizontal
  peek p = BSP
    <$> (#{peek TCOD_bsp_t, tree} p)
    <*> (#{peek TCOD_bsp_t, x} p)
    <*> (#{peek TCOD_bsp_t, y} p)
    <*> (#{peek TCOD_bsp_t, w} p)
    <*> (#{peek TCOD_bsp_t, h} p)
    <*> (#{peek TCOD_bsp_t, position} p)
    <*> (#{peek TCOD_bsp_t, level} p)
    <*> (#{peek TCOD_bsp_t, horizontal} p)

-- | Callback for BSP traversal
type BSPCallback = BSP -> IO Bool

-- | Creating the root node
--
-- First, you have to create the root node of the tree. This node encompasses
-- the whole rectangular region.
bspNew :: IO BSP
bspNew = peek . castPtr =<< [C.exp| void* {TCOD_bsp_new()}|]

-- | Creating the root node
--
-- First, you have to create the root node of the tree. This node encompasses
-- the whole rectangular region.
bspNewWithSize :: Int -- ^ x Top left corner position and size of the rectangular region covered by the BSP tree.
  -> Int -- ^ y
  -> Int -- ^ w
  -> Int -- ^ h
  -> IO BSP
bspNewWithSize x y w h = do
  let x' = fromIntegral x
      y' = fromIntegral y
      w' = fromIntegral w
      h' = fromIntegral h
  peek . castPtr =<< [C.exp| void* {TCOD_bsp_new_with_size($(int x'), $(int y'), $(int w'), $(int h'))}|]

-- | Destroy BSP and all subtrees
bspDelete :: BSP -> IO ()
bspDelete bsp = do
  bsp' <- castPtr <$> new bsp
  [C.exp| void {TCOD_bsp_delete((TCOD_bsp_t*)$(void* bsp'))}|]

-- | Get left subtree
bspLeft :: BSP -> IO (Maybe BSP)
bspLeft bsp = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
  ptr <- [C.exp| void* { (void*)TCOD_bsp_left((TCOD_bsp_t*)$(void* bsp''))} |]
  if ptr == nullPtr then pure Nothing else Just <$> peek (castPtr ptr)

-- | Get right subtree
bspRight :: BSP -> IO (Maybe BSP)
bspRight bsp = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
  ptr <- [C.exp| void* { (void*)TCOD_bsp_right((TCOD_bsp_t*)$(void* bsp''))} |]
  if ptr == nullPtr then pure Nothing else Just <$> peek (castPtr ptr)

-- | Get parent subtree
bspFather :: BSP -> IO (Maybe BSP)
bspFather bsp = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
  ptr <- [C.exp| void* { (void*)TCOD_bsp_father((TCOD_bsp_t*)$(void* bsp''))} |]
  if ptr == nullPtr then pure Nothing else Just <$> peek (castPtr ptr)

-- | You can know if a node is a leaf (not split, no sons) with this function
bspIsLeaf :: BSP -> IO Bool
bspIsLeaf bsp = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
  toBool <$> [C.exp| int { (int)TCOD_bsp_is_leaf((TCOD_bsp_t*)$(void* bsp'')) } |]

-- | Traversing the tree
--
-- You can scan all the nodes of the tree and have a custom function called back
-- for each node. Each traversal function returns false if the traversal has been
-- interrupted (a callback returned false).
--
-- Pre-order : the callback is called for the current node, then for the left
-- son, then for the right son.
bspTraversePreOrder :: BSP -- ^ node
  -> BSPCallback
  -> IO Bool
bspTraversePreOrder bsp f = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      f' ptr _ = fromBool <$> (f =<< peek (castPtr ptr))
  toBool <$> [C.exp| int { (int)TCOD_bsp_traverse_pre_order((TCOD_bsp_t*)$(void* bsp''), (TCOD_bsp_callback_t)$fun:(int (*f')(void*, void*)), NULL) } |]

-- | Traversing the tree
--
-- You can scan all the nodes of the tree and have a custom function called back
-- for each node. Each traversal function returns false if the traversal has been
-- interrupted (a callback returned false).
--
-- In-order : the callback is called for the left son, then for current node,
-- then for the right son.
bspTraverseInOrder :: BSP -- ^ node
  -> BSPCallback
  -> IO Bool
bspTraverseInOrder bsp f = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      f' ptr _ = fromBool <$> (f =<< peek (castPtr ptr))
  toBool <$> [C.exp| int { (int)TCOD_bsp_traverse_in_order((TCOD_bsp_t*)$(void* bsp''), (TCOD_bsp_callback_t)$fun:(int (*f')(void*, void*)), NULL) } |]

-- | Traversing the tree
--
-- You can scan all the nodes of the tree and have a custom function called back
-- for each node. Each traversal function returns false if the traversal has been
-- interrupted (a callback returned false).
--
-- Post-order : the callback is called for the left son, then for the right son,
-- then for the current node.
bspTraversePostOrder :: BSP -- ^ node
  -> BSPCallback
  -> IO Bool
bspTraversePostOrder bsp f = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      f' ptr _ = fromBool <$> (f =<< peek (castPtr ptr))
  toBool <$> [C.exp| int { (int)TCOD_bsp_traverse_post_order((TCOD_bsp_t*)$(void* bsp''), (TCOD_bsp_callback_t)$fun:(int (*f')(void*, void*)), NULL) } |]

-- | Traversing the tree
--
-- You can scan all the nodes of the tree and have a custom function called back
-- for each node. Each traversal function returns false if the traversal has been
-- interrupted (a callback returned false).
--
-- Level-order : the callback is called for the nodes level by level, from left
-- to right.
bspTraverseLevelOrder :: BSP -- ^ node
  -> BSPCallback
  -> IO Bool
bspTraverseLevelOrder bsp f = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      f' ptr _ = fromBool <$> (f =<< peek (castPtr ptr))
  toBool <$> [C.exp| int { (int)TCOD_bsp_traverse_level_order((TCOD_bsp_t*)$(void* bsp''), (TCOD_bsp_callback_t)$fun:(int (*f')(void*, void*)), NULL) } |]

-- | Traversing the tree
--
-- You can scan all the nodes of the tree and have a custom function called back
-- for each node. Each traversal function returns false if the traversal has been
-- interrupted (a callback returned false).
--
-- Inverted level-order : the callback is called in the exact inverse order as
-- Level-order.
bspTraverseInvertedLevelOrder :: BSP -- ^ node
  -> BSPCallback
  -> IO Bool
bspTraverseInvertedLevelOrder bsp f = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      f' ptr _ = fromBool <$> (f =<< peek (castPtr ptr))
  toBool <$> [C.exp| int { (int)TCOD_bsp_traverse_inverted_level_order((TCOD_bsp_t*)$(void* bsp''), (TCOD_bsp_callback_t)$fun:(int (*f')(void*, void*)), NULL) } |]

-- | Check if a cell is inside a node
bspContains :: BSP
  -> Int -- ^ x
  -> Int -- ^ y
  -> IO Bool
bspContains bsp x y = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      x' = fromIntegral x
      y' = fromIntegral y
  toBool <$> [C.exp| int { (int)TCOD_bsp_contains((TCOD_bsp_t*)$(void* bsp''), $(int x'), $(int y')) } |]

-- | Getting the node containing a cell
bspFindNode :: BSP
  -> Int -- ^ x
  -> Int -- ^ y
  -> IO (Maybe BSP)
bspFindNode bsp x y = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      x' = fromIntegral x
      y' = fromIntegral y
  ptr <- castPtr <$> [C.exp| void* { (void*)TCOD_bsp_find_node((TCOD_bsp_t*)$(void* bsp''), $(int x'), $(int y')) } |]
  if ptr == nullPtr then pure Nothing else Just <$> peek (castPtr ptr)

-- | This operation resets the size of the tree nodes without changing the
-- splitting data (orientation/position). It should be called with the initial
-- region size or a bigger size, else some splitting position may be out of
-- the region.
--
-- You can use it if you changed the nodes size and position while using the
-- BSP tree, which happens typically when you use the tree to build a dungeon.
-- You create rooms inside the tree leafs, then shrink the leaf to fit the room
-- size. Calling resize on the root node with the original region size allows you
-- to reset all nodes to their original size.
bspResize :: BSP
  -> Int -- ^ x
  -> Int -- ^ y
  -> Int -- ^ w
  -> Int -- ^ h
  -> IO ()
bspResize bsp x y w h = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      x' = fromIntegral x
      y' = fromIntegral y
      w' = fromIntegral w
      h' = fromIntegral h
  [C.exp| void { TCOD_bsp_resize((TCOD_bsp_t*)$(void* bsp''), $(int x'), $(int y'), $(int w'), $(int h')) } |]

-- | Splitting a node once
--
-- Once you have the root node, you can split it into two smaller non-overlapping nodes.
bspSplitOnce :: BSP
  -> Bool -- ^ horizontal?
  -> Int -- ^ position
  -> IO ()
bspSplitOnce bsp h p = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      h' = fromBool h
      p' = fromIntegral p
  [C.exp| void { TCOD_bsp_split_once((TCOD_bsp_t*)$(void* bsp''), $(int h')!=0, $(int p')) } |]

-- | Recursively splitting a node
--
-- You can also recursively split the bsp. At each step, a random orientation
-- (horizontal/vertical) and position are chosen
bspSplitRecursive :: BSP
  -> TCODRandom -- ^ randomize algorithm
  -> Int -- ^ Number of recursion levels.
  -> Int -- ^ min h size. Minimum values of w and h for a node. A node is split only if the resulting sub-nodes are bigger than minHSize x minVSize
  -> Int -- ^ min v size
  -> Float -- ^ max h ratio. Maximum values of w/h and h/w for a node. If a node does not conform, the splitting orientation is forced to reduce either the w/h or the h/w ratio. Use values near 1.0 to promote square nodes.
  -> Float -- ^ max v ratio
  -> IO ()
bspSplitRecursive bsp (TCODRandom r) nb mh mv mhr mvr = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
      nb' = fromIntegral nb
      mh' = fromIntegral mh
      mv' = fromIntegral mv
      mhr' = realToFrac mhr
      mvr' = realToFrac mvr
  [C.exp| void { TCOD_bsp_split_recursive((TCOD_bsp_t*)$(void* bsp''), $(void* r), $(int nb'), $(int mh'), $(int mv'), $(float mhr'), $(float mvr')) } |]

-- | Deleting a part of the tree
--
-- You can delete a part of the tree, releasing resources for all sub nodes with
bspRemoveSons :: BSP -> IO ()
bspRemoveSons bsp = with bsp $ \bsp' -> do
  let bsp'' = castPtr bsp'
  [C.exp| void { TCOD_bsp_remove_sons((TCOD_bsp_t*)$(void* bsp'')) } |]