content: {-# LANGUAGE ConstraintKinds #-}
  {-# LANGUAGE GADTs                #-} -- for ghc < 7.8, TypeFamilies covers GADT patten mathcing in > 7.8
  {-# LANGUAGE FlexibleContexts     #-}
  {-# LANGUAGE FlexibleInstances    #-}
  {-# LANGUAGE TypeFamilies         #-}
  {-# LANGUAGE UndecidableInstances #-}
  
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  Diagrams.Tangent
  -- Copyright   :  (c) 2013 diagrams-lib team (see LICENSE)
  -- License     :  BSD-style (see LICENSE)
  -- Maintainer  :  diagrams-discuss@googlegroups.com
  --
  -- Computing tangent and normal vectors for segments and trails.
  --
  -----------------------------------------------------------------------------
  module Diagrams.Tangent
      ( -- ** Tangents
        tangentAtParam
      , tangentAtStart
      , tangentAtEnd
  
        -- ** Normals
      , normalAtParam
      , normalAtStart
      , normalAtEnd
  
        -- ** Tangent newtype
      , Tangent(..)
      )
      where
  
  import           Diagrams.Core
  import           Diagrams.Located
  import           Diagrams.Parametric
  import           Diagrams.Segment
  
  import           Linear.Vector
  import           Linear.Metric
  import           Linear.V2
  
  ------------------------------------------------------------
  -- Tangent
  ------------------------------------------------------------
  
  -- | A newtype wrapper used to give different instances of
  --   'Parametric' and 'EndValues' that compute tangent vectors.
  newtype Tangent t = Tangent t
  
  type instance V (Tangent t) = V t
  type instance N (Tangent t) = N t
  type instance Codomain (Tangent t) = V t
  
  instance DomainBounds t => DomainBounds (Tangent t) where
    domainLower (Tangent t) = domainLower t
    domainUpper (Tangent t) = domainUpper t
  
  instance Parametric (Tangent t) => Parametric (Tangent (Located t)) where
    Tangent l `atParam` p = Tangent (unLoc l) `atParam` p
  
  instance (DomainBounds t, EndValues (Tangent t))
      => EndValues (Tangent (Located t)) where
    atStart (Tangent l) = atStart (Tangent (unLoc l))
    atEnd   (Tangent l) = atEnd   (Tangent (unLoc l))
  
  -- | Compute the tangent vector to a segment or trail at a particular
  --   parameter.
  --
  --   Examples of more specific types this function can have include
  --
  --   * @Segment Closed V2 -> Double -> V2 Double@
  --
  --   * @Trail' Line V2 -> Double -> V2 Double@
  --
  --   * @Located (Trail V2) -> Double -> V2 Double@
  --
  --   See the instances listed for the 'Tangent' newtype for more.
  tangentAtParam :: Parametric (Tangent t) => t -> N t -> Vn t
  tangentAtParam t p = Tangent t `atParam` p
  
  -- | Compute the tangent vector at the start of a segment or trail.
  tangentAtStart :: EndValues (Tangent t) => t -> Vn t
  tangentAtStart = atStart . Tangent
  
  -- | Compute the tangent vector at the end of a segment or trail.
  tangentAtEnd :: EndValues (Tangent t) => t -> Vn t
  tangentAtEnd = atEnd . Tangent
  
  --------------------------------------------------
  -- Segment
  
  instance (Additive v, Num n)
      => Parametric (Tangent (Segment Closed v n)) where
    Tangent (Linear (OffsetClosed v)) `atParam` _ = v
    Tangent (Cubic c1 c2 (OffsetClosed x2)) `atParam` p
      = (3*(3*p*p-4*p+1))*^c1 ^+^ (3*(2-3*p)*p)*^c2 ^+^ (3*p*p)*^x2
  
  instance (Additive v, Num n)
      => EndValues (Tangent (Segment Closed v n)) where
    atStart (Tangent (Linear (OffsetClosed v)))      = v
    atStart (Tangent (Cubic c1 _ _))                 = c1
    atEnd   (Tangent (Linear (OffsetClosed v)))      = v
    atEnd   (Tangent (Cubic _ c2 (OffsetClosed x2))) = x2 ^-^ c2
  
  instance (Additive v, Num n)
      => Parametric (Tangent (FixedSegment v n)) where
    atParam (Tangent fSeg) = atParam $ Tangent (fromFixedSeg fSeg)
  
  instance (Additive v, Num n)
      => EndValues (Tangent (FixedSegment v n)) where
    atStart (Tangent fSeg) = atStart $ Tangent (fromFixedSeg fSeg)
    atEnd (Tangent fSeg)   = atEnd $ Tangent (fromFixedSeg fSeg)
  
  ------------------------------------------------------------
  -- Normal
  ------------------------------------------------------------
  
  -- | Compute the (unit) normal vector to a segment or trail at a
  --   particular parameter.
  --
  --   Examples of more specific types this function can have include
  --
  --   * @Segment Closed V2 Double -> Double -> V2 Double@
  --
  --   * @Trail' Line V2 Double -> Double -> V2 Double@
  --
  --   * @Located (Trail V2 Double) -> Double -> V2 Double@
  --
  --   See the instances listed for the 'Tangent' newtype for more.
  normalAtParam
    :: (InSpace V2 n t, Parametric (Tangent t), Floating n)
    => t -> n -> V2 n
  normalAtParam t p = normize (t `tangentAtParam` p)
  
  -- | Compute the normal vector at the start of a segment or trail.
  normalAtStart
    :: (InSpace V2 n t, EndValues (Tangent t), Floating n)
    => t -> V2 n
  normalAtStart = normize . tangentAtStart
  
  -- | Compute the normal vector at the end of a segment or trail.
  normalAtEnd
    :: (InSpace V2 n t, EndValues (Tangent t), Floating n)
    => t -> V2 n
  normalAtEnd = normize . tangentAtEnd
  
  -- | Construct a normal vector from a tangent.
  normize :: Floating n => V2 n -> V2 n
  normize = negated . perp . signorm
  