-- | -- Module: Control.Wire.Prefab.Move -- Copyright: (c) 2012 Ertugrul Soeylemez -- License: BSD3 -- Maintainer: Ertugrul Soeylemez -- -- This module provides the wires for various kinds of moving objects. -- In particular this includes various calculus wires like integrals and -- differentials. module Control.Wire.Prefab.Move ( -- * Calculus -- ** Integrals integral, integral_, integralLim, integralLim_, integral1, integral1_, integralLim1, integralLim1_, -- ** Differentials derivative, derivative_, -- * Simulations/games object, object_, ObjectState(..), ObjectDiff(..) ) where import Control.Applicative import Control.Arrow import Control.Category import Control.Wire.Prefab.Accum import Control.Wire.Prefab.Time import Control.Wire.Wire import Data.Data import Data.VectorSpace import Prelude hiding ((.), id) -- | Object state. This includes the position and velocity. data ObjectState a = ObjectState { objPosition :: a, -- ^ Position. objVelocity :: a -- ^ Velocity. } deriving (Data, Eq, Ord, Read, Show, Typeable) -- | Differential for objects. data ObjectDiff a -- | Accelerate (units per second). = Accelerate a -- | Teleport to the given position instantly (velocity will be -- unchanged). | Position a -- | Specify velocity (units per second). | Velocity a deriving (Data, Eq, Ord, Read, Show, Typeable) -- | Derivative. Receives @x@ and @dt@ and calculates the change rate -- @dx/dt@. Note that @dt@ despite its name does not have to be time. -- -- The exception handler function is called when @dt@ is zero. That -- function's result is the wire's output for those instants. If you -- don't want to handle exceptional cases specially, just pass @(^/)@ as -- the handler function. -- -- * Depends: current instant. derivative :: (Eq dt, Fractional dt, VectorSpace b, Scalar b ~ dt) => (b -> dt -> b) -- ^ Handle exceptional change rates (receives dx and dt). -> b -- ^ Initial position. -> Wire e m (b, dt) b derivative catch x0 = mkPure $ \_ (x1, dt) -> let dx = x1 ^-^ x0 d | dt == 0 = catch dx dt | otherwise = dx ^/ dt in (Right d, derivative catch x1) -- | Same as 'derivative', but with respect to time. -- -- * Depends: current instant. derivative_ :: (Monad m, VectorSpace b, Scalar b ~ Time) => (b -> Time -> b) -- ^ Handle exceptional cases. -> b -- ^ Initial position. -> Wire e m b b derivative_ catch x0 = derivative catch x0 . (id &&& dtime) -- | Integral wire. Produces position from velocity in the sense of the -- given vector space. -- -- * Depends: previous instant. integral :: (VectorSpace b) => b -> Wire e m (b, Scalar b) b integral = accum (\x (dx, dt) -> x ^+^ dt *^ dx) -- | Non-delaying variant of 'integral'. -- -- * Depends: current instant. integral1 :: (VectorSpace b) => b -> Wire e m (b, Scalar b) b integral1 = accum1 (\x (dx, dt) -> x ^+^ dt *^ dx) -- | Same as 'integral', but with respect to time. -- -- * Depends: previous instant. integral_ :: (VectorSpace b, Scalar b ~ Time) => b -> Wire e m b b integral_ = accumT (\dt x dx -> x ^+^ dt *^ dx) -- | Non-delaying variant of 'integral_'. -- -- * Depends: current instant. integral1_ :: (Monad m, VectorSpace b, Scalar b ~ Time) => b -> Wire e m b b integral1_ = accumT1 (\dt x dx -> x ^+^ dt *^ dx) -- | Variant of 'integral', where you can specify a post-update -- function, which receives the previous position as well as the current -- (in that order). This is useful for limiting the output (think of -- robot arms that can't be moved freely). -- -- * Depends: current instant if the post-update function is strict in -- its first argument, previous instant if not. integralLim :: (VectorSpace b) => (w -> b -> b -> b) -- ^ Post-update function. -> b -- ^ Initial value. -> Wire e m ((b, w), Scalar b) b integralLim uf = accum (\x ((dx, w), dt) -> uf w x (x ^+^ dt *^ dx)) -- | Non-delaying variant of 'integralLim'. -- -- * Depends: current instant. integralLim1 :: (VectorSpace b) => (w -> b -> b -> b) -- ^ Post-update function. -> b -- ^ Initial value. -> Wire e m ((b, w), Scalar b) b integralLim1 uf = accum1 (\x ((dx, w), dt) -> uf w x (x ^+^ dt *^ dx)) -- | Same as 'integralLim', but with respect to time. -- -- * Depends: previous instant. integralLim_ :: (VectorSpace b, Scalar b ~ Time) => (w -> b -> b -> b) -> b -> Wire e m (b, w) b integralLim_ uf = accumT (\dt x (dx, w) -> uf w x (x ^+^ dt *^ dx)) -- | Non-delaying variant of 'integralLim_'. -- -- * Depends: current instant. integralLim1_ :: (VectorSpace b, Scalar b ~ Time) => (w -> b -> b -> b) -> b -> Wire e m (b, w) b integralLim1_ uf = accumT1 (\dt x (dx, w) -> uf w x (x ^+^ dt *^ dx)) -- | Objects are generalized integrals. They are controlled through -- velocity and/or acceleration and can be collision-checked as well as -- instantly teleported. -- -- The post-move update function receives the world state and the -- current object state. It is applied just before the wire produces -- its output. You can use it to perform collision-checks or to limit -- the velocity. -- -- Note that teleportation doesn't change the velocity. -- -- * Depends: current instant. object :: forall b m dt e w. (VectorSpace b, Scalar b ~ dt) => (w -> ObjectState b -> ObjectState b) -- ^ Post-move update function. -> ObjectState b -- ^ Initial state. -> Wire e m (ObjectDiff b, w, dt) (ObjectState b) object uf = loop where applyDiff :: dt -> ObjectDiff b -> ObjectState b -> ObjectState b applyDiff dt (Accelerate dv) (ObjectState x' v') = ObjectState x v where v = v' ^+^ dt *^ dv x = x' ^+^ dt *^ v applyDiff _ (Position x) (ObjectState _ v) = ObjectState x v applyDiff dt (Velocity v) (ObjectState x' _) = ObjectState (x' ^+^ dt *^ v) v loop :: ObjectState b -> Wire e m (ObjectDiff b, w, dt) (ObjectState b) loop os' = mkPure $ \_ (dos, w, dt) -> let os = uf w . applyDiff dt dos $ os' in (Right os, loop os) -- | Same as 'object', but with respect to time. -- -- * Depends: current instant. object_ :: (Monad m, VectorSpace b, Scalar b ~ Time) => (w -> ObjectState b -> ObjectState b) -- ^ Post-move update function. -> ObjectState b -- ^ Initial state. -> Wire e m (ObjectDiff b, w) (ObjectState b) object_ uf x0 = object uf x0 . liftA2 (\(dx, w) dt -> (dx, w, dt)) id dtime