{-
******************************************************************************
* 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
oosMissile, -- :: Position2 -> Velocity2 -> ObsObjState
oosAlien, -- :: Position2 -> Heading -> Velocity2 -> ObsObjState
isGun, -- :: 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 FRP.Yampa (SF, Event)
import FRP.Yampa.Geometry
import FRP.Yampa.Forceable
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 (),
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
}
| OOSMissile {
oosPos :: !Position2,
oosVel :: !Velocity2,
oosRadius :: !Length
}
| OOSAlien {
oosPos :: !Position2,
oosHdng :: !Heading,
oosVel :: !Velocity2,
oosRadius :: !Length
}
------------------------------------------------------------------------------
-- 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.
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 -> ObsObjState
oosAlien p h v = OOSAlien {
oosPos = p,
oosHdng = normalizeHeading h,
oosVel = v,
oosRadius = alienRadius
}
------------------------------------------------------------------------------
-- Recognizers
------------------------------------------------------------------------------
isGun :: ObsObjState -> Bool
isGun (OOSGun {}) = True
isGun _ = 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
-}