diagrams-lib-1.3: src/Diagrams/ThreeD/Transform.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}
-----------------------------------------------------------------------------
-- |
-- Module : Diagrams.ThreeD.Transform
-- Copyright : (c) 2013 diagrams-lib team (see LICENSE)
-- License : BSD-style (see LICENSE)
-- Maintainer : diagrams-discuss@googlegroups.com
--
-- Transformations specific to three dimensions, with a few generic
-- transformations (uniform scaling, translation) also re-exported for
-- convenience.
--
-----------------------------------------------------------------------------
module Diagrams.ThreeD.Transform
( T3
-- * Rotation
, aboutX, aboutY, aboutZ
, rotationAbout, rotateAbout
, pointAt, pointAt'
-- * Scaling
, scalingX, scalingY, scalingZ
, scaleX, scaleY, scaleZ
, scaling, scale
-- * Translation
, translationX, translateX
, translationY, translateY
, translationZ, translateZ
, translation, translate
-- * Reflection
, reflectionX, reflectX
, reflectionY, reflectY
, reflectionZ, reflectZ
, reflectionAcross, reflectAcross
) where
import Diagrams.Core
import Diagrams.Core.Transform
import Diagrams.Angle
import Diagrams.Direction
import Diagrams.ThreeD.Types
import Diagrams.Transform
import Diagrams.Points
import Control.Lens (view, (&), (*~), (.~), (//~))
import Data.Semigroup
import Diagrams.TwoD.Transform
import Linear.Affine
import Linear.Metric
import Linear.V3 (cross)
import Linear.Vector
-- | Create a transformation which rotates by the given angle about
-- a line parallel the Z axis passing through the local origin.
-- A positive angle brings positive x-values towards the positive-y axis.
--
-- The angle can be expressed using any type which is an
-- instance of 'Angle'. For example, @aboutZ (1\/4 \@\@
-- 'turn')@, @aboutZ (tau\/4 \@\@ 'rad')@, and @aboutZ (90 \@\@
-- 'deg')@ all represent the same transformation, namely, a
-- counterclockwise rotation by a right angle. For more general rotations,
-- see 'rotationAbout'.
--
-- Note that writing @aboutZ (1\/4)@, with no type annotation, will
-- yield an error since GHC cannot figure out which sort of angle
-- you want to use.
aboutZ :: Floating n => Angle n -> Transformation V3 n
aboutZ (view rad -> a) = fromOrthogonal r where
r = rot a <-> rot (-a)
rot θ (V3 x y z) = V3 (cos θ * x - sin θ * y)
(sin θ * x + cos θ * y)
z
-- | Like 'aboutZ', but rotates about the X axis, bringing positive y-values
-- towards the positive z-axis.
aboutX :: Floating n => Angle n -> Transformation V3 n
aboutX (view rad -> a) = fromOrthogonal r where
r = rot a <-> rot (-a)
rot θ (V3 x y z) = V3 x
(cos θ * y - sin θ * z)
(sin θ * y + cos θ * z)
-- | Like 'aboutZ', but rotates about the Y axis, bringing postive
-- x-values towards the negative z-axis.
aboutY :: Floating n => Angle n -> Transformation V3 n
aboutY (view rad -> a) = fromOrthogonal r where
r = rot a <-> rot (-a)
rot θ (V3 x y z) = V3 (cos θ * x + sin θ * z)
y
(-sin θ * x + cos θ * z)
-- | @rotationAbout p d a@ is a rotation about a line parallel to @d@
-- passing through @p@.
rotationAbout
:: Floating n
=> Point V3 n -- ^ origin of rotation
-> Direction V3 n -- ^ direction of rotation axis
-> Angle n -- ^ angle of rotation
-> Transformation V3 n
rotationAbout (P t) d (view rad -> a)
= mconcat [translation (negated t),
fromOrthogonal r,
translation t] where
r = rot a <-> rot (-a)
w = fromDirection d
rot θ v = v ^* cos θ
^+^ cross w v ^* sin θ
^+^ w ^* ((w `dot` v) * (1 - cos θ))
-- | @rotationAbout p d a@ is a rotation about a line parallel to @d@
-- passing through @p@.
rotateAbout
:: (InSpace V3 n t, Floating n, Transformable t)
=> Point V3 n -- ^ origin of rotation
-> Direction V3 n -- ^ direction of rotation axis
-> Angle n -- ^ angle of rotation
-> t -> t
rotateAbout p d theta = transform (rotationAbout p d theta)
-- | @pointAt about initial final@ produces a rotation which brings
-- the direction @initial@ to point in the direction @final@ by first
-- panning around @about@, then tilting about the axis perpendicular
-- to @about@ and @final@. In particular, if this can be accomplished
-- without tilting, it will be, otherwise if only tilting is
-- necessary, no panning will occur. The tilt will always be between
-- ± 1/4 turn.
pointAt :: Floating n
=> Direction V3 n -> Direction V3 n -> Direction V3 n
-> Transformation V3 n
pointAt a i f = pointAt' (fromDirection a) (fromDirection i) (fromDirection f)
-- | pointAt' has the same behavior as 'pointAt', but takes vectors
-- instead of directions.
pointAt' :: Floating n => V3 n -> V3 n -> V3 n -> Transformation V3 n
pointAt' about initial final = pointAtUnit (signorm about) (signorm initial) (signorm final)
-- | pointAtUnit has the same behavior as @pointAt@, but takes unit vectors.
pointAtUnit :: Floating n => V3 n -> V3 n -> V3 n -> Transformation V3 n
pointAtUnit about initial final = tilt <> pan where
-- rotating u by (signedAngle rel u v) about rel gives a vector in the direction of v
signedAngle rel u v = signum (cross u v `dot` rel) *^ angleBetween u v
inPanPlaneF = final ^-^ project about final
inPanPlaneI = initial ^-^ project about initial
panAngle = signedAngle about inPanPlaneI inPanPlaneF
pan = rotationAbout origin (direction about) panAngle
tiltAngle = signedAngle tiltAxis (transform pan initial) final
tiltAxis = cross final about
tilt = rotationAbout origin (direction tiltAxis) tiltAngle
-- Scaling -------------------------------------------------
-- | Construct a transformation which scales by the given factor in
-- the z direction.
scalingZ :: (Additive v, R3 v, Fractional n) => n -> Transformation v n
scalingZ c = fromSymmetric $ (_z *~ c) <-> (_z //~ c)
-- | Scale a diagram by the given factor in the z direction. To scale
-- uniformly, use 'scale'.
scaleZ :: (InSpace v n t, R3 v, Fractional n, Transformable t) => n -> t -> t
scaleZ = transform . scalingZ
-- Translation ----------------------------------------
-- | Construct a transformation which translates by the given distance
-- in the z direction.
translationZ :: (Additive v, R3 v, Num n) => n -> Transformation v n
translationZ z = translation (zero & _z .~ z)
-- | Translate a diagram by the given distance in the y
-- direction.
translateZ :: (InSpace v n t, R3 v, Transformable t) => n -> t -> t
translateZ = transform . translationZ
-- Reflection ----------------------------------------------
-- | Construct a transformation which flips a diagram across z=0,
-- i.e. sends the point (x,y,z) to (x,y,-z).
reflectionZ :: (Additive v, R3 v, Num n) => Transformation v n
reflectionZ = fromSymmetric $ (_z *~ (-1)) <-> (_z *~ (-1))
-- | Flip a diagram across z=0, i.e. send the point (x,y,z) to
-- (x,y,-z).
reflectZ :: (InSpace v n t, R3 v, Transformable t) => t -> t
reflectZ = transform reflectionZ
-- | @reflectionAcross p v@ is a reflection across the plane through
-- the point @p@ and normal to vector @v@. This also works as a 2D
-- transform where @v@ is the normal to the line passing through point
-- @p@.
reflectionAcross :: (Metric v, Fractional n)
=> Point v n -> v n -> Transformation v n
reflectionAcross p v =
conjugate (translation (origin .-. p)) reflect
where
reflect = fromLinear t (linv t)
t = f v <-> f (negated v)
f u w = w ^-^ 2 *^ project u w
-- | @reflectAcross p v@ reflects a diagram across the plane though
-- the point @p@ and the vector @v@. This also works as a 2D transform
-- where @v@ is the normal to the line passing through point @p@.
reflectAcross :: (InSpace v n t, Metric v, Fractional n, Transformable t)
=> Point v n -> v n -> t -> t
reflectAcross p v = transform (reflectionAcross p v)