{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-} {-# OPTIONS -Wall #-} {-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------ -- | -- Module : Wumpus.Core.AffineTrans -- Copyright : (c) Stephen Tetley 2009-2011 -- License : BSD3 -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable -- Portability : GHC -- -- Affine transformations. -- -- The common affine transformations represented as type classes - -- scaling, rotation, translation. -- -- Internally, when a Picture is composed and transformed, Wumpus -- only transforms the bounding box - transformations of the -- picture content (paths or text labels) are communicated to -- PostScript or SVG for final rendering. This is because Wumpus -- has no access to the paths that make fonts so cannot transform -- them directly. -- -- Other elements - Vectors, Points, BoundingBoxes and Primtives - -- are also instances of the affine classes. However, generally -- Wumpus transforms these elements directly rather than -- delegating the transformation to PostScript or SVG (the -- situation for the Label primitive is more complicated - the -- /start/ point is transformed by Wumpus but a matrix -- transformation is sent to PostScript to manipulate the opaque -- character objects). -- -- Note - transformations on Primitives are applied to the control -- points of the primitive not the /drawing/. A scaled, stroked -- path will be drawn with at the standard line width rather than -- with a thicker line. Also, text may not render pleasantly after -- it has been transformed, PostScript references seem to caution -- against transforming text and recommend changing @/scalefont@ -- instead of scaling via a transfomation. -- -- To generate efficient PostScript, Wumpus relies on the matrix -- representations of the affine transformations being invertible. -- Do not scale elements by zero! -- -- -- Design note - the formulation of the affine classes is not -- ideal as dealing with units is avoided and the instances for -- Point2 and Vec2 are only applicable to @DPoint2@ and @DVec2@. -- Dealing with units is avoided as some useful units -- (particulary Em and En) have contextual interterpretations - -- i.e. their size is dependent on the current font size - and so -- they cannot be accommodated without some monadic context. -- -- For this reason, the naming scheme for the affine classes was -- changed at revision 0.50.0 to the current \"d\"-prefixed names. -- This allows higher-level frameworks to define their own -- functions or class-methods using the obvious good names -- (@rotate@, @scale@ etc.). The derived operations (@rotate30@, -- @uniformScale, etc.) have been removed as a higher-level -- implementation is expected to re-implement them accounting for -- polymorphic units as necessary. -- -------------------------------------------------------------------------------- module Wumpus.Core.AffineTrans ( -- * Type classes Transform(..) , Rotate(..) , RotateAbout(..) , Scale(..) , Translate(..) -- * Common rotations , rotate30 , rotate30About , rotate45 , rotate45About , rotate60 , rotate60About , rotate90 , rotate90About , rotate120 , rotate120About -- * Common scalings , uniformScale , reflectX , reflectY -- * Translate by a vector , translateBy -- * Reflections in supplied plane rather than about the origin , reflectXPlane , reflectYPlane ) where import Wumpus.Core.Geometry -------------------------------------------------------------------------------- -- Affine transformations -- -- Design Note -- -- Perhaps the Transform class is not generally useful in the -- presence of units. -- -- | Apply a matrix transformation directly. -- class Transform t where transform :: u ~ DUnit t => Matrix3'3 u -> t -> t instance Transform a => Transform (Maybe a) where transform = fmap . transform instance (u ~ DUnit a, u ~ DUnit b, Transform a, Transform b) => Transform (a,b) where transform mtrx (a,b) = (transform mtrx a, transform mtrx b) instance Num u => Transform (Point2 u) where transform ctm = (ctm *#) instance Num u => Transform (Vec2 u) where transform ctm = (ctm *#) -------------------------------------------------------------------------------- -- | Type class for rotation. -- class Rotate t where rotate :: Radian -> t -> t instance Rotate a => Rotate (Maybe a) where rotate = fmap . rotate instance (Rotate a, Rotate b) => Rotate (a,b) where rotate ang (a,b) = (rotate ang a, rotate ang b) instance (Real u, Floating u) => Rotate (Point2 u) where rotate ang pt = P2 x y where v = pvec zeroPt pt (V2 x y) = avec (ang + vdirection v) $ vlength v instance (Real u, Floating u) => Rotate (Vec2 u) where rotate ang v = avec (ang + vdirection v) $ vlength v -- -- -- | Type class for rotation about a point. -- -- Note - the point is a @DPoint2@ - i.e. it has PostScript points -- for x and y-units. -- class RotateAbout t where rotateAbout :: u ~ DUnit t => Radian -> Point2 u -> t -> t -- -- Note - it seems GHC 7.0.2 at least, would let us define a -- RotateAbout instance for @()@, even though it has no valid -- DUnit instance. -- -- Still it seems safer to define a nil type with a phantom unit: -- -- > data UNil u = UNil -- -- This data type is provided by Wumpus-Basic. -- instance RotateAbout a => RotateAbout (Maybe a) where rotateAbout ang pt = fmap (rotateAbout ang pt) instance (u ~ DUnit a, u ~ DUnit b, RotateAbout a, RotateAbout b) => RotateAbout (a,b) where rotateAbout ang pt (a,b) = (rotateAbout ang pt a, rotateAbout ang pt b) instance (Real u, Floating u) => RotateAbout (Point2 u) where rotateAbout ang (P2 ox oy) = translate ox oy . rotate ang . translate (-ox) (-oy) instance (Real u, Floating u) => RotateAbout (Vec2 u) where rotateAbout ang (P2 ox oy) = translate ox oy . rotate ang . translate (-ox) (-oy) -------------------------------------------------------------------------------- -- Scale -- | Type class for scaling. -- class Scale t where scale :: Double -> Double -> t -> t instance Scale a => Scale (Maybe a) where scale sx sy = fmap (scale sx sy) instance (Scale a, Scale b) => Scale (a,b) where scale sx sy (a,b) = (scale sx sy a, scale sx sy b) instance Fractional u => Scale (Point2 u) where scale sx sy (P2 x y) = P2 (x * realToFrac sx) (y * realToFrac sy) instance Fractional u => Scale (Vec2 u) where scale sx sy (V2 x y) = V2 (x * realToFrac sx) (y * realToFrac sy) -------------------------------------------------------------------------------- -- Translate -- | Type class for translation. -- class Translate t where translate :: u ~ DUnit t => u -> u -> t -> t instance Translate a => Translate (Maybe a) where translate dx dy = fmap (translate dx dy) instance (u ~ DUnit a, u ~ DUnit b, Translate a, Translate b) => Translate (a,b) where translate dx dy (a,b) = (translate dx dy a, translate dx dy b) instance Num u => Translate (Point2 u) where translate dx dy (P2 x y) = P2 (x + dx) (y + dy) -- | Vectors do not respond to translation. -- instance Translate (Vec2 u) where translate _ _ v0 = v0 -------------------------------------------------------------------------------- -- Common rotations -- | Rotate by 30 degrees about the origin. -- rotate30 :: Rotate t => t -> t rotate30 = rotate (pi/6) -- | Rotate by 30 degrees about the supplied point. -- rotate30About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate30About = rotateAbout (pi/6) -- | Rotate by 45 degrees about the origin. -- rotate45 :: Rotate t => t -> t rotate45 = rotate (pi/4) -- | Rotate by 45 degrees about the supplied point. -- rotate45About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate45About = rotateAbout (pi/4) -- | Rotate by 60 degrees about the origin. -- rotate60 :: Rotate t => t -> t rotate60 = rotate (2*pi/3) -- | Rotate by 60 degrees about the supplied point. -- rotate60About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate60About = rotateAbout (2*pi/3) -- | Rotate by 90 degrees about the origin. -- rotate90 :: Rotate t => t -> t rotate90 = rotate (pi/2) -- | Rotate by 90 degrees about the supplied point. -- rotate90About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate90About = rotateAbout (pi/2) -- | Rotate by 120 degrees about the origin. -- rotate120 :: Rotate t => t -> t rotate120 = rotate (4*pi/3) -- | Rotate by 120 degrees about the supplied point. -- rotate120About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate120About = rotateAbout (4*pi/3) -------------------------------------------------------------------------------- -- Common scalings -- | Scale both x and y dimensions by the same amount. -- uniformScale :: Scale t => Double -> t -> t uniformScale a = scale a a -- | Reflect in the X-plane about the origin. -- reflectX :: Scale t => t -> t reflectX = scale (-1) 1 -- | Reflect in the Y-plane about the origin. -- reflectY :: Scale t => t -> t reflectY = scale 1 (-1) -------------------------------------------------------------------------------- -- Translations -- | Translate by the x and y components of a vector. -- translateBy :: (Translate t, DUnit t ~ u) => Vec2 u -> t -> t translateBy (V2 x y) = translate x y -------------------------------------------------------------------------------- -- Translation and scaling -- | Reflect in the X plane that intersects the supplied point. -- reflectXPlane :: (Num u, Scale t, Translate t, u ~ DUnit t) => Point2 u -> t -> t reflectXPlane (P2 x y) = translate x y . scale (-1) 1 . translate (-x) (-y) -- | Reflect in the Y plane that intersects the supplied point. -- reflectYPlane :: (Num u, Scale t, Translate t, u ~ DUnit t) => Point2 u -> t -> t reflectYPlane (P2 x y) = translate x y . scale 1 (-1) . translate (-x) (-y)