{- $Id: Object.hs,v 1.2 2003/11/10 21:28:58 antony Exp $
******************************************************************************
*                              I N V A D E R S                               *
*                                                                            *
*       Module:		Object						     *
*       Purpose:	Definition of objects in the world and their static  *
*			properties.					     *
*       Author:		Henrik Nilsson					     *
*                                                                            *
*             Copyright (c) Yale University, 2003                            *
*                                                                            *
******************************************************************************
-}

module Object (
    Object,
    ObjInput(..),
    ObjOutput(..),
    ObsObjState(..),
    oosGun,		-- :: Position2 -> Velocity2 -> ObsObjState
    oosFallingGun,	-- :: Position2 -> Velocity2 -> ObsObjState
    oosMissile,		-- :: Position2 -> Velocity2 -> ObsObjState
    oosAlien,		-- :: Position2 -> Heading -> Velocity2 -> ObsObjState
    isGun,		-- :: ObsObjState -> Bool
    isFallingGun,	-- :: ObsObjState -> Bool
    isMissile,		-- :: ObsObjState -> Bool
    isAlien,		-- :: ObsObjState -> Bool
    touches,		-- :: ObsObjState -> ObsObjState -> Bool
    approaches,		-- :: ObsObjState -> ObsObjState -> Bool
    colliding,		-- :: ObsObjState -> ObsObjState -> Bool
    gunRadius,		-- :: Length
    gunBase,		-- :: Length
    gunHeight,		-- :: Length
    gunSpeedMax,	-- :: Speed
    gunAccMax,		-- :: Acceleration
    missileRadius, 	-- :: Length
    missileInitialSpeed,-- :: Speed
    missileLifeSpan,	-- :: Time
    alienRadius,	-- :: Length
    alienWingRadius,	-- :: Length
    alienAccMax		-- :: Acceleration
) where

import Debug.Trace
import AFRP (SF, Event)
import AFRPGeometry
import AFRPForceable

import Parser (GameInput)
import PhysicalDimensions
import WorldGeometry


------------------------------------------------------------------------------
-- Object and related types
------------------------------------------------------------------------------

-- Objects are represented by signal functions, i.e. they are reactive and
-- can carry internal state.

type Object = SF ObjInput ObjOutput

data ObjInput = ObjInput {
    oiHit       :: Event Velocity,  -- Velocity only used for gun hitting gun
    oiGameInput :: GameInput
}

data ObjOutput = ObjOutput {
    ooObsObjState :: !ObsObjState,
    ooKillReq     :: Event (),
    ooSpawnReq    :: Event [Object]
}

-- Note: ObsObjState (Observable Object State) should only be constructed/
-- updated through the provided constructors/update functions since some of
-- the fields (e.g. if a bounding box field were added) might be dependent on
-- others. The reason ObsObjState is not exported abstractly is that it is
-- convenient to inspect it by pattern matching.
-- 
-- To avoid space leaks, all fields (except possibly dependent ones) are
-- strict.

data ObsObjState =
      OOSGun {
	  oosPos    :: !Position2,
	  oosVel    :: !Velocity2,
          oosRadius :: !Length,
          oosAmLvl  :: !Int
      }
    | OOSFallingGun {
	  oosPos    :: !Position2,
	  oosVel    :: !Velocity2,
          oosRadius :: !Length
      }
    | OOSMissile {
	  oosPos    :: !Position2,
	  oosVel    :: !Velocity2,
          oosRadius :: !Length
      }
    | OOSAlien {
	  oosPos    :: !Position2,
	  oosHdng   :: !Heading,
	  oosVel    :: !Velocity2,
          oosRadius :: !Length,
          oosHasGun :: !Bool
      }


------------------------------------------------------------------------------
-- Instances
------------------------------------------------------------------------------

instance Forceable ObsObjState where
    -- If non-strict fields: oosNonStrict1 obj `seq` ... `seq` obj 
    force obj = obj


------------------------------------------------------------------------------
-- Smart constructors
------------------------------------------------------------------------------

-- If dependent fields (such as a bounding box) are added, these should also
-- be computed here.

oosFallingGun :: Position2 -> Velocity2 -> ObsObjState
oosFallingGun p v = OOSFallingGun {
                   oosPos    = p,
	           oosVel    = v,
	           oosRadius = gunRadius
               }

oosGun :: Position2 -> Velocity2 -> Int -> ObsObjState
oosGun p v l = OOSGun {
                   oosPos    = p,
	           oosVel    = v,
	           oosRadius = gunRadius,
		   oosAmLvl  = l
               }


