{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Diagrams.Transform.ScaleInv
( ScaleInv(..)
, scaleInvObj, scaleInvDir, scaleInvLoc
, scaleInv, scaleInvPrim)
where
import Control.Lens (makeLenses, view, (^.))
#if __GLASGOW_HASKELL__ < 710
import Data.Semigroup
#endif
import Data.Typeable
import Diagrams.Angle
import Diagrams.Core
import Diagrams.TwoD.Transform
import Diagrams.TwoD.Types
import Linear.Affine
import Linear.Vector
data ScaleInv t =
ScaleInv
{ _scaleInvObj :: t
, _scaleInvDir :: Vn t
, _scaleInvLoc :: Point (V t) (N t)
}
deriving Typeable
deriving instance (Show t, Show (Vn t)) => Show (ScaleInv t)
makeLenses ''ScaleInv
-- | Create a scale-invariant object pointing in the given direction,
-- located at the origin.
scaleInv :: (V t ~ v, N t ~ n, Additive v, Num n) => t -> v n -> ScaleInv t
scaleInv t d = ScaleInv t d origin
type instance V (ScaleInv t) = V t
type instance N (ScaleInv t) = N t
instance (V t ~ v, N t ~ n, Additive v, Num n, HasOrigin t) => HasOrigin (ScaleInv t) where
moveOriginTo p (ScaleInv t v l) = ScaleInv (moveOriginTo p t) v (moveOriginTo p l)
instance (V t ~ V2, N t ~ n, RealFloat n, Transformable t) => Transformable (ScaleInv t) where
transform tr (ScaleInv t v l) = ScaleInv (trans . rot $ t) (rot v) l'
where
angle = transform tr v ^. _theta
rot :: (V k ~ V t, N k ~ N t, Transformable k) => k -> k
rot = rotateAround l angle
l' = transform tr l
trans = translate (l' .-. l)
{- Proof that the above satisfies the monoid action laws.
1. transform mempty (ScaleInv t v l)
= ScaleInv (trans . rot $ t) (rot v) l'
{ l' = transform mempty l = l }
{ trans = translate (l' .-. l)
= translate (l .-. l)
= translate zeroV
= id
}
{ rot = rotateAround l angle
= rotateAround l (direction (transform mempty v) - direction v)
= rotateAround l (direction v - direction v)
= rotateAround l 0
= id
}
= ScaleInv t v l
2. transform t1 (transform t2 (ScaleInv t v l))
= let angle = direction (transform t2 v) - direction v
rot = rotateAround l angle
l' = transform t2 l
trans = translate (l' .-. l)
in
transform t1 (ScaleInv (trans . rot $ t) (rot v) l')
= let angle = direction (transform t2 v) - direction v
rot = rotateAround l angle
l' = transform t2 l
trans = translate (l' .-. l)
angle2 = direction (transform t1 (rot v)) - direction (rot v)
rot2 = rotateAround l' angle2
l'2 = transform t1 l'
trans2 = translate (l'2 .-. l')
in
ScaleInv (trans2 . rot2 . trans . rot $ t) (rot2 . rot $ v) l'2
{ l'2 = transform t1 l'
= transform t1 (transform t2 l)
= transform (t1 <> t2) l
}
{ trans2 = translate (l'2 .-. l')
= translate (transform (t1 <> t2) l .-. transform t2 l)
= translate (transform t1 l .-. l)
}
{ rot v = rotateAround l angle v
= rotate angle `under` translation (origin .-. l) $ v
= rotate angle v
}
{ angle2 = direction (transform t1 (rot v)) - direction (rot v)
= direction (transform t1 (rotate angle v)) - direction (rotate angle v)
= direction (transform t1 (rotate angle v)) - direction v - angle
}
{ rot2 = rotateAround l' angle2
= ???
}
-}
instance (V t ~ V2, N t ~ n, RealFloat n, Renderable t b) => Renderable (ScaleInv t) b where
render b = render b . view scaleInvObj
-- | Create a diagram from a single scale-invariant primitive. The
-- vector argument specifies the direction in which the primitive is
-- \"pointing\" (for the purpose of keeping it rotated correctly
-- under non-uniform scaling). The primitive is assumed to be
-- \"located\" at the origin (for the purpose of translating it
-- correctly under scaling).
--
-- Note that the resulting diagram will have an /empty/ envelope,
-- trace, and query. The reason is that the envelope, trace, and
-- query cannot be cached---applying a transformation would cause
-- the cached envelope, etc. to get \"out of sync\" with the
-- scale-invariant object. The intention, at any rate, is that
-- scale-invariant things will be used only as \"decorations\" (/e.g./
-- arrowheads) which should not affect the envelope, trace, and
-- query.
scaleInvPrim :: (V t ~ V2, N t ~ n, RealFloat n, Typeable t, Renderable t b, Monoid m)
=> t -> V2 n -> QDiagram b (V t) (N t) m
scaleInvPrim t d = mkQD (Prim $ scaleInv t d) mempty mempty mempty mempty