{-# LANGUAGE DeriveGeneric #-}
module Game.LambdaHack.Common.Vector
( Vector(..), VectorI
, isUnit, isDiagonal, neg, chessDistVector, euclidDistSqVector
, moves, movesCardinal, movesCardinalI, movesDiagonal, movesDiagonalI
, compassText, vicinityBounded, vicinityUnsafe
, vicinityCardinal, vicinityCardinalUnsafe, squareUnsafeSet
, shift, shiftBounded, trajectoryToPath, trajectoryToPathBounded
, vectorToFrom, computeTrajectory
, RadianAngle, rotate, towards
#ifdef EXPOSE_INTERNAL
, _moveTexts, longMoveTexts, movesSquare, pathToTrajectory
, normalize, normalizeVector
#endif
) where
import Prelude ()
import Game.LambdaHack.Core.Prelude
import Control.DeepSeq
import Data.Binary
import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES
import Data.Int (Int32)
import qualified Data.IntSet as IS
import qualified Data.Primitive.PrimArray as PA
import GHC.Generics (Generic)
import Game.LambdaHack.Common.Point
import Game.LambdaHack.Common.Time
import Game.LambdaHack.Definition.Defs
data Vector = Vector
{ vx :: X
, vy :: Y
}
deriving (Show, Read, Eq, Ord, Generic)
instance Binary Vector where
put = put . (fromIntegral :: Int -> Int32) . fromEnum
get = fmap (toEnum . (fromIntegral :: Int32 -> Int)) get
instance Enum Vector where
fromEnum Vector{..} =
let !xsize = PA.indexPrimArray speedupHackXSize 0
in vx + vy * xsize
toEnum n =
let !xsize = PA.indexPrimArray speedupHackXSize 0
!xsizeHalf = xsize `div` 2
(!y, !x) = n `quotRem` xsize
(!vx, !vy) | x >= xsizeHalf = (x - xsize, y + 1)
| x <= - xsizeHalf = (x + xsize, y - 1)
| otherwise = (x, y)
in Vector{..}
instance NFData Vector
type VectorI = Int
isUnit :: Vector -> Bool
{-# INLINE isUnit #-}
isUnit v = chessDistVector v == 1
isDiagonal :: Vector -> Bool
{-# INLINE isDiagonal #-}
isDiagonal (Vector x y) = x * y /= 0
neg :: Vector -> Vector
{-# INLINE neg #-}
neg (Vector vx vy) = Vector (-vx) (-vy)
chessDistVector :: Vector -> Int
{-# INLINE chessDistVector #-}
chessDistVector (Vector x y) = max (abs x) (abs y)
euclidDistSqVector :: Vector -> Vector -> Int
euclidDistSqVector (Vector x0 y0) (Vector x1 y1) =
(x1 - x0) ^ (2 :: Int) + (y1 - y0) ^ (2 :: Int)
moves :: [Vector]
moves =
map (uncurry Vector)
[(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)]
movesCardinal :: [Vector]
movesCardinal = map (uncurry Vector) [(0, -1), (1, 0), (0, 1), (-1, 0)]
movesCardinalI :: [VectorI]
movesCardinalI = map fromEnum movesCardinal
movesDiagonal :: [Vector]
movesDiagonal = map (uncurry Vector) [(-1, -1), (1, -1), (1, 1), (-1, 1)]
movesDiagonalI :: [VectorI]
movesDiagonalI = map fromEnum movesDiagonal
_moveTexts :: [Text]
_moveTexts = ["NW", "N", "NE", "E", "SE", "S", "SW", "W"]
longMoveTexts :: [Text]
longMoveTexts = [ "northwest", "north", "northeast", "east"
, "southeast", "south", "southwest", "west" ]
compassText :: Vector -> Text
compassText v = let m = EM.fromList $ zip moves longMoveTexts
assFail = error $ "not a unit vector" `showFailure` v
in EM.findWithDefault assFail v m
insideP :: Point -> (X, Y, X, Y) -> Bool
{-# INLINE insideP #-}
insideP (Point x y) (x0, y0, x1, y1) = x1 >= x && x >= x0 && y1 >= y && y >= y0
vicinityBounded :: X -> Y
-> Point
-> [Point]
vicinityBounded rXmax rYmax p =
if insideP p (1, 1, rXmax - 2, rYmax - 2)
then vicinityUnsafe p
else [ res | dxy <- moves
, let res = shift p dxy
, insideP res (0, 0, rXmax - 1, rYmax - 1) ]
vicinityUnsafe :: Point -> [Point]
{-# INLINE vicinityUnsafe #-}
vicinityUnsafe p = [ shift p dxy | dxy <- moves ]
vicinityCardinal :: X -> Y
-> Point
-> [Point]
vicinityCardinal rXmax rYmax p =
[ res | dxy <- movesCardinal
, let res = shift p dxy
, insideP res (0, 0, rXmax - 1, rYmax - 1) ]
vicinityCardinalUnsafe :: Point -> [Point]
vicinityCardinalUnsafe p = [ shift p dxy | dxy <- movesCardinal ]
movesSquare :: [VectorI]
movesSquare = map (fromEnum . uncurry Vector)
[ (-1, -1), (0, -1), (1, -1)
, (-1, 0), (0, 0), (1, 0)
, (-1, 1), (0, 1), (1, 1) ]
squareUnsafeSet :: Point -> ES.EnumSet Point
{-# INLINE squareUnsafeSet #-}
squareUnsafeSet p =
ES.intSetToEnumSet $ IS.fromDistinctAscList $ map (fromEnum p +) movesSquare
shift :: Point -> Vector -> Point
{-# INLINE shift #-}
shift (Point x0 y0) (Vector x1 y1) = Point (x0 + x1) (y0 + y1)
shiftBounded :: X -> Y -> Point -> Vector -> Point
shiftBounded rXmax rYmax pos v@(Vector xv yv) =
if insideP pos (-xv, -yv, rXmax - xv - 1, rYmax - yv - 1)
then shift pos v
else pos
trajectoryToPath :: Point -> [Vector] -> [Point]
trajectoryToPath _ [] = []
trajectoryToPath start (v : vs) = let next = shift start v
in next : trajectoryToPath next vs
trajectoryToPathBounded :: X -> Y -> Point -> [Vector] -> [Point]
trajectoryToPathBounded _ _ _ [] = []
trajectoryToPathBounded rXmax rYmax start (v : vs) =
let next = shiftBounded rXmax rYmax start v
in next : trajectoryToPathBounded rXmax rYmax next vs
vectorToFrom :: Point -> Point -> Vector
{-# INLINE vectorToFrom #-}
vectorToFrom (Point x0 y0) (Point x1 y1) = Vector (x0 - x1) (y0 - y1)
pathToTrajectory :: [Point] -> [Vector]
pathToTrajectory [] = []
pathToTrajectory lp1@(_ : lp2) = zipWith vectorToFrom lp2 lp1
computeTrajectory :: Int -> Int -> Int -> [Point] -> ([Vector], (Speed, Int))
computeTrajectory weight throwVelocity throwLinger path =
let speed = speedFromWeight weight throwVelocity
trange = rangeFromSpeedAndLinger speed throwLinger
btrajectory = pathToTrajectory $ take (trange + 1) path
in (btrajectory, (speed, trange))
type RadianAngle = Double
rotate :: RadianAngle -> Vector -> Vector
rotate angle (Vector x' y') =
let x = fromIntegral x'
y = fromIntegral y'
dx = x * cos (-angle) - y * sin (-angle)
dy = x * sin (-angle) + y * cos (-angle)
in normalize dx dy
normalize :: Double -> Double -> Vector
normalize dx dy =
assert (dx /= 0 || dy /= 0 `blame` "can't normalize zero" `swith` (dx, dy)) $
let angle :: Double
angle = atan (dy / dx) / (pi / 2)
dxy | angle <= -0.75 && angle >= -1.25 = (0, -1)
| angle <= -0.25 = (1, -1)
| angle <= 0.25 = (1, 0)
| angle <= 0.75 = (1, 1)
| angle <= 1.25 = (0, 1)
| otherwise = error $ "impossible angle" `showFailure` (dx, dy, angle)
in if dx >= 0
then uncurry Vector dxy
else neg $ uncurry Vector dxy
normalizeVector :: Vector -> Vector
normalizeVector v@(Vector vx vy) =
let res = normalize (fromIntegral vx) (fromIntegral vy)
in assert (not (isUnit v) || v == res
`blame` "unit vector gets untrivially normalized"
`swith` (v, res))
res
towards :: Point -> Point -> Vector
towards pos0 pos1 =
assert (pos0 /= pos1 `blame` "towards self" `swith` (pos0, pos1))
$ normalizeVector $ pos1 `vectorToFrom` pos0