{-# LANGUAGE RankNTypes #-} -- | A QuadTree can be used to recursively divide up 2D space into quadrants. -- The smallest division corresponds to an unit `Extent`, so the total depth -- of the tree will depend on what sized `Extent` you start with. module Graphics.Gloss.Data.QuadTree ( QuadTree (..) , emptyTree , emptyNode , takeQuadOfTree , liftToQuad , insertByPath , insertByCoord , lookupNodeByPath , lookupByPath , lookupByCoord , flattenQuadTree , flattenQuadTreeWithExtents) where import Graphics.Gloss.Data.Quad import Graphics.Gloss.Data.Extent -- | The quad tree structure. data QuadTree a -- | An empty node. = TNil -- | A leaf containint some value. | TLeaf a -- | A node with four children. | TNode (QuadTree a) (QuadTree a) -- NW NE (QuadTree a) (QuadTree a) -- SW SE deriving Show -- | A `TNil` tree. emptyTree :: QuadTree a emptyTree = TNil -- | A node with `TNil`. for all its branches. emptyNode :: QuadTree a emptyNode = TNode TNil TNil TNil TNil -- | Get a quadrant from a node. -- If the tree does not have an outer node then `Nothing`. takeQuadOfTree :: Quad -> QuadTree a -> Maybe (QuadTree a) takeQuadOfTree quad tree = case tree of TNil -> Nothing TLeaf{} -> Nothing TNode nw ne sw se -> case quad of NW -> Just nw NE -> Just ne SW -> Just sw SE -> Just se -- | Apply a function to a quadrant of a node. -- If the tree does not have an outer node then return the original tree. liftToQuad :: Quad -> (QuadTree a -> QuadTree a) -> QuadTree a -> QuadTree a liftToQuad quad f tree = case tree of TNil -> tree TLeaf{} -> tree TNode nw ne sw se -> case quad of NW -> TNode (f nw) ne sw se NE -> TNode nw (f ne) sw se SW -> TNode nw ne (f sw) se SE -> TNode nw ne sw (f se) -- | Insert a value into the tree at the position given by a path. -- If the path intersects an existing `TLeaf` then return the original tree. insertByPath :: [Quad] -> a -> QuadTree a -> QuadTree a insertByPath [] x _ = TLeaf x insertByPath (q:qs) x tree = case tree of TNil -> liftToQuad q (insertByPath qs x) emptyNode TLeaf{} -> tree TNode{} -> liftToQuad q (insertByPath qs x) tree -- | Insert a value into the node containing this coordinate. -- The node is created at maximum depth, corresponding to an unit `Extent`. insertByCoord :: Extent -> Coord -> a -> QuadTree a -> Maybe (QuadTree a) insertByCoord extent coord x tree = do path <- pathToCoord extent coord return $ insertByPath path x tree -- | Lookup a node based on a path to it. lookupNodeByPath :: [Quad] -> QuadTree a -> Maybe (QuadTree a) lookupNodeByPath [] tree = Just tree lookupNodeByPath (q:qs) tree = case tree of TNil -> Nothing TLeaf{} -> Nothing TNode{} -> let Just quad = takeQuadOfTree q tree in lookupNodeByPath qs quad -- | Lookup an element based given a path to it. lookupByPath :: [Quad] -> QuadTree a -> Maybe a lookupByPath path tree = case lookupNodeByPath path tree of Just (TLeaf x) -> Just x _ -> Nothing -- | Lookup a node if a tree given a coordinate which it contains. lookupByCoord :: forall a . Extent -- ^ Extent that covers the whole tree. -> Coord -- ^ Coordinate of the value of interest. -> QuadTree a -> Maybe a lookupByCoord extent coord tree = do path <- pathToCoord extent coord lookupByPath path tree -- | Flatten a QuadTree into a list of its contained values, with coordinates. flattenQuadTree :: forall a . Extent -- ^ Extent that covers the whole tree. -> QuadTree a -> [(Coord, a)] flattenQuadTree extentInit treeInit = flatten' extentInit treeInit where flatten' extent tree = case tree of TNil -> [] TLeaf x -> let (_, s, _, w) = takeExtent extent in [((w, s), x)] TNode{} -> concat $ map (flattenQuad extent tree) allQuads flattenQuad extent tree quad = let extent' = cutQuadOfExtent quad extent Just tree' = takeQuadOfTree quad tree in flatten' extent' tree' -- | Flatten a QuadTree into a list of its contained values, with coordinates. flattenQuadTreeWithExtents :: forall a . Extent -- ^ Extent that covers the whole tree. -> QuadTree a -> [(Extent, a)] flattenQuadTreeWithExtents extentInit treeInit = flatten' extentInit treeInit where flatten' extent tree = case tree of TNil -> [] TLeaf x -> [(extent, x)] TNode{} -> concat $ map (flattenQuad extent tree) allQuads flattenQuad extent tree quad = let extent' = cutQuadOfExtent quad extent Just tree' = takeQuadOfTree quad tree in flatten' extent' tree'