{-# LANGUAGE BangPatterns #-}
{-|
  2D transformation matrices capable of translating, scaling,
  rotating, and skewing.
-}
module Reanimate.Transform
  ( identity
  , transformPoint
  , mkMatrix
  , toTransformation
  ) where

-- XXX: Use Linear.Matrix instead of Data.Matrix to drop the 'matrix' dependency.
import           Data.List        (foldl')
import           Data.Matrix      (Matrix)
import qualified Data.Matrix      as M
import           Data.Maybe       (fromMaybe)
import           Graphics.SvgTree (Coord, RPoint, Transformation (..))
import           Linear.V2        (V2 (V2))

-- | Identity matrix.
--
--   @transformPoints identity x = x@
identity :: Matrix Coord
identity :: Matrix Coord
identity = Int -> Matrix Coord
forall a. Num a => Int -> Matrix a
M.identity Int
3

fromList :: [Coord] -> Matrix Coord
fromList :: [Coord] -> Matrix Coord
fromList [Coord
a,Coord
b,Coord
c,Coord
d,Coord
e,Coord
f] = Int -> Int -> [Coord] -> Matrix Coord
forall a. Int -> Int -> [a] -> Matrix a
M.fromList Int
3 Int
3 [Coord
a,Coord
c,Coord
e,Coord
b,Coord
d,Coord
f,Coord
0,Coord
0,Coord
1]
fromList [Coord]
_             = [Char] -> Matrix Coord
forall a. HasCallStack => [Char] -> a
error [Char]
"Reanimate.Transform.fromList: bad input"

-- | Apply a transformation matrix to a 2D point.
transformPoint :: Matrix Coord -> RPoint -> RPoint
transformPoint :: Matrix Coord -> RPoint -> RPoint
transformPoint Matrix Coord
m (V2 Coord
x Coord
y) = Coord -> Coord -> RPoint
forall a. a -> a -> V2 a
V2 (Coord
aCoord -> Coord -> Coord
forall a. Num a => a -> a -> a
*Coord
x Coord -> Coord -> Coord
forall a. Num a => a -> a -> a
+Coord
cCoord -> Coord -> Coord
forall a. Num a => a -> a -> a
*Coord
y Coord -> Coord -> Coord
forall a. Num a => a -> a -> a
+ Coord
e) (Coord
bCoord -> Coord -> Coord
forall a. Num a => a -> a -> a
*Coord
x Coord -> Coord -> Coord
forall a. Num a => a -> a -> a
+ Coord
dCoord -> Coord -> Coord
forall a. Num a => a -> a -> a
*Coord
y Coord -> Coord -> Coord
forall a. Num a => a -> a -> a
+Coord
f)
  where
    !a :: Coord
a = Int -> Int -> Matrix Coord -> Coord
forall a. Int -> Int -> Matrix a -> a
M.unsafeGet Int
1 Int
1 Matrix Coord
m
    !c :: Coord
c = Int -> Int -> Matrix Coord -> Coord
forall a. Int -> Int -> Matrix a -> a
M.unsafeGet Int
1 Int
2 Matrix Coord
m
    !e :: Coord
e = Int -> Int -> Matrix Coord -> Coord
forall a. Int -> Int -> Matrix a -> a
M.unsafeGet Int
1 Int
3 Matrix Coord
m
    !b :: Coord
b = Int -> Int -> Matrix Coord -> Coord
forall a. Int -> Int -> Matrix a -> a
M.unsafeGet Int
2 Int
1 Matrix Coord
m
    !d :: Coord
d = Int -> Int -> Matrix Coord -> Coord
forall a. Int -> Int -> Matrix a -> a
M.unsafeGet Int
2 Int
2 Matrix Coord
m
    !f :: Coord
f = Int -> Int -> Matrix Coord -> Coord
forall a. Int -> Int -> Matrix a -> a
M.unsafeGet Int
2 Int
3 Matrix Coord
m
    -- (a:c:e:b:d:f:_) = M.toList m

-- | Convert multiple SVG transformations into a single transformation matrix.
mkMatrix :: Maybe [Transformation] -> Matrix Coord
mkMatrix :: Maybe [Transformation] -> Matrix Coord
mkMatrix Maybe [Transformation]
Nothing   = Matrix Coord
identity
mkMatrix (Just [Transformation]
ts) = (Matrix Coord -> Matrix Coord -> Matrix Coord)
-> Matrix Coord -> [Matrix Coord] -> Matrix Coord
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Matrix Coord -> Matrix Coord -> Matrix Coord
forall a. Num a => a -> a -> a
(*) Matrix Coord
identity ((Transformation -> Matrix Coord)
-> [Transformation] -> [Matrix Coord]
forall a b. (a -> b) -> [a] -> [b]
map Transformation -> Matrix Coord
transformationMatrix [Transformation]
ts)

-- | Convert an SVG transformation into a transformation matrix.
transformationMatrix :: Transformation -> Matrix Coord
transformationMatrix :: Transformation -> Matrix Coord
transformationMatrix Transformation
transformation =
  case Transformation
transformation of
    TransformMatrix Coord
a Coord
b Coord
c Coord
d Coord
e Coord
f -> [Coord] -> Matrix Coord
fromList [Coord
a,Coord
b,Coord
c,Coord
d,Coord
e,Coord
f]
    Translate Coord
x Coord
y               -> Coord -> Coord -> Matrix Coord
translate Coord
x Coord
y
    Scale Coord
sx Maybe Coord
mbSy               -> [Coord] -> Matrix Coord
fromList [Coord
sx,Coord
0,Coord
0,Coord -> Maybe Coord -> Coord
forall a. a -> Maybe a -> a
fromMaybe Coord
sx Maybe Coord
mbSy,Coord
0,Coord
0]
    Rotate Coord
a Maybe (Coord, Coord)
Nothing            -> Coord -> Matrix Coord
rotate Coord
a
    Rotate Coord
a (Just (Coord
x,Coord
y))       -> Coord -> Coord -> Matrix Coord
translate Coord
x Coord
y Matrix Coord -> Matrix Coord -> Matrix Coord
forall a. Num a => a -> a -> a
* Coord -> Matrix Coord
rotate Coord
a Matrix Coord -> Matrix Coord -> Matrix Coord
forall a. Num a => a -> a -> a
* Coord -> Coord -> Matrix Coord
translate (-Coord
x) (-Coord
y)
    SkewX Coord
a                     -> [Coord] -> Matrix Coord
fromList [Coord
1,Coord
0,Coord -> Coord
forall a. Floating a => a -> a
tan (Coord
aCoord -> Coord -> Coord
forall a. Num a => a -> a -> a
*Coord
forall a. Floating a => a
piCoord -> Coord -> Coord
forall a. Fractional a => a -> a -> a
/Coord
180),Coord
1,Coord
0,Coord
0]
    SkewY Coord
a                     -> [Coord] -> Matrix Coord
fromList [Coord
1,Coord -> Coord
forall a. Floating a => a -> a
tan (Coord
aCoord -> Coord -> Coord
forall a. Num a => a -> a -> a
*Coord
forall a. Floating a => a
piCoord -> Coord -> Coord
forall a. Fractional a => a -> a -> a
/Coord
180),Coord
0,Coord
1,Coord
0,Coord
0]
    Transformation
TransformUnknown            -> Matrix Coord
identity
  where
    translate :: Coord -> Coord -> Matrix Coord
translate Coord
x Coord
y = [Coord] -> Matrix Coord
fromList [Coord
1,Coord
0,Coord
0,Coord
1,Coord
x,Coord
y]
    rotate :: Coord -> Matrix Coord
rotate Coord
a = [Coord] -> Matrix Coord
fromList [Coord -> Coord
forall a. Floating a => a -> a
cos Coord
r,Coord -> Coord
forall a. Floating a => a -> a
sin Coord
r,-Coord -> Coord
forall a. Floating a => a -> a
sin Coord
r,Coord -> Coord
forall a. Floating a => a -> a
cos Coord
r,Coord
0,Coord
0]
      where r :: Coord
r = Coord
a Coord -> Coord -> Coord
forall a. Num a => a -> a -> a
* Coord
forall a. Floating a => a
pi Coord -> Coord -> Coord
forall a. Fractional a => a -> a -> a
/ Coord
180

-- | Convert a transformation matrix back into an SVG transformation.
toTransformation :: Matrix Coord -> Transformation
toTransformation :: Matrix Coord -> Transformation
toTransformation Matrix Coord
m = Coord
-> Coord -> Coord -> Coord -> Coord -> Coord -> Transformation
TransformMatrix Coord
a Coord
b Coord
c Coord
d Coord
e Coord
f
  where
    [Coord
a,Coord
c,Coord
e,Coord
b,Coord
d,Coord
f,Coord
_,Coord
_,Coord
_] = Matrix Coord -> [Coord]
forall a. Matrix a -> [a]
M.toList Matrix Coord
m