Every picture value is expressed within its own local coordinate system. The geometric Transform constructor applied to a Picture returns a new picture with a transformed local coordinate system, so, for instance, doubleSize doubles the scaling in both directions. Since scaling and rotation are about the origin of the local coordinate system, we often need to translate the picture prior to performing a scaling or rotation:(2)
\hbox{
\hspace{0.25in}
\begin{minipage}[b]{2in}
\begin{verbatim}
ellipseA =
Transform (rotate (pi/4)) $
ellipse (30,20)
ellipseB =
Transform (rotate (pi/4)) $
Transform (xlt (30,0)) $
ellipse (30,20)
\end{verbatim}
\end{minipage}
\vbox{\psfig{figure=rotate-1.eps}}
\vbox{\vspace*{0.3in}
\psfig{figure=rotate-2.eps}}}
ellipseA =
Transform (rotate (pi/4)) $
ellipse (30,20)
ellipseB =
Transform (rotate (pi/4)) $
Transform (xlt (30,0)) $
ellipse (30,20)
To rotate around the leftmost point of an ellipse (rightmost picture), the ellipse has to be translated along the X-axis before rotation, as seen in the definition of ellipseB. For ellipses, rotation around the centre is straigtforward, as the origin of the ellipse picture coincides with the origin of its local coordinate system.
However, for ellipseB, the definition depended on knowing the exact amount it had to translate by. This makes it hard to write a general picture combinator for rotating a picture around the leftmost or western point of its bounding box, without some extra support. Rather than providing a function that computes the bounding box (i.e., the smallest rectangle that encapsulates the picture shape), we provide a mechanism called structured translation:
@tindex{PicMove} @tindex{CompassDirection} @tindex{Offset} \vspace{0.15in} \hbox{ \hspace{0.3in} \begin{minipage}[b]{2.2in} \begin{verbatim} data Picture = ... | PicMove Offset Picture ... data Offset = OffDir CompassDirection | OffPropX Double -- [0.0..1.0] | OffPropY Double -- [0.0..1.0] \end{verbatim} \end{minipage} \vbox{\vspace*{0.5in} \begin{minipage}[b]{2in} \begin{verbatim} data CompassDirection = West | NorthWest | North | ... | South | Centre \end{verbatim} \end{minipage}}}
data Picture = ... | Move Offset Picture data CompassDirection ... = West | NorthWest data Offset = | North | OffDir CompassDirection ... | OffPropX Double -- [0.0..1.0] | South | Centre | OffPropY Double -- [0.0..1.0]
Structured Translation allows you to abstractly translate a picture with respect to its bounding box, leaving it up to the renderer to compute the actual translation amount. Generalising the rotation performed by ellipseB becomes then
westRot :: Radians -> Picture -> Picture westRot rad pic = Transform (rotate rad) $ Move (OffDir West) pic
westRot translates pic such that its bounding box is shifted to the right of the vertical axis and centred around the horizontal axis. The structured translation constructor PicMove is parameterised on Offset which is either a translation to one of eight points on the bounding box perimeter (or the centre), or a proportional translation in either the X or Y direction.
Nested applications of the PicMove constructor are clearly redundant, and can be transformed away:
PicMove dir1 (PicMove dir2 pic) = PicMove dir1 pic
i.e., since the Move constructor does not alter the size of a picture's bounding box, the inner application of Move can safely be ignored since the outer Move will potentially undo whatever translation the inner Move did. This useful rule is made use of by the rendering function (see Section See section Rendering pictures) to `simplify' a Picture value before rendering.