content: {-# LANGUAGE ConstraintKinds       #-}
  {-# LANGUAGE FlexibleContexts      #-}
  {-# LANGUAGE FlexibleInstances     #-}
  {-# LANGUAGE MultiParamTypeClasses #-}
  {-# LANGUAGE RankNTypes            #-}
  {-# LANGUAGE TypeFamilies          #-}
  
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  Diagrams.ThreeD.Projection
  -- Copyright   :  (c) 2014 diagrams team (see LICENSE)
  -- License     :  BSD-style (see LICENSE)
  -- Maintainer  :  diagrams-discuss@googlegroups.com
  --
  -- 3D projections are a way of viewing a three-dimensional objects on a
  -- two-dimensional plane.
  --
  -- This module can be used with the functions in "Linear.Projection".
  --
  -- Disclaimer: This module should be considered experimental and is
  -- likely to change.
  --
  -----------------------------------------------------------------------------
  
  module Diagrams.ThreeD.Projection
    ( -- * Orthographic projections
  
      -- $orthographic
      -- ** Parallel projections
      facingXY
    , facingXZ
    , facingYZ
  
      -- ** axonometric
      -- $axonometric
  
      -- *** Isometric projections
      -- $isometric
    , isometricApply
    , isometric
  
    , lookingAt
  
      -- ** Affine maps
    , m44AffineApply
    , m44AffineMap
    , m33AffineApply
    , m33AffineMap
  
      -- * Perspective projections
      -- $perspective
      -- ** Perspective deformations
    , m44Deformation
    , module Linear.Projection
    ) where
  
  import           Control.Lens           hiding (transform)
  import           Data.Functor.Rep
  
  import           Diagrams.Core
  import           Diagrams.Deform
  import           Diagrams.Direction
  import           Diagrams.LinearMap
  import           Diagrams.ThreeD.Types  (P3)
  import           Diagrams.ThreeD.Vector
  
  import           Linear                 as L
  import           Linear.Affine
  import           Linear.Projection
  
  ------------------------------------------------------------------------
  -- Orthographic projections
  ------------------------------------------------------------------------
  
  -- $orthographic
  -- Orthographic projections are a form of parallel projections where are
  -- projection lines are orthogonal to the projection plane.
  
  -- Parallel projections
  
  -- | Look at the xy-plane with y as the up direction.
  facingXY :: (Epsilon n, Floating n) => AffineMap V3 V2 n
  facingXY = lookingAt unitZ origin yDir
  
  -- | Look at the xz-plane with z as the up direction.
  facingXZ :: (Epsilon n, Floating n) => AffineMap V3 V2 n
  facingXZ = lookingAt unitY origin zDir
  
  -- | Look at the yz-plane with z as the up direction.
  facingYZ :: (Epsilon n, Floating n) => AffineMap V3 V2 n
  facingYZ = lookingAt unitX origin zDir
  
  -- $axonometric
  -- Axonometric projections are a type of orthographic projection where
  -- the object is rotated along one or more of its axes relative to the
  -- plane of projection.
  
  -- $isometric
  -- Isometric projections are when the scale along each axis of the
  -- projection is the same and the angle between any axis is 120
  -- degrees.
  
  -- | Apply an isometric projection given the up direction
  isometricApply :: (InSpace V3 n a, InSpace V2 n b, AffineMappable a b, Floating n, Epsilon n)
                 => Direction V3 n -> a -> b
  isometricApply up = amap (isometric up)
  
  -- | Make an isometric affine map with the given up direction.
  isometric :: (Floating n, Epsilon n) => Direction V3 n -> AffineMap V3 V2 n
  isometric up = m44AffineMap m
    where
      m = lookAt (V3 1 1 1) zero (fromDirection up)
  
  lookingAt :: (Epsilon n, Floating n)
            => P3 n -- ^ Eye
            -> P3 n -- ^ Center
            -> Direction V3 n -- ^ Up
            -> AffineMap V3 V2 n
  lookingAt (P cam) (P center) d = m44AffineMap m
    where
      m = lookAt cam center (d^._Dir)
  
  -- | Apply the affine part of a homogeneous matrix.
  m44AffineApply :: (InSpace V3 n a, InSpace V2 n b, AffineMappable a b)
                 => M44 n -> a -> b
  m44AffineApply = amap . m44AffineMap
  
  -- | Create an 'AffineMap' from a 4x4 homogeneous matrix, ignoring any
  --   perspective transforms.
  m44AffineMap :: Num n => M44 n -> AffineMap V3 V2 n
  m44AffineMap m = AffineMap (LinearMap f) (f v)
    where
      f  = view _xy . (m' !*)
      m' = m ^. linearTransform
      v  = m ^. L.translation
  
  -- | Apply a transformation matrix and translation.
  m33AffineApply :: (InSpace V3 n a, InSpace V2 n b, AffineMappable a b)
                 => M33 n -> V2 n -> a -> b
  m33AffineApply m = amap . m33AffineMap m
  
  -- | Create an 'AffineMap' from a 3x3 transformation matrix and a
  --   translation vector.
  m33AffineMap :: Num n => M33 n -> V2 n -> AffineMap V3 V2 n
  m33AffineMap m = AffineMap (LinearMap f)
    where
      f = view _xy . (m !*)
  
  -- | Extract the linear transform part of a homogeneous matrix.
  linearTransform :: (Representable u, R3 v, R3 u) => Lens' (u (v n)) (M33 n)
  linearTransform = column _xyz . _xyz
  
  ------------------------------------------------------------------------
  -- Perspective transforms
  ------------------------------------------------------------------------
  
  -- For the time being projective transforms use the deformable class.
  -- Eventually we would like to replace this with a more specialised
  -- method.
  
  -- $perspective
  -- Perspective projections are when closer objects appear bigger.
  
  -- | Make a deformation from a 4x4 homogeneous matrix.
  m44Deformation :: Fractional n => M44 n -> Deformation V3 V2 n
  m44Deformation m =
    Deformation (P . view _xy . normalizePoint . (m !*) . point . view _Point)
  
  