{-# LANGUAGE TypeFamilies #-} {-# OPTIONS -Wall #-} -------------------------------------------------------------------------------- -- | -- Module : Wumpus.Core.TrafoInternal -- Copyright : (c) Stephen Tetley 2010-2011 -- License : BSD3 -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable -- Portability : GHC -- -- Transformations on (Path) Primtives in Wumpus-Core are -- performed on the control points rather than transmitted to -- PostScript. -- -- However because text labels are opaque to Wumpus, the corner -- start point is manipulated in Wumpus, but transformations on -- the actual text are communicated to PostScript as matrix -- transformations. -- -------------------------------------------------------------------------------- module Wumpus.Core.TrafoInternal ( -- * Types PrimCTM(..) , AffineTrafo(..) -- * CTM operations , identityCTM , makeThetaCTM , makeTranslCTM , startPointCTM , translateCTM , scaleCTM , rotateCTM , rotateAboutCTM , matrixRepCTM , unCTM -- * AffineTrafo operations , concatTrafos , matrixRepr ) where import Wumpus.Core.AffineTrans import Wumpus.Core.Geometry import Wumpus.Core.Utils.Common ( dtruncFmt ) import Wumpus.Core.Utils.FormatCombinators -- Note - PrimCTM can be specialized to Double. -- Primitives support affine transformations. -- -- The control points of a path, baseline-left of text or center -- of ellipse are transformed as points; scaling and rotation -- or text and ellipses are transmitted to PostScript and SVG. -- -- Note - line thickness of a stroked path will not be scaled. -- data PrimCTM = PrimCTM { ctm_trans_x :: !Double , ctm_trans_y :: !Double , ctm_scale_x :: !Double , ctm_scale_y :: !Double , ctm_rotation :: !Radian } deriving (Eq,Show) type instance DUnit PrimCTM = Double -- | For Pictures - Affine transformations are represented as -- /syntax/ so they can be manipulated easily. -- data AffineTrafo = Matrix (Matrix3'3 Double) | Rotate Radian | RotAbout Radian (Point2 Double) | Scale Double Double | Translate Double Double deriving (Eq,Show) type instance DUnit AffineTrafo = Double -------------------------------------------------------------------------------- -- instances instance Format PrimCTM where format (PrimCTM dx dy sx sy ang) = parens (text "CTM" <+> text "dx =" <> dtruncFmt dx <+> text "dy =" <> dtruncFmt dy <+> text "sx =" <> dtruncFmt sx <+> text "sy =" <> dtruncFmt sy <+> text "ang=" <> format ang ) -------------------------------------------------------------------------------- -- Manipulating the PrimCTM identityCTM :: PrimCTM identityCTM = PrimCTM { ctm_trans_x = 0 , ctm_trans_y = 0 , ctm_scale_x = 1 , ctm_scale_y = 1 , ctm_rotation = 0 } makeThetaCTM :: Double -> Double -> Radian -> PrimCTM makeThetaCTM dx dy ang = PrimCTM { ctm_trans_x = dx , ctm_trans_y = dy , ctm_scale_x = 1 , ctm_scale_y = 1 , ctm_rotation = ang } makeTranslCTM :: Double -> Double -> PrimCTM makeTranslCTM dx dy = PrimCTM { ctm_trans_x = dx , ctm_trans_y = dy , ctm_scale_x = 1 , ctm_scale_y = 1 , ctm_rotation = 0 } startPointCTM :: DPoint2 -> PrimCTM startPointCTM (P2 x y) = PrimCTM { ctm_trans_x = x , ctm_trans_y = y , ctm_scale_x = 1 , ctm_scale_y = 1 , ctm_rotation = 0 } translateCTM :: Double -> Double -> PrimCTM -> PrimCTM translateCTM x1 y1 (PrimCTM dx dy sx sy ang) = PrimCTM (x1+dx) (y1+dy) sx sy ang -- Note - the matrix is not used entirely conventionally. -- -- It is expected that the point is extracted from the matrix, so -- scales and rotations operate on the point coordinates as well -- as the scale and rotation components. -- -- | Scale the CTM. -- scaleCTM :: Double -> Double -> PrimCTM -> PrimCTM scaleCTM x1 y1 (PrimCTM dx dy sx sy ang) = let P2 x y = scale x1 y1 (P2 dx dy) in PrimCTM x y (x1*sx) (y1*sy) ang -- | Rotate the CTM. -- rotateCTM :: Radian -> PrimCTM -> PrimCTM rotateCTM theta (PrimCTM dx dy sx sy ang) = let P2 x y = rotate theta (P2 dx dy) in PrimCTM x y sx sy (circularModulo $ theta+ang) -- | RotateAbout the CTM. -- rotateAboutCTM :: Radian -> DPoint2 -> PrimCTM -> PrimCTM rotateAboutCTM theta pt (PrimCTM dx dy sx sy ang) = let P2 x y = rotateAbout theta pt (P2 dx dy) in PrimCTM x y sx sy (circularModulo $ theta+ang) -- Note - the order of combining a translation (i.e. the location -- of a point) and the CTM is crucial as matrix multiplication is -- not commutative. -- -- This function encapsulates the correct order (or does it? - -- some of the demos are not working properly...). -- matrixRepCTM :: PrimCTM -> Matrix3'3 Double matrixRepCTM (PrimCTM dx dy sx sy ang) = translationMatrix dx dy * rotationMatrix (circularModulo ang) * scalingMatrix sx sy -- | Destructor for the CTM extracts the /start point/ and a -- /residual/ CTM. -- -- If the residual CTM is the identity CTM, the SVG or PostScript -- output can be optimized. -- unCTM :: PrimCTM -> (DPoint2, PrimCTM) unCTM (PrimCTM dx dy sx sy ang) = (P2 dx dy, PrimCTM 0 0 sx sy ang) -------------------------------------------------------------------------------- -- AffineTrafo operations concatTrafos :: [AffineTrafo] -> Matrix3'3 Double concatTrafos = foldr (\e ac -> matrixRepr e * ac) identityMatrix matrixRepr :: AffineTrafo -> Matrix3'3 Double matrixRepr (Matrix mtrx) = mtrx matrixRepr (Rotate theta) = rotationMatrix theta matrixRepr (RotAbout theta pt) = originatedRotationMatrix theta pt matrixRepr (Scale sx sy) = scalingMatrix sx sy matrixRepr (Translate dx dy) = translationMatrix dx dy