diagrams-core-0.6: src/Diagrams/Core/Transform.hs
{-# LANGUAGE TypeOperators
, FlexibleContexts
, FlexibleInstances
, UndecidableInstances
, TypeFamilies
, MultiParamTypeClasses
, GeneralizedNewtypeDeriving
, TypeSynonymInstances
#-}
-----------------------------------------------------------------------------
-- |
-- Module : Diagrams.Core.Transform
-- Copyright : (c) 2011 diagrams-core team (see LICENSE)
-- License : BSD-style (see LICENSE)
-- Maintainer : diagrams-discuss@googlegroups.com
--
-- "Diagrams" defines the core library of primitives
-- forming the basis of an embedded domain-specific language for
-- describing and rendering diagrams.
--
-- The @Transform@ module defines generic transformations
-- parameterized by any vector space.
--
-----------------------------------------------------------------------------
module Diagrams.Core.Transform
(
-- * Transformations
-- ** Invertible linear transformations
(:-:)(..), (<->), linv, lapp
-- ** General transformations
, Transformation(..)
, inv, transp, transl
, apply
, papply
, fromLinear
-- * The Transformable class
, HasLinearMap
, Transformable(..)
-- * Translational invariance
, TransInv(..)
-- * Vector space independent transformations
-- | Most transformations are specific to a particular vector
-- space, but a few can be defined generically over any
-- vector space.
, translation, translate
, scaling, scale
) where
import qualified Data.Map as M
import Data.Semigroup
import qualified Data.Set as S
import Data.AdditiveGroup
import Data.AffineSpace ((.-.))
import Data.Basis
import Data.LinearMap
import Data.MemoTrie
import Data.Monoid.Action
import Data.Monoid.Deletable
import Data.VectorSpace
import Diagrams.Core.HasOrigin
import Diagrams.Core.Points
import Diagrams.Core.V
------------------------------------------------------------
-- Transformations ---------------------------------------
------------------------------------------------------------
-------------------------------------------------------
-- Invertible linear transformations ----------------
-------------------------------------------------------
-- | @(v1 :-: v2)@ is a linear map paired with its inverse.
data (:-:) u v = (u :-* v) :-: (v :-* u)
infixr 7 :-:
-- | Create an invertible linear map from two functions which are
-- assumed to be linear inverses.
(<->) :: (HasLinearMap u, HasLinearMap v) => (u -> v) -> (v -> u) -> (u :-: v)
f <-> g = linear f :-: linear g
instance HasLinearMap v => Semigroup (v :-: v) where
(f :-: f') <> (g :-: g') = f *.* g :-: g' *.* f'
-- | Invertible linear maps from a vector space to itself form a
-- monoid under composition.
instance HasLinearMap v => Monoid (v :-: v) where
mempty = idL :-: idL
mappend = (<>)
-- | Invert a linear map.
linv :: (u :-: v) -> (v :-: u)
linv (f :-: g) = g :-: f
-- | Apply a linear map to a vector.
lapp :: (VectorSpace v, Scalar u ~ Scalar v, HasLinearMap u) => (u :-: v) -> u -> v
lapp (f :-: _) = lapply f
--------------------------------------------------
-- Affine transformations ----------------------
--------------------------------------------------
-- | General (affine) transformations, represented by an invertible
-- linear map, its /transpose/, and a vector representing a
-- translation component.
--
-- By the /transpose/ of a linear map we mean simply the linear map
-- corresponding to the transpose of the map's matrix
-- representation. For example, any scale is its own transpose,
-- since scales are represented by matrices with zeros everywhere
-- except the diagonal. The transpose of a rotation is the same as
-- its inverse.
--
-- The reason we need to keep track of transposes is because it
-- turns out that when transforming a shape according to some linear
-- map L, the shape's /normal vectors/ transform according to L's
-- inverse transpose. This is exactly what we need when
-- transforming bounding functions, which are defined in terms of
-- /perpendicular/ (i.e. normal) hyperplanes.
data Transformation v = Transformation (v :-: v) (v :-: v) v
type instance V (Transformation v) = v
-- | Invert a transformation.
inv :: HasLinearMap v => Transformation v -> Transformation v
inv (Transformation t t' v) = Transformation (linv t) (linv t')
(negateV (lapp (linv t) v))
-- | Get the transpose of a transformation (ignoring the translation
-- component).
transp :: Transformation v -> (v :-: v)
transp (Transformation _ t' _) = t'
-- | Get the translational component of a transformation.
transl :: Transformation v -> v
transl (Transformation _ _ v) = v
-- | Transformations are closed under composition; @t1 <> t2@ is the
-- transformation which performs first @t2@, then @t1@.
instance HasLinearMap v => Semigroup (Transformation v) where
Transformation t1 t1' v1 <> Transformation t2 t2' v2
= Transformation (t1 <> t2) (t2' <> t1') (v1 ^+^ lapp t1 v2)
instance HasLinearMap v => Monoid (Transformation v) where
mempty = Transformation mempty mempty zeroV
mappend = (<>)
-- | Transformations can act on transformable things.
instance (HasLinearMap v, v ~ (V a), Transformable a)
=> Action (Transformation v) a where
act = transform
-- | Apply a transformation to a vector. Note that any translational
-- component of the transformation will not affect the vector, since
-- vectors are invariant under translation.
apply :: HasLinearMap v => Transformation v -> v -> v
apply (Transformation t _ _) = lapp t
-- | Apply a transformation to a point.
papply :: HasLinearMap v => Transformation v -> Point v -> Point v
papply (Transformation t _ v) (P p) = P $ lapp t p ^+^ v
-- | Create a general affine transformation from an invertible linear
-- transformation and its transpose. The translational component is
-- assumed to be zero.
fromLinear :: AdditiveGroup v => (v :-: v) -> (v :-: v) -> Transformation v
fromLinear l1 l2 = Transformation l1 l2 zeroV
------------------------------------------------------------
-- The Transformable class -------------------------------
------------------------------------------------------------
-- | 'HasLinearMap' is a poor man's class constraint synonym, just to
-- help shorten some of the ridiculously long constraint sets.
class (HasBasis v, HasTrie (Basis v), VectorSpace v) => HasLinearMap v
instance (HasBasis v, HasTrie (Basis v), VectorSpace v) => HasLinearMap v
-- | Type class for things @t@ which can be transformed.
class HasLinearMap (V t) => Transformable t where
-- | Apply a transformation to an object.
transform :: Transformation (V t) -> t -> t
instance HasLinearMap v => Transformable (Transformation v) where
transform t1 t2 = t1 <> t2
instance HasLinearMap v => HasOrigin (Transformation v) where
moveOriginTo p = translate (origin .-. p)
instance (Transformable a, Transformable b, V a ~ V b)
=> Transformable (a,b) where
transform t (x,y) = ( transform t x
, transform t y
)
instance (Transformable a, Transformable b, Transformable c, V a ~ V b, V a ~ V c)
=> Transformable (a,b,c) where
transform t (x,y,z) = ( transform t x
, transform t y
, transform t z
)
instance Transformable t => Transformable [t] where
transform = map . transform
instance (Transformable t, Ord t) => Transformable (S.Set t) where
transform = S.map . transform
instance Transformable t => Transformable (M.Map k t) where
transform = M.map . transform
instance HasLinearMap v => Transformable (Point v) where
transform = papply
instance Transformable m => Transformable (Deletable m) where
transform = fmap . transform
instance Transformable Double where
transform = apply
instance Transformable Rational where
transform = apply
------------------------------------------------------------
-- Translational invariance ------------------------------
------------------------------------------------------------
-- | @TransInv@ is a wrapper which makes a transformable type
-- translationally invariant; the translational component of
-- transformations will no longer affect things wrapped in
-- @TransInv@.
newtype TransInv t = TransInv { unTransInv :: t }
deriving (Show, Semigroup, Monoid)
type instance V (TransInv t) = V t
instance VectorSpace (V t) => HasOrigin (TransInv t) where
moveOriginTo = const id
instance Transformable t => Transformable (TransInv t) where
transform tr (TransInv t) = TransInv (translate (negateV (transl tr)) . transform tr $ t)
------------------------------------------------------------
-- Generic transformations -------------------------------
------------------------------------------------------------
-- | Create a translation.
translation :: HasLinearMap v => v -> Transformation v
translation = Transformation mempty mempty
-- | Translate by a vector.
translate :: (Transformable t, HasLinearMap (V t)) => V t -> t -> t
translate = transform . translation
-- | Create a uniform scaling transformation.
scaling :: (HasLinearMap v, Fractional (Scalar v))
=> Scalar v -> Transformation v
scaling s = fromLinear lin lin -- scaling is its own transpose
where lin = (s *^) <-> (^/ s)
-- | Scale uniformly in every dimension by the given scalar.
scale :: (Transformable t, Fractional (Scalar (V t)), Eq (Scalar (V t)))
=> Scalar (V t) -> t -> t
scale 0 = error "scale by zero! Halp!" -- XXX what should be done here?
scale s = transform $ scaling s