oosMissile :: Position2 -> Velocity2 -> ObsObjState
oosMissile p v = OOSMissile {
                     oosPos    = p,
	             oosVel    = v,
	             oosRadius = missileRadius
                 }


oosAlien :: Position2 -> Heading -> Velocity2 -> Bool -> ObsObjState
oosAlien p h v g = OOSAlien {
                     oosPos    = p,
	             oosHdng   = normalizeHeading h,
	             oosVel    = v,
	             oosRadius = alienRadius,
                     oosHasGun = g
                 }


------------------------------------------------------------------------------
-- Recognizers
------------------------------------------------------------------------------

isGun :: ObsObjState -> Bool
isGun (OOSGun {}) = True
isGun _           = False

isFallingGun :: ObsObjState -> Bool
isFallingGun (OOSFallingGun {}) = True
isFallingGun _ = False

isMissile :: ObsObjState -> Bool
isMissile (OOSMissile {}) = True
isMissile _               = False


isAlien :: ObsObjState -> Bool
isAlien (OOSAlien {}) = True
isAlien _             = False


------------------------------------------------------------------------------
-- Object and bounding box geometrical predicates
------------------------------------------------------------------------------


-- Check if two objects touch each other.
-- Currently only a radius-based intersection test.
touches :: ObsObjState -> ObsObjState -> Bool
oos1 `touches` oos2 =
    norm ((oosPos oos2) .-. (oosPos oos1)) < (oosRadius oos2 + oosRadius oos1)


-- Check if two objects are approaching each other.
approaches :: ObsObjState -> ObsObjState -> Bool
oos1 `approaches` oos2 =
    (oosVel oos2 ^-^ oosVel oos1) `dot` (oosPos oos2 .-. oosPos oos1) < 0.0


-- Check if two objects are colliding.
colliding :: ObsObjState -> ObsObjState -> Bool
oos1 `colliding` oos2 = oos1 `touches` oos2 && oos1 `approaches` oos2


------------------------------------------------------------------------------
-- Constants defining various object properties
------------------------------------------------------------------------------

-- All objects are treated as round as far as intersection tests etc. are
-- concerned, but they may be drawn using a different shape. It is assumed
-- that that shape is centered around the position of the object, and that
-- its size roughly corresponds to the size implied by the object radius.

-- Constants for gun.

-- Gun: drawn triangular
gunRadius, gunBase, gunHeight :: Length
gunRadius = 25
gunBase   = 2 * gunRadius
gunHeight = gunBase

gunSpeedMax :: Speed
gunSpeedMax = 500

gunAccMax :: Acceleration
gunAccMax = 500


-- Constants for missile.

-- Missiles: drawn circular.
missileRadius :: Length
missileRadius = 5.0

missileInitialSpeed :: Speed
missileInitialSpeed = 200

missileLifeSpan :: Time
missileLifeSpan = 5.0


-- Constants for alien.

-- Alien: drawn as a sphere with equatorial wing plane ("saturn-shaped")
alienRadius, alienWingRadius :: Length
alienRadius = 25
alienWingRadius = 2 * alienRadius

alienAccMax :: Acceleration
alienAccMax = 100


------------------------------------------------------------------------------
-- Support functions
------------------------------------------------------------------------------

{-
-- Old stuff

-- Bounding box is calculated once and cached inside object.

computeObjBBox :: Object -> BBox
computeObjBBox (ObjBlock {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 (blockSide / 2) (blockSide / 2)
computeObjBBox (ObjNSWall {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 (nsWallXSide / 2) (nsWallYSide / 2)
computeObjBBox (ObjEWWall {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 (ewWallXSide / 2) (ewWallYSide / 2)
computeObjBBox (ObjSimbotA {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 simbotARadius simbotARadius
computeObjBBox (ObjSimbotB {objPos = p, objHdng = d}) = BBox p1 p2
    where
        Point2 x1 y1 = p .+^ (vector2Polar simbotBRadius d) -- Nose
        Point2 x2 y2 = p .+^ (vector2Polar simbotBRadius (d + 2*pi/3))
        Point2 x3 y3 = p .+^ (vector2Polar simbotBRadius (d - 2*pi/3))

        p1 = Point2 (minimum [x1,x2,x3]) (minimum [y1,y2,y3])
        p2 = Point2 (maximum [x1,x2,x3]) (maximum [y1,y2,y3])
computeObjBBox (ObjBall {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 ballRadius ballRadius
-}
