-
Notifications
You must be signed in to change notification settings - Fork 43
/
Transform.hs
425 lines (338 loc) · 15 KB
/
Transform.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-unused-imports #-}
-- |
-- Module : Diagrams.Core.Transform
-- Copyright : (c) 2011-2015 diagrams-core team (see LICENSE)
-- License : BSD-style (see LICENSE)
-- Maintainer : diagrams-discuss@googlegroups.com
--
-- "Diagrams" defines the core library of primitives
-- forming the basis of an embedded domain-specific language for
-- describing and rendering diagrams.
--
-- The @Transform@ module defines generic transformations
-- parameterized by any vector space.
--
-----------------------------------------------------------------------------
module Diagrams.Core.Transform
(
-- * Transformations
-- ** Invertible linear transformations
(:-:)(..), (<->), linv, lapp
-- ** General transformations
, Transformation(..)
, inv, transp, transl
, dropTransl
, apply
, papply
, fromLinear
, fromOrthogonal
, fromSymmetric
, basis
, dimension
, onBasis
, listRep
, matrixRep
, matrixHomRep
, determinant
, isReflection
, avgScale
, eye
-- * The Transformable class
, HasLinearMap
, HasBasis
, Transformable(..)
-- * Translational invariance
, TransInv(TransInv)
-- * Vector space independent transformations
-- | Most transformations are specific to a particular vector
-- space, but a few can be defined generically over any
-- vector space.
, translation, translate
, scaling, scale
) where
import Control.Lens (Rewrapped, Traversable, Wrapped (..),
iso, (&), (.~))
import qualified Data.Map as M
import Data.Semigroup
import qualified Data.Set as S
import Data.Monoid.Action
import Data.Monoid.Deletable
import Linear.Affine
import Linear.Vector
import Data.Foldable (Foldable, toList)
import Data.Functor.Rep
import Diagrams.Core.HasOrigin
import Diagrams.Core.Measure
import Diagrams.Core.Points ()
import Diagrams.Core.V
------------------------------------------------------------
-- Transformations ---------------------------------------
------------------------------------------------------------
-------------------------------------------------------
-- Invertible linear transformations ----------------
-------------------------------------------------------
-- | @(v1 :-: v2)@ is a linear map paired with its inverse.
data (:-:) u v = (u -> v) :-: (v -> u)
infixr 7 :-:
-- | Create an invertible linear map from two functions which are
-- assumed to be linear inverses.
(<->) :: (u -> v) -> (v -> u) -> (u :-: v)
f <-> g = f :-: g
instance Semigroup (a :-: a) where
(f :-: f') <> (g :-: g') = f . g :-: g' . f'
-- | Invertible linear maps from a vector space to itself form a
-- monoid under composition.
instance Monoid (v :-: v) where
mempty = id :-: id
mappend = (<>)
-- | Invert a linear map.
linv :: (u :-: v) -> (v :-: u)
linv (f :-: g) = g :-: f
-- | Apply a linear map to a vector.
lapp :: (u :-: v) -> u -> v
lapp (f :-: _) = f
--------------------------------------------------
-- Affine transformations ----------------------
--------------------------------------------------
-- | General (affine) transformations, represented by an invertible
-- linear map, its /transpose/, and a vector representing a
-- translation component.
--
-- By the /transpose/ of a linear map we mean simply the linear map
-- corresponding to the transpose of the map's matrix
-- representation. For example, any scale is its own transpose,
-- since scales are represented by matrices with zeros everywhere
-- except the diagonal. The transpose of a rotation is the same as
-- its inverse.
--
-- The reason we need to keep track of transposes is because it
-- turns out that when transforming a shape according to some linear
-- map L, the shape's /normal vectors/ transform according to L's
-- inverse transpose. (For a more detailed explanation and proof,
-- see <https://wiki.haskell.org/Diagrams/Dev/Transformations>.)
-- This is exactly what we need when transforming bounding
-- functions, which are defined in terms of /perpendicular/
-- (i.e. normal) hyperplanes.
--
-- For more general, non-invertible transformations, see
-- @Diagrams.Deform@ (in @diagrams-lib@).
data Transformation v n = Transformation (v n :-: v n) (v n :-: v n) (v n)
type instance V (Transformation v n) = v
type instance N (Transformation v n) = n
-- | Identity matrix.
eye :: (HasBasis v, Num n) => v (v n)
eye = tabulate $ \(E e) -> zero & e .~ 1
-- | Invert a transformation.
inv :: (Functor v, Num n) => Transformation v n -> Transformation v n
inv (Transformation t t' v) = Transformation (linv t) (linv t')
(negated (lapp (linv t) v))
-- | Get the transpose of a transformation (ignoring the translation
-- component).
transp :: Transformation v n -> (v n :-: v n)
transp (Transformation _ t' _) = t'
-- | Get the translational component of a transformation.
transl :: Transformation v n -> v n
transl (Transformation _ _ v) = v
-- | Drop the translational component of a transformation, leaving only
-- the linear part.
dropTransl :: (Additive v, Num n) => Transformation v n -> Transformation v n
dropTransl (Transformation a a' _) = Transformation a a' zero
-- | Transformations are closed under composition; @t1 <> t2@ is the
-- transformation which performs first @t2@, then @t1@.
instance (Additive v, Num n) => Semigroup (Transformation v n) where
Transformation t1 t1' v1 <> Transformation t2 t2' v2
= Transformation (t1 <> t2) (t2' <> t1') (v1 ^+^ lapp t1 v2)
instance (Additive v, Num n) => Monoid (Transformation v n) where
mempty = Transformation mempty mempty zero
mappend = (<>)
-- | Transformations can act on transformable things.
instance (Transformable a, V a ~ v, N a ~ n) => Action (Transformation v n) a where
act = transform
-- | Apply a transformation to a vector. Note that any translational
-- component of the transformation will not affect the vector, since
-- vectors are invariant under translation.
apply :: Transformation v n -> v n -> v n
apply (Transformation (t :-: _) _ _) = t
-- | Apply a transformation to a point.
papply :: (Additive v, Num n) => Transformation v n -> Point v n -> Point v n
papply (Transformation t _ v) (P p) = P $ lapp t p ^+^ v
-- | Create a general affine transformation from an invertible linear
-- transformation and its transpose. The translational component is
-- assumed to be zero.
fromLinear :: (Additive v, Num n) => (v n :-: v n) -> (v n :-: v n) -> Transformation v n
fromLinear l1 l2 = Transformation l1 l2 zero
-- | An orthogonal linear map is one whose inverse is also its transpose.
fromOrthogonal :: (Additive v, Num n) => (v n :-: v n) -> Transformation v n
fromOrthogonal t = fromLinear t (linv t)
-- | A symmetric linear map is one whose transpose is equal to its self.
fromSymmetric :: (Additive v, Num n) => (v n :-: v n) -> Transformation v n
fromSymmetric t = fromLinear t t
-- | Get the dimension of an object whose vector space is an instance of
-- @HasLinearMap@, e.g. transformations, paths, diagrams, etc.
dimension :: forall a. (Additive (V a), Traversable (V a)) => a -> Int
dimension _ = length (basis :: [V a Int])
-- | Get the matrix equivalent of the linear transform,
-- (as a list of columns) and the translation vector. This
-- is mostly useful for implementing backends.
onBasis :: (Additive v, Traversable v, Num n) => Transformation v n -> ([v n], v n)
onBasis (Transformation (f :-: _) _ t) = (map f basis, t)
-- Remove the nth element from a list
remove :: Int -> [a] -> [a]
remove n xs = ys ++ zs
where
(ys, _ : zs) = splitAt n xs
-- Minor matrix of cofactor C(i,j)
minor :: Int -> Int -> [[a]] -> [[a]]
minor i j xs = remove j $ map (remove i) xs
-- The determinant of a square matrix represented as a list of lists
-- representing column vectors, that is [column].
det :: Num a => [[a]] -> a
det [a : _] = a
det m@(c1 : _) = sum [(-1) ^ i * (c1 !! i) * det (minor i 0 m) | i <- [0 .. (n - 1)]]
where
n = length m
-- | Convert a vector v to a list of scalars.
listRep :: Foldable v => v n -> [n]
listRep = toList
-- | Convert the linear part of a `Transformation` to a matrix
-- representation as a list of column vectors which are also lists.
matrixRep :: (Additive v, Traversable v, Num n) => Transformation v n -> [[n]]
matrixRep (Transformation (f :-: _) _ _) = map (toList . f) basis
-- | Convert a `Transformation v` to a homogeneous matrix representation.
-- The final list is the translation.
-- The representation leaves off the last row of the matrix as it is
-- always [0,0, ... 1] and this representation is the defacto standard
-- for backends.
matrixHomRep :: (Additive v, Traversable v, Num n) => Transformation v n -> [[n]]
matrixHomRep t = mr ++ [toList tl]
where
mr = matrixRep t
tl = transl t
-- | The determinant of (the linear part of) a `Transformation`.
determinant :: (Additive v, Traversable v, Num n) => Transformation v n -> n
determinant = det . matrixRep
-- | Determine whether a `Transformation` includes a reflection
-- component, that is, whether it reverses orientation.
isReflection :: (Additive v, Traversable v, Num n, Ord n) => Transformation v n -> Bool
isReflection = (<0) . determinant
-- | Compute the \"average\" amount of scaling performed by a
-- transformation. Satisfies the properties
--
-- @
-- avgScale (scaling k) == k
-- avgScale (t1 <> t2) == avgScale t1 * avgScale t2
-- @
--
avgScale :: (Additive v, Traversable v, Floating n) => Transformation v n -> n
avgScale t = (abs . determinant) t ** (recip . fromIntegral . dimension) t
{-
avgScale is computed as the nth root of the positive determinant.
This works because the determinant is the factor by which a transformation
scales area/volume. See http://en.wikipedia.org/wiki/Determinant.
Proofs for the specified properties:
1. |det (scaling k)|^(1/n) = (k^n)^(1/n) = k
2. |det t1|^(1/n) * |det t2|^(1/n)
= (|det t1| * |det t2|)^(1/n)
= |det t1 * det t2|^(1/n)
= |det (t1 <> t2)|^(1/n)
-}
------------------------------------------------------------
-- The Transformable class -------------------------------
------------------------------------------------------------
-- | 'HasLinearMap' is a constraint synonym, just to
-- help shorten some of the ridiculously long constraint sets.
type HasLinearMap v = (HasBasis v, Traversable v)
-- | An 'Additive' vector space whose representation is made up of basis elements.
type HasBasis v = (Additive v, Representable v, Rep v ~ E v)
-- | Type class for things @t@ which can be transformed.
class Transformable t where
-- | Apply a transformation to an object.
transform :: Transformation (V t) (N t) -> t -> t
instance (Additive v, Num n) => Transformable (Transformation v n) where
transform t1 t2 = t1 <> t2
instance (Additive v, Num n) => HasOrigin (Transformation v n) where
moveOriginTo p = translate (origin .-. p)
instance (Transformable t, Transformable s, V t ~ V s, N t ~ N s)
=> Transformable (t, s) where
transform t (x,y) = ( transform t x
, transform t y
)
instance (Transformable t, Transformable s, Transformable u, V s ~ V t, N s ~ N t, V s ~ V u, N s ~ N u)
=> Transformable (t,s,u) where
transform t (x,y,z) = ( transform t x
, transform t y
, transform t z
)
-- Transform functions by conjugation. That is, reverse-transform argument and
-- forward-transform result. Intuition: If someone shrinks you, you see your
-- environment enlarged. If you rotate right, you see your environment
-- rotating left. Etc. This technique was used extensively in Pan for modular
-- construction of image filters. Works well for curried functions, since all
-- arguments get inversely transformed.
instance ( V t ~ v, N t ~ n, V t ~ V s, N t ~ N s, Functor v, Num n
, Transformable t, Transformable s)
=> Transformable (s -> t) where
transform tr f = transform tr . f . transform (inv tr)
instance Transformable t => Transformable [t] where
transform = map . transform
instance (Transformable t, Ord t) => Transformable (S.Set t) where
transform = S.map . transform
instance Transformable t => Transformable (M.Map k t) where
transform = M.map . transform
instance (Additive v, Num n) => Transformable (Point v n) where
transform = papply
instance Transformable m => Transformable (Deletable m) where
transform = fmap . transform
------------------------------------------------------------
-- Translational invariance ------------------------------
------------------------------------------------------------
-- | @TransInv@ is a wrapper which makes a transformable type
-- translationally invariant; the translational component of
-- transformations will no longer affect things wrapped in
-- @TransInv@.
newtype TransInv t = TransInv t
deriving (Eq, Ord, Show, Semigroup, Monoid)
instance Wrapped (TransInv t) where
type Unwrapped (TransInv t) = t
_Wrapped' = iso (\(TransInv t) -> t) TransInv
instance Rewrapped (TransInv t) (TransInv t')
type instance V (TransInv t) = V t
type instance N (TransInv t) = N t
instance HasOrigin (TransInv t) where
moveOriginTo = const id
instance (Num (N t), Additive (V t), Transformable t) => Transformable (TransInv t) where
transform (Transformation a a' _) (TransInv t)
= TransInv (transform (Transformation a a' zero) t)
instance (InSpace v n t, Transformable t, HasLinearMap v, Floating n)
=> Transformable (Measured n t) where
transform t = scaleLocal n . fmap (transform t')
where
t' = t <> scaling (1 / avgScale t)
n = avgScale t
------------------------------------------------------------
-- Generic transformations -------------------------------
------------------------------------------------------------
-- | Create a translation.
translation :: v n -> Transformation v n
translation = Transformation mempty mempty
-- | Translate by a vector.
translate :: (Transformable t) => Vn t -> t -> t
translate = transform . translation
-- | Create a uniform scaling transformation.
scaling :: (Additive v, Fractional n) => n -> Transformation v n
scaling s = fromSymmetric lin
where lin = (s *^) <-> (^/ s)
-- | Scale uniformly in every dimension by the given scalar.
scale :: (InSpace v n a, Eq n, Fractional n, Transformable a)
=> n -> a -> a
scale 0 = error "scale by zero! Halp!" -- XXX what should be done here?
scale s = transform $ scaling s