Last active
June 16, 2018 23:48
-
-
Save Garciat/ad044e8828a04b223f57c06f7b45c860 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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