packages feed

apecs-0.10.0: src/Apecs/Experimental/Util.hs

{-|
Stability : experimental

This module is experimental, and its API might change between point releases. Use at your own risk.
-
-}
module Apecs.Experimental.Util
  ( -- * Spatial hashing
    -- $hash
    quantize
  , flatten
  , inbounds
  , region
  , flatten'
  ) where

{- $hash
The following are helper functions for spatial hashing.
Your spatial hash is defined by two vectors;

  - The cell size vector contains real components and dictates
    how large each cell in your table is in world space units.
    It is used by @quantize@ to translate a world space coordinate into a table space index vector
  - The table size vector contains integral components and dictates how
    many cells your field consists of in each direction.
    It is used by @flatten@ to translate a table-space index vector into a flat integer
-}

{- | Quantize turns a world-space coordinate into a table-space coordinate by dividing
  by the given cell size and rounding towards negative infinity.
-}
{-# INLINE quantize #-}
quantize
  :: (Fractional (v a), Integral b, RealFrac a, Functor v)
  => v a
  -- ^ Quantization cell size
  -> v a
  -- ^ Vector to be quantized
  -> v b
quantize cell vec = floor <$> vec / cell

{- | Turns a table-space vector into an integral index, given some table size vector.
  Yields Nothing for out-of-bounds queries
-}
{-# INLINE flatten #-}
flatten
  :: (Applicative v, Integral a, Foldable v)
  => v a -- Field size vector
  -> v a
  -> Maybe a
flatten size vec = if inbounds size vec then Just (flatten' size vec) else Nothing

-- | Tests whether a vector is in the region given by 0 and the size vector (inclusive)
{-# INLINE inbounds #-}
inbounds
  :: (Num a, Ord a, Applicative v, Foldable v)
  => v a -- Field size vector
  -> v a
  -> Bool
inbounds size vec = and ((\v s -> v >= 0 && v <= s) <$> vec <*> size)

{- | For two table-space vectors indicating a region's bounds, gives a list of the vectors contained between them.
  This is useful for querying a spatial hash.
-}
{-# INLINE region #-}
region
  :: (Enum a, Applicative v, Traversable v)
  => v a
  -- ^ Lower bound for the region
  -> v a
  -- ^ Higher bound for the region
  -> [v a]
region a b = sequence $ enumFromTo <$> a <*> b

-- | flatten, but yields garbage for out-of-bounds vectors.
{-# INLINE flatten' #-}
flatten'
  :: (Applicative v, Integral a, Foldable v)
  => v a -- Field size vector
  -> v a
  -> a
flatten' size vec = foldr (\(n, x) acc -> n * acc + x) 0 $ (,) <$> size <*> vec