wumpus-core-0.50.0: src/Wumpus/Core/AffineTrans.hs
{-# 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)