{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE BangPatterns #-} module Play.Engine.Movement where import Play.Engine.Utils import Play.Engine.Types import Control.Lens import Control.DeepSeq import GHC.Generics import Data.Typeable data Movement = Movement { _posFloatPart :: !FPoint , _speed :: !FPoint , _minSpeed :: !FPoint , _maxSpeed :: !FPoint , _acceleration :: !FPoint , _accelerationTimer :: !Int } deriving (Show, Eq, Generic, Typeable, NFData) makeFieldsNoPrefix ''Movement data MakeArgs = MakeArgs { startspeed :: !FPoint , minspeed :: !FPoint , maxspeed :: !FPoint , accel :: !FPoint } defArgs :: MakeArgs defArgs = MakeArgs { startspeed = Point 0 0 , minspeed = Point 0 0 , maxspeed = Point 1 1 , accel = Point 0.1 0.1 } make :: MakeArgs -> Movement make MakeArgs{..} = Movement { _posFloatPart = Point 0 0 , _speed = startspeed , _minSpeed = fmap abs minspeed , _maxSpeed = fmap abs maxspeed , _acceleration = accel , _accelerationTimer = 0 } updateMovement :: FPoint -> Movement -> Movement updateMovement !direction !mv = let updateSpeedC c spd = let accel = if direction ^. c /= 0 then (mv ^. acceleration . c) * (direction ^. c) else limit (abs spd) $ (mv ^. acceleration . c) * negate (normalize spd) in if mv ^. accelerationTimer == 0 then let newSpd = limit (mv ^. maxSpeed . c) . (+ accel) $ spd norm = normalize newSpd * (mv ^. minSpeed . c) in if abs newSpd < abs norm then norm else newSpd else spd in force $ mv & over (speed . x) (updateSpeedC x) & over (speed . y) (updateSpeedC y) & over accelerationTimer (\t -> if t <= 0 then 1 else t - 1) limit :: Float -> Float -> Float limit !lim !n | abs n > lim = normalize n * lim | otherwise = n normalize :: Float -> Float normalize !n | n == 0 = 0 | n > 0 = n / n | otherwise = (-1) * (n / n) update :: FPoint -> Movement -> (Movement, IPoint) update !dir !mv = let spd = (mv ^. speed) `addPoint` (mv ^. posFloatPart) addition = fmap (\s -> if s < 0 then ceiling s else floor s) spd pfp = spd `addPoint` fmap (fromIntegral . negate) addition in force (set posFloatPart pfp $ updateMovement dir mv, addition) straight :: FPoint -> Movement straight spd = make $ defArgs { startspeed = spd , maxspeed = spd , accel = Point 1 1 `mulPoint` fmap abs spd } fastGradualStart :: FPoint -> Movement fastGradualStart spd = make $ defArgs { startspeed = spd , maxspeed = fmap (*4) spd , accel = Point 0.11 0.11 `mulPoint` fmap abs spd } gradualSlowdown :: FPoint -> Movement gradualSlowdown spd = make $ defArgs { startspeed = spd , minspeed = fmap (/4) spd , maxspeed = spd , accel = Point (-0.02) (-0.02) `mulPoint` fmap abs spd }