imj-base-0.1.0.2: src/Imj/Physics/Discrete/Collision.hs
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Imj.Physics.Discrete.Collision
( -- * Handling collisions
{- | To give a better realism in the game, if the location
/before/ a collision is not touching a wall, then the position /after/ the
collision will be forced to touch a wall. Note that forcing the position
only happens if the absolute speed of one coordinate is >= 2.
-}
mirrorSpeedAndMoveToPrecollisionIfNeeded
, CollisionStatus(..)
, firstCollision
, Location(..)
) where
import Imj.Prelude
import Imj.Geo.Discrete
import Imj.Physics.Discrete.Types
-- | Describes if a collision exists.
data Location = InsideWorld
-- ^ No collision.
| OutsideWorld
-- ^ A collision exists.
deriving(Eq, Show)
data CollisionStatus = NoCollision
-- ^ no collision on the trajectory, position is unchanged
| PreCollision
-- ^ a collision exists on the trajectory,
-- position was changed to be just before the collision
-- and speed was mirrored
-- | On collision, mirrors speed and moves to the pre-collision position.
mirrorSpeedAndMoveToPrecollisionIfNeeded :: (Coords Pos -> Location)
-- ^ Interaction function.
-> PosSpeed
-- ^ Input position and speed.
-> (PosSpeed, CollisionStatus)
-- ^ The speed was potentially mirrored
mirrorSpeedAndMoveToPrecollisionIfNeeded getLocation posspeed@(PosSpeed pos speed) =
maybe
(posspeed, NoCollision)
adjustPosSpeed
$ firstCollision getLocation trajectory
where
trajectory = bresenham $ mkSegment pos $ sumPosSpeed pos speed
adjustPosSpeed (mirror, newPos) = (PosSpeed newPos $ mirrorSpeed speed mirror, PreCollision)
-- | Handles the first collision on a trajectory, assuming that the first position
-- has no collision.
firstCollision :: (Coords Pos -> Location)
-- ^ The collision function.
-> [Coords Pos]
-- ^ The trajectory (the first position is expected to be collision-free).
-> Maybe (Mirror, Coords Pos)
-- ^ On collision, the kind of speed mirroring
-- that should be applied and the position just before the collision.
firstCollision getLocation (p1:theRest@(p2:_)) =
mirrorIfNeededAtomic getLocation (PosSpeed p1 (diffPosToSpeed p2 p1)) <|> firstCollision getLocation theRest
firstCollision _ _ = Nothing
-- | Mirrors a speed
mirrorSpeed :: Coords Vel -> Mirror -> Coords Vel
mirrorSpeed (Coords dr dc) m =
case m of
MirrorRow -> Coords (negate dr) dc
MirrorCol -> Coords dr (negate dc)
MirrorAll -> Coords (negate dr) (negate dc)
-- | The kind of speed mirroring to apply in reaction to a collision.
data Mirror = MirrorRow
-- ^ Mirror the y coordinate
| MirrorCol
-- ^ Mirror the x coordinate
| MirrorAll
-- ^ Mirror x and y coordinates
-- | When continuing with current speed, if at next iteration we encounter a wall
-- (or go through a wall for diagonal case),
-- we change the speed according to the normal of the closest wall before collision
mirrorIfNeededAtomic :: (Coords Pos -> Location) -> PosSpeed -> Maybe (Mirror, Coords Pos)
mirrorIfNeededAtomic getLocation (PosSpeed pos@(Coords r c) (Coords dr dc)) =
let future = Coords (r+dr) (c+dc)
isWall coord = getLocation coord == OutsideWorld
mirror = case getLocation future of
OutsideWorld
| dr == 0 -> Just MirrorCol
| dc == 0 -> Just MirrorRow
| otherwise -> -- diagonal case
case (isWall (Coords (r+dr) c),
isWall (Coords r (c+dc))) of
(True, True) -> Just MirrorAll
(False, False) -> Just MirrorAll
(True, False) -> Just MirrorRow
(False, True) -> Just MirrorCol
InsideWorld
| dr == 0 -> Nothing
| dc == 0 -> Nothing
| otherwise -> -- diagonal case
case (isWall (Coords (r+dr) c),
isWall (Coords r (c+dc))) of
(True, True) -> Just MirrorAll
(False, False) -> Nothing
(True, False) -> Just MirrorRow
(False, True) -> Just MirrorCol
in maybe Nothing (\m -> Just (m, pos)) mirror