{-# OPTIONS -fglasgow-exts #-}
-- | Find actors in the world that are in contact with each other.
module Contact where
import World
import QuadTree
import Actor
import Graphics.Gloss.Picture (Point)
import Graphics.Gloss.Geometry.Line
import Data.Maybe
import Data.List
import GHC.Exts
import GHC.Prim
import Data.Map (Map)
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Map as Map
-- Find all pairs of actors in the world that are in contact with each other.
findContacts
:: World
-> ( Set (Index, Index) -- ^ a set of all pairs of actors that are in contact.
, QuadTree Actor) -- ^ also return the quadtree so we can draw it in the window.
findContacts (World actors _)
= let
-- the initial tree has no actors in it and has a
-- size of 300 (with is half the width of the box).
treeInit = treeZero 300
-- insert all the actors into the quadtree.
tree' = Map.fold insertActor treeInit actors
-- the potential contacts are lists of actors
-- that _might_ be in contact.
potentialContacts
= treeElems tree'
-- filter the lists of potential contacts to determine the actors
-- which are _actually_ in contact.
contactSet = makeContacts potentialContacts
in (contactSet, tree')
-- | Make add all these test pairs to a map
-- normalise so the actor with the lowest ix is first in the pair.
makeContacts :: [[Actor]] -> Set (Index, Index)
makeContacts contactLists
= makeContacts' Set.empty contactLists
makeContacts' acc xx
= case xx of
-- no more potentials to add, return the current contact set
[] -> acc
-- add pairs of actors that are actually in contact to the contact set
(list : lists)
-> makeContacts' (makeTests acc list) lists
makeTests acc [] = acc
makeTests acc (x:xs)
= makeTests (makeTests1 acc x xs) xs
makeTests1 acc a1 [] = acc
makeTests1 acc a1 (a2 : as)
| inContact a1 a2
= let k1 = actorIx a1
k2 = actorIx a2
contact = (min k1 k2, max k1 k2)
acc' = Set.insert contact acc
in makeTests1 acc' a1 as
| otherwise
= makeTests1 acc a1 as
-- See if these two actors are in contact
inContact :: Actor -> Actor -> Bool
inContact a1 a2
| isBead a1 && isWall a2 = inContact_beadWall a1 a2
| isWall a1 && isBead a2 = inContact_beadWall a2 a1
| isBead a1 && isBead a2 = inContact_beadBead a1 a2
| otherwise = False
-- | Check whether a bead is in contact with a wall.
inContact_beadWall :: Actor -> Actor -> Bool
inContact_beadWall
bead@(Bead ix mode radius pBead _)
wall@(Wall _ pWall1 pWall2)
= let -- work out the point on the infinite line between pWall1 and pWall2
-- which is closest to the bead.
pClosest = closestPointOnLine pWall1 pWall2 pBead
-- the distance between the bead center and pClosest
-- needs to be less than the bead radius for them to touch.
!(F# radius#) = radius
closeEnough = distancePP_contact pBead pClosest `ltFloat#` radius#
-- uParam gives where pClosest is relative to the endponts of the wall
uParam = closestPointOnLine_param pWall1 pWall2 pBead
-- pClosest needs to lie on the line segment between pWal1 and pWall2
inSegment = uParam >= 0 && uParam <= 1
in closeEnough && inSegment
-- | Check whether a bead is in concat with another bead.
inContact_beadBead :: Actor -> Actor -> Bool
inContact_beadBead
bead1@(Bead ix1 _ radius1 pBead1 _)
bead2@(Bead ix2 _ radius2 pBead2 _)
=let !dist# = distancePP_contact pBead1 pBead2
!(F# rad) = radius1 + radius2
in (dist# `ltFloat#` rad ) && (dist# `gtFloat#` 0.1#)
-- | Return the distance between these two points.
{-# INLINE distancePP_contact #-}
distancePP_contact :: Point -> Point -> Float#
distancePP_contact (F# x1, F# y1) (F# x2, F# y2)
= sqrtFloat# (xd2 `plusFloat#` yd2)
where !xd = x2 `minusFloat#` x1
!xd2 = xd `timesFloat#` xd
!yd = y2 `minusFloat#` y1
!yd2 = yd `timesFloat#` yd