    {-# LANGUAGE CPP                   #-}
    {-# LANGUAGE DeriveDataTypeable    #-}
    {-# LANGUAGE FlexibleContexts      #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE TemplateHaskell       #-}
    {-# LANGUAGE TypeFamilies          #-}
    {-# LANGUAGE ScopedTypeVariables   #-}
    {-# LANGUAGE UndecidableInstances  #-}

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Diagrams.ThreeD.Camera
    -- Copyright   :  (c) 2013 diagrams-lib team (see LICENSE)
    -- License     :  BSD-style (see LICENSE)
    -- Maintainer  :  diagrams-discuss@googlegroups.com
    --
    -- Types to specify viewpoint for 3D rendering.
    --
    -----------------------------------------------------------------------------

    module Diagrams.ThreeD.Camera
           ( Camera  -- do not export constructor
            -- These are safe to construct manually
           , PerspectiveLens(..), OrthoLens(..)
           , horizontalFieldOfView, verticalFieldOfView
           , orthoWidth, orthoHeight
           , camLoc, camForward, camUp, camRight, camLens
           , facing_ZCamera, mm50Camera
           , mm50, mm50Wide, mm50Narrow
           , aspect, camAspect
           )
           where

    import           Control.Lens           (makeLenses)
    import           Data.Monoid
    import           Data.Typeable

    import           Diagrams.Angle
    import           Diagrams.Core
    import           Diagrams.Direction
    import           Diagrams.ThreeD.Vector

    import           Linear.V3

    -- Parameterize Camera on the lens type, so that Backends can express which
    -- lenses they handle.
    data Camera l n = Camera
        { camLoc  :: Point V3 n
        , forward :: V3 n
        , up      :: V3 n
        , lens    :: l n
        }
    #if __GLASGOW_HASKELL__ >= 707
      deriving Typeable
    #else

    instance forall l. Typeable1 l => Typeable1 (Camera l) where
      typeOf1 _ = mkTyConApp (mkTyCon3 "diagrams-lib" "Diagrams.ThreeD.Camera" "Camera") [] `mkAppTy`
                  typeOf1 (undefined :: l n)
    #endif

    type instance V (Camera l n) = V3
    type instance N (Camera l n) = n

    #if __GLASGOW_HASKELL__ > 707
    class Typeable l => CameraLens l where
    #else
    class Typeable1 l => CameraLens l where
    #endif
      -- | The natural aspect ratio of the projection.
      aspect :: Floating n => l n -> n

    -- | A perspective projection
    data PerspectiveLens n = PerspectiveLens
      { _horizontalFieldOfView :: Angle n -- ^ Horizontal field of view.
      , _verticalFieldOfView   :: Angle n -- ^ Vertical field of view.
      }
      deriving Typeable

    makeLenses ''PerspectiveLens

    type instance V (PerspectiveLens n) = V3
    type instance N (PerspectiveLens n) = n

    instance CameraLens PerspectiveLens where
      aspect (PerspectiveLens h v) = angleRatio h v

    -- | An orthographic projection
    data OrthoLens n = OrthoLens
                   { _orthoWidth  :: n -- ^ Width
                   , _orthoHeight :: n -- ^ Height
                   }
      deriving Typeable

    makeLenses ''OrthoLens

    type instance V (OrthoLens n) = V3
    type instance N (OrthoLens n) = n

    instance CameraLens OrthoLens where
      aspect (OrthoLens h v) = h / v

    instance Num n => Transformable (Camera l n) where
      transform t (Camera p f u l) =
          Camera (transform t p)
                 (transform t f)
                 (transform t u)
                 l

    instance Num n => Renderable (Camera l n) NullBackend where
      render _ _ = mempty

    -- | A camera at the origin facing along the negative Z axis, with its
    -- up-axis coincident with the positive Y axis.  The field of view is
    -- chosen to match a 50mm camera on 35mm film. Note that Cameras take
    -- up no space in the Diagram.
    mm50Camera :: (Typeable n, Floating n, Ord n, Renderable (Camera PerspectiveLens n) b)
               => QDiagram b V3 n Any
    mm50Camera = facing_ZCamera mm50

    -- | 'facing_ZCamera l' is a camera at the origin facing along the
    -- negative Z axis, with its up-axis coincident with the positive Y
    -- axis, with the projection defined by l.
    facing_ZCamera :: (Floating n, Ord n, Typeable n, CameraLens l, Renderable (Camera l n) b) =>
                      l n -> QDiagram b V3 n Any
    facing_ZCamera l = mkQD (Prim $ Camera origin unit_Z unitY l)
            mempty mempty mempty (Query . const . Any $ False)
    {-# ANN facing_ZCamera ("HLint: ignore Use camelCase" :: String) #-}

    mm50, mm50Wide, mm50Narrow :: Floating n => PerspectiveLens n

    -- | mm50 has the field of view of a 50mm lens on standard 35mm film,
    -- hence an aspect ratio of 3:2.
    mm50 = PerspectiveLens (40.5 @@ deg) (27 @@ deg)

    -- | mm50blWide has the same vertical field of view as mm50, but an
    -- aspect ratio of 1.6, suitable for wide screen computer monitors.
    mm50Wide = PerspectiveLens (43.2 @@ deg)  (27 @@ deg)

    -- | mm50Narrow has the same vertical field of view as mm50, but an
    -- aspect ratio of 4:3, for VGA and similar computer resolutions.
    mm50Narrow = PerspectiveLens (36 @@ deg) (27 @@ deg)

    camForward :: Fractional n => Camera l n -> Direction V3 n
    camForward = direction . forward

    camUp :: Fractional n => Camera l n -> Direction V3 n
    camUp = direction . up

    camRight :: Fractional n => Camera l n -> Direction V3 n
    camRight c = direction right where
      right = cross (forward c) (up c)

    camLens :: Camera l n -> l n
    camLens = lens

    camAspect :: (Floating n, CameraLens l) => Camera l n -> n
    camAspect = aspect . camLens