packages feed

nspace-0.1.0.0: src/Data/QuadTree/Internal.hs

{-# LANGUAGE PatternSynonyms      #-}
{-# LANGUAGE RoleAnnotations      #-}
{-# LANGUAGE UndecidableInstances #-}

module Data.QuadTree.Internal where

import Control.Applicative (liftA2)
import Data.Maybe (isJust)
import Data.Monoid (Ap(..))
import GHC.Generics (Generic)
import Linear.V2
import Linear.V4


------------------------------------------------------------------------------
-- | An axis-aligned bounding box in 3-space.
data Rect a = Rect
  { r_pos  :: !(V2 a)
  , r_size :: !(V2 a)
  }
  deriving stock (Show, Read, Eq, Generic, Ord, Functor)


------------------------------------------------------------------------------
r_x, r_y, r_w, r_h :: Rect a -> a
r_x (Rect (V2 x _) (V2 _ _)) = x
r_y (Rect (V2 _ y) (V2 _ _)) = y
r_w (Rect (V2 _ _) (V2 w _)) = w
r_h (Rect (V2 _ _) (V2 _ h)) = h


------------------------------------------------------------------------------
-- | @'containsRect' c1 c2@ is true when @c2@ is inside or equal to @c1@.
rectContainsRect :: (Num a, Ord a) => Rect a -> Rect a -> Bool
rectContainsRect r1@(Rect (V2 bx by) (V2 bw bh)) r2@(Rect (V2 sx sy) (V2 sw sh)) =
  r1 == r2 ||
  and
    [ bx <= sx
    , by <= sy
    , sx + sw <= bx + bw
    , sy + sh <= by + bh
    ]


------------------------------------------------------------------------------
-- | Does the rect contain a given point?
rectContainsPoint :: (Ord a, Num a) => Rect a -> V2 a -> Bool
rectContainsPoint (Rect _ (V2 w h)) _
  | w <= 0 || h <= 0
  = False
rectContainsPoint (Rect (V2 x y) (V2 w h)) (V2 tx ty) =
  and
    [ x <= tx
    , y <= ty
    , tx < x + w
    , ty < y + h
    ]

------------------------------------------------------------------------------
-- | Get the co-ordinates of the corners of a 'Rect'.
rectCorners :: Num a => Rect a -> V4 (V2 a)
rectCorners (Rect (V2 x y) (V2 w h)) =
  let p = V2 x y
      dx = V2 w 0
      dy = V2 0 h
   in fmap (p +) $ V4 0   dx         dy       (dx + dy)


------------------------------------------------------------------------------
-- | 'Control.Monad.Free.Free', but with better instances.
data Free a
  = Fill a
  | Split (V4 (Free a))
  deriving (Functor, Foldable, Traversable, Generic)

deriving via Ap Free a instance (Semigroup a) => Semigroup (Free a)
deriving via Ap Free a instance (Monoid    a) => Monoid    (Free a)

deriving stock instance (Show a) => Show (Free a)

instance (Eq a) => Eq (Free a) where
  Fill a   == Fill b    = a             == b
  Split qu == Split qu' = qu            == qu'
  Fill a   == Split qu  = pure (pure a) == qu
  Split qu == Fill a    = pure (pure a) == qu

instance Applicative Free where
  pure = Fill
  liftA2 fabc (Fill a) (Fill b) = Fill $ fabc a b
  liftA2 fabc (Fill a) (Split qu) = Split $ fmap (fmap (fabc a)) qu
  liftA2 fabc (Split qu) (Fill b) = Split $ fmap (fmap (flip  fabc b)) qu
  liftA2 fabc (Split qu) (Split qu') = Split $ liftA2 (liftA2 fabc) qu qu'

instance Monad Free where
  Fill a >>= f = f a
  Split qu >>= f = Split $ fmap (>>= f) qu


------------------------------------------------------------------------------
-- | Normalize a 'Rect' so it has a positive 'r_size'.
normalize :: (Num a, Ord a) => Rect a -> Rect a
normalize q@(Rect (V2 x y) (V2 w h))
  | w < 0 = let w' = abs w in normalize $ Rect (V2 (x - w') y) $ V2 w' h
  | h < 0 = let h' = abs h in normalize $ Rect (V2 x (y - h')) $ V2 w h'
  | otherwise = q


------------------------------------------------------------------------------
-- | Do two 'Rect's intersect?
intersects :: (Ord a, Num a) => Rect a -> Rect a -> Bool
intersects r1 r2 = isJust $ getIntersect r1 r2


------------------------------------------------------------------------------
-- | Get the area of a 'Rect'.
rectSize :: Num a => Rect a -> a
rectSize (Rect _ (V2 w h)) = w * h


------------------------------------------------------------------------------
-- | Compute the intersection of two 'Rect's.
getIntersect :: (Ord a, Num a) => Rect a -> Rect a -> Maybe (Rect a)
getIntersect (normalize -> r1) (normalize -> r2)
 | rectSize r1 == 0 = Just r1
 | rectSize r2 == 0 = Just r2
 | otherwise =
  let x0 = max (r_x r1) (r_x r2)
      y0 = max (r_y r1) (r_y r2)
      x1 = min (r_x r1 + r_w r1) (r_x r2 + r_w r2)
      y1 = min (r_y r1 + r_h r1) (r_y r2 + r_h r2)
      w = x1 - x0
      h = y1 - y0
   in case 0 < w && 0 < h of
        True -> Just $ Rect (V2 x0 y0) (V2 w h)
        False -> Nothing


unwrap :: Free a -> V4 (Free a)
unwrap (Fill a) = pure $ pure a
unwrap (Split qu) = qu


------------------------------------------------------------------------------
-- | Join together 'Split' constructors which all contain the same value.
fuse :: Eq a => Free a -> Free a
fuse (Fill a) = Fill a
fuse (Split q) = doFuse $ fmap fuse q


doFuse :: Eq a => V4 (Free a) -> Free a
doFuse (V4 (Fill a) (Fill b) (Fill c) (Fill d))
  | a == b
  , b == c
  , c == d
  = Fill a
doFuse q = Split q