Skip to content

Instantly share code, notes, and snippets.

@Garciat
Last active June 16, 2018 23:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Garciat/ad044e8828a04b223f57c06f7b45c860 to your computer and use it in GitHub Desktop.
Save Garciat/ad044e8828a04b223f57c06f7b45c860 to your computer and use it in GitHub Desktop.
import Data.Maybe (catMaybes)
import qualified Data.Set as Set
(|>) = flip ($)
between :: Ord a => a -> (a, a) -> Bool
between x (a, b) = a <= x && x <= b
class Clamp a where
clampedBy :: a -> (a, a) -> a
instance Clamp Double where
clampedBy x (a, b) = max a $ min b x
data Vec2
= Vec2
{ vec2X :: Double
, vec2Y :: Double
}
deriving (Show, Eq, Ord)
instance Clamp Vec2 where
clampedBy (Vec2 x y) (Vec2 ax ay, Vec2 bx by) =
Vec2 (x `clampedBy` (ax, bx)) (y `clampedBy` (ay, by))
distanceSq :: Vec2 -> Vec2 -> Double
distanceSq (Vec2 x y) (Vec2 x' y') =
let dx = x - x'
dy = y - y' in
dx * dx + dy * dy
data AABB
= AABB
{ aabbXY :: Vec2
, aabbWH :: Vec2
}
deriving (Show, Eq)
aabbSE :: AABB -> Vec2
aabbSE (AABB (Vec2 x y) (Vec2 w h)) =
Vec2 (x + w) (y + h)
aabbEdges :: AABB -> (Vec2, Vec2)
aabbEdges bounds = (aabbXY bounds, aabbSE bounds)
aabb :: Double -> Double -> Double -> Double -> AABB
aabb x y w h = AABB (Vec2 x y) (Vec2 w h)
quadrisect :: AABB -> (AABB, AABB, AABB, AABB)
quadrisect (AABB (Vec2 x y) (Vec2 w h)) =
( AABB (Vec2 (x+0) (y+0)) (Vec2 w2 h2)
, AABB (Vec2 (x+w2) (y+0)) (Vec2 w2 h2)
, AABB (Vec2 (x+0) (y+h2)) (Vec2 w2 h2)
, AABB (Vec2 (x+w2) (y+h2)) (Vec2 w2 h2)
)
where
w2 = w / 2
h2 = h / 2
data Circle
= Circle
{ circleXY :: Vec2
, circleR :: Double
}
deriving (Show, Eq)
circle :: Double -> Double -> Double -> Circle
circle x y r = Circle (Vec2 x y) r
class Geometric a where
inBounds :: AABB -> a -> Bool
class Intersect a where
intersect :: a -> a -> Bool
intersectGet :: Intersect a => a -> a -> Maybe (a, a)
intersectGet x y =
if intersect x y
then Just (x, y)
else Nothing
instance Intersect Circle where
intersect (Circle p r) (Circle q r') =
distanceSq p q <= (r + r') ** 2
instance Geometric Vec2 where
inBounds (AABB (Vec2 x y) (Vec2 w h)) (Vec2 x' y') =
x' `between` (x, x + w)
&& y' `between` (y, y + h)
instance Geometric Circle where
inBounds bounds (Circle xy r) =
distanceSq xy (xy `clampedBy` aabbEdges bounds) <= r ** 2
data QTree a
= QLeaf AABB [a]
| QNode AABB (QTree a) (QTree a) (QTree a) (QTree a)
deriving Show
emptyQT :: AABB -> QTree a
emptyQT bounds = QLeaf bounds []
buildQT :: Geometric a => Int -> Int -> AABB -> [a] -> QTree a
buildQT maxCapacity maxDepth bounds =
foldr (insert maxCapacity maxDepth) (emptyQT bounds)
nodeBounds :: QTree a -> AABB
nodeBounds (QLeaf bounds _) = bounds
nodeBounds (QNode bounds _ _ _ _) = bounds
subdivide :: Geometric a => QTree a -> QTree a
subdivide (QLeaf bounds items) =
let (nwb, neb, swb, seb) = quadrisect bounds in
QNode
bounds
(itemsFor nwb)
(itemsFor neb)
(itemsFor swb)
(itemsFor seb)
where
itemsFor b =
QLeaf b $ filter (inBounds b) items
subdivide node = node
insert :: Geometric a => Int -> Int -> a -> QTree a -> QTree a
insert maxCapacity maxDepth item node =
if inBounds (nodeBounds node) item
then go 0 node
else node
where
go depth node@(QLeaf bounds items) =
if length items >= maxCapacity && depth < maxDepth
then go depth (subdivide node)
else QLeaf bounds $ item:items
go depth (QNode bounds nw ne sw se) =
QNode
bounds
(go (depth+1) nw)
(go (depth+1) ne)
(go (depth+1) sw)
(go (depth+1) se)
quadrants :: QTree a -> [[a]]
quadrants = go
where
go (QLeaf _ items) = [items]
go (QNode _ nw ne sw se) =
go nw ++ go ne ++ go sw ++ go se
data ID a
= ID Int a
deriving Show
instance Eq (ID a) where
ID i _ == ID j _ = i == j
instance Ord (ID a) where
compare (ID i _) (ID j _) = compare i j
instance Geometric a => Geometric (ID a) where
inBounds bounds (ID _ x) = inBounds bounds x
instance Intersect a => Intersect (ID a) where
intersect (ID i x) (ID j y) = i /= j && intersect x y
-- upper triangle of cartesian product; pairwise order is critical down the line
symmetricProductWith :: (a -> a -> b) -> [a] -> [b]
symmetricProductWith f = go
where
go [] = []
go (x:xs) =
map (f x) xs ++ go xs
enumerate :: [a] -> [ID a]
enumerate = zipWith ID [0..]
unique :: Ord a => [a] -> [a]
unique = Set.toList . Set.fromList
findCollisions :: Intersect a => [a] -> [(a, a)]
findCollisions =
catMaybes . symmetricProductWith intersectGet
collisionCount :: (Geometric a, Intersect a) => AABB -> [a] -> Int
collisionCount bounds items =
items
|> enumerate
|> buildQT 50 7 bounds
|> quadrants
|> concatMap findCollisions
|> unique
|> length
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment