\section{Inverse Kinematics}

\begin{code}
{-# LANGUAGE Arrows #-}
{-# OPTIONS_GHC -fallow-undecidable-instances #-}

module RSAGL.InverseKinematics
    (leg,
     Leg,
     jointAnimation,
     legs,
     approach,
     approachA)
    where

import Control.Arrow
import Control.Arrow.Operations
import Data.Fixed
import RSAGL.Vector
import RSAGL.FRP
import RSAGL.Affine
import RSAGL.CoordinateSystems
import RSAGL.KinematicSensors
import RSAGL.Vector
import RSAGL.Joint
import RSAGL.Time
import RSAGL.AbstractVector
import RSAGL.Angle
\end{code}

\subsection{The Foot}

This simulates a single foot that hops along by itself whenever its coordinate system moves.  A \texttt{foot} always trys to walk on the plane \texttt{y == 0}.
\texttt{foot} takes as input a boolean which, if true, indicates that there are not enough feet on the ground and that this \texttt{foot} should perform an
``emergency foot down.''  \texttt{foot} ignores the emergency foot down flag if it is already down.  \texttt{foot} emits the position of the foot and a boolean
which indicates if the foot is up (\texttt{True}) or down (\texttt{False}).

\texttt{foot} works by reading two odometers, one for forward movement and another for sideways movement.  This is like spinning the wheel by a magical odometer
instead of spinning the odometer by the wheel.

\texttt{pre\_stepage} is the total odometer reading for all travel in both directions.  \texttt{stepage\_adjustment} is the amount by which we need to increase
\texttt{pre\_stepage} to ensure that the foot is correct based on an accumulation of emergency foot downs.  \texttt{cyclic\_stepage} is a value between 0 and 2,
where a value greater than 1 indicates that the foot is down.

FIXME: this could be done better.

\begin{code}
foot :: (Arrow a,ArrowApply a,ArrowChoice a,CoordinateSystemClass s,ArrowState s a) => Double -> Double -> Double -> FRPX any t i o a Bool (CSN Point3D,Bool)
foot forward_radius side_radius lift_radius = proc emergency_footdown ->
    do fwd_total_stepage <- arr (* recip forward_radius) <<< odometer root_coordinate_system (Vector3D 0 0 1) -< ()
       side_total_stepage <- arr (* recip side_radius) <<< odometer root_coordinate_system (Vector3D 1 0 0) -< ()
       let pre_stepage = sqrt $ fwd_total_stepage^2 + side_total_stepage^2
       stepage_adjustment <- integralRK4 fps30 add 0 -< (\_ p -> if (p + pre_stepage) `mod'` 2 < 1 && emergency_footdown then perSecond 1 else perSecond 0) 
       let adjusted_stepage = stepage_adjustment + pre_stepage
       let cyclic_stepage = (`mod'` 2) $ adjusted_stepage
       motion <- derivative -< adjusted_stepage
       let foot_lift = max 0 $ min 1 (motion `over` fromSeconds 1) * lift_radius * (sine $ fromRotations (cyclic_stepage / 2))
       let stepage_offset = if cyclic_stepage > 1 then 1.5 - cyclic_stepage else cyclic_stepage - 0.5
       let step_vector = scale (Vector3D side_radius 0 forward_radius) $ vectorScaleTo stepage_offset $ Vector3D side_total_stepage 0 fwd_total_stepage
       foot_position <- importA <<< arr (remoteCSN root_coordinate_system $ scale (Vector3D 1 0 1)) <<< exportA -< translate step_vector origin_point_3d
       csn_foot_position <- exportA -< translate (Vector3D 0 foot_lift 0) foot_position
       returnA -< (csn_foot_position,cyclic_stepage > 1)
\end{code}

\texttt{leg} creates a leg that tries to walk over the ground no matter how it is positioned.  \texttt{legs} combines a group of legs so that at least half
of the legs always try to touch the ground.

\begin{code}
newtype Leg threaded t i o a = Leg (FRPX threaded t i o a [Bool] [Bool])

instance (ArrowChoice a,ArrowState s a,CoordinateSystemClass s) => AffineTransformable (Leg threaded t i o a) where
    transform m (Leg l) = Leg (proc x -> transformA l -< (Affine $ transform m,x))

leg :: (ArrowApply a,ArrowChoice a,ArrowState s a,CoordinateSystemClass s) => Vector3D -> Point3D -> Double -> Point3D -> (FRPX threaded t i o a Joint ()) -> Leg threaded t i o a
leg bend base len end animation = Leg $ proc feet_that_are_down ->
    do let declare_emergency_foot_down = length (filter id feet_that_are_down) < length (filter not feet_that_are_down) &&
                                         not (and $ take 1 feet_that_are_down)
       (p,foot_is_down) <- first importA <<< 
                           transformA (foot foot_radius foot_radius (foot_radius/5)) -< (Affine $ translate (vectorToFrom end origin_point_3d),declare_emergency_foot_down)
       animation -< joint bend base len p
       returnA -< (foot_is_down || declare_emergency_foot_down) : feet_that_are_down
  where foot_radius = sqrt (len^2 - (distanceBetween base end)^2) / 2

legs :: (ArrowChoice a) => [Leg threaded t i o a] -> FRPX threaded t i o a () ()
legs ls = (foldl (>>>) (arr $ const []) $ map (\(Leg l) -> l) ls) >>> (arr $ const ())
\end{code}

\texttt{jointAnimation} is just a simple combinator to combine the upper and lower components of a joint into an animated Joint.

\begin{code}
jointAnimation :: (ArrowChoice a,ArrowState s a,CoordinateSystemClass s) => FRPX any t i o a () () -> FRPX any t i o a () () -> FRPX any t i o a Joint ()
jointAnimation upperJoint lowerJoint = proc j ->
    do transformA upperJoint -< (affineOf $ joint_arm_upper j,())
       transformA lowerJoint -< (affineOf $ joint_arm_lower j,())
\end{code}

\subsection{Approach}

\texttt{approach} is an acceleration function that that tries to approach a goal point.  It begins slowing down when it comes within
the goal radius, and otherwise travels at a fixed speed toward the goal.  The goal radius and speed are defined in terms of the
\texttt{magnitude} of the vector type.

\begin{code}
approach :: (AbstractVector v,AbstractSubtract p v,AbstractMagnitude v) => p -> Double -> Rate Double -> (Time -> p -> Rate v)
approach goal_point goal_radius max_speed _ position = withTime (fromSeconds 1) (\x -> abstractScaleTo (x * speed_ratio) goal_vector) max_speed
    where goal_vector = goal_point `sub` position
          speed_ratio = min 1 $ magnitude goal_vector / goal_radius

approachA :: (ArrowChoice a,ArrowApply a,AbstractVector v,AbstractAdd p v, AbstractSubtract p v,AbstractMagnitude v) => Double -> Rate Double -> FRPX any t i o a p p
approachA goal_radius max_speed = frp1Context $ proc initial_value -> switchContinue -< (Just $ approachA_ initial_value,initial_value)
    where approachA_ initial_value = proc goal_point -> integralRK4 frequency add initial_value -< approach goal_point goal_radius max_speed
          frequency = 8 `per` time goal_radius max_speed 
\end{code}