I want to make a decoration where successive decoration segments are connected by a Bézier curve whose first control point is defined relative to the first decoration segment, while the second point is defined relative to the second segment. To make a simple example, here is my attempt at a decoration which creates a series of curves which:
- Start at a point offset by the decoration amplitude from the generating path, at the start point of decoration segment n, i.e., at the point
(0, \pgfdecorationsegmentamplitude)
in the coordinates of decoration segment n; - Have the first control point on the generating path at the point
(0,0)
in the coordinates of decoration segment n; - Have the second control point on the generating path at the point
(0,0)
in the coordinates of decoration segment n+1; - End at a point analogous to the start point, but at the next position along the curve, at point
(0, \pgfdecorationsegmentamplitude)
in the coordinates of decoration segment n+1.
This should make a series of very sharp points, whose two sides begin co-linear. The curves between successive points should be smooth.
edit: Here is the desired result drawn explicitly (i.e., not using a decoration) on a straight line. The generating path is dashed. But, note that this desired result is an example; what I really want is just a mechanism for passing points from one step in the decoration automaton to the next.
\begin{tikzpicture} \draw[dashed] (0,0) -- (20em,0); \draw (0,2 em) \foreach \x in {1, 2, ..., 20} { .. controls (\x em - 1 em,0 em) and (\x em,0em) .. (\x em,2 em) } ; \end{tikzpicture}
The curve needs to actually be drawn in segment $n+1$, when the second control point and endpoint can be calculated. The start point is not a problem, because it is the current end point of the path and thus does not need to be specified in \pgfpathcurveto
. The difficulty is how to pass the first control point, which is calculated in the coordinate system of n, to segment n+2.
I know about the persistent precomputation
and persistent postcomputation
options, which can be used to execute code whose results will persist to the next state, but I don't know how to get the point location into the persistent postcomputation
code, which is executed outside the TEX-group where the coordinate transformation for the state is defined. My attempt calls \pgfpoint
inside the state code, where it can use the transform to calculate \pgf@x
and \pgf@y
, and then tries to capture the values using \pgfgetlastxy
in the persistent postcomputation
. However, this doesn't work, I think because values of \pgf@x
and \pgf@y
set inside a TEX-group are local to that TEX-group. I tried using \pgf@process
to make the the values global. This works in my test case:
\documentclass{article}
\usepackage{tikz}
\begin{document}
\pgfpoint{0}{0}
\begingroup
\pgfpoint{2}{3}
\endgroup
\pgfgetlastxy{\lastx}{\lasty}
$\\lastx$ is \lastx; $\\lasty$ is \lasty.
\begingroup
\makeatletter
\pgf@process{
\pgfpoint{2}{3}
}
\endgroup
\pgfgetlastxy{\lastx}{\lasty}
$\\lastx$ is \lastx; $\\lasty$ is \lasty.
\end{document}
However, in the context of the decoration, the \pgf@process
raises an Undefined control sequence
error.
edit: Mark Wibrow pointed out that the
\makeatletter
needed to be outside the decoration definition. After changing that,\pgf@process
works without an error, and it does appear that the point is being captured by thepersistent postcalculation
. However, what is being captured is that point's coordinates in the reference frame of segment n, which is (0,0). (I have confirmed that the point is really being captured, by changing the values to something else). Then, when I refer to it in segment n+1, it is (0,0) in the coordinates of n+1, even though I use\pgftransformreset
before declaring the point. Maybe there's something I don't understand about the way the transformations are implemented? I thought that a call to\pgfpoint
applies the current transformation matrix to the coordinates you give it, and then sets the values of\pgf@x
and\pgf@y
accordingly. If that's true, why is the point I get still (0,0)? And why isn't\pgftransformreset
working? I get exactly the same result whether I use it or not.(I have also simplified the example code even more; it had some remnants of my more complicated original design left.)
\usetikzlibrary{decorations} \newdimen\ya \newdimen\xa \makeatletter \pgfdeclaredecoration{test}{initial} { \state{initial}[% width=\pgfdecorationsegmentlength, % next state=curve, % persistent postcomputation={ %store the next control point \pgfgetlastxy{\xa}{\ya} } ] { \pgfpathmoveto{\pgfpoint{0}{\pgfdecorationsegmentamplitude}} %calculate the next control point \pgf@process{\pgfpoint{0}{0}} } % \state{curve}[% width=\pgfdecorationsegmentlength, next state=curve, persistent postcomputation={ %store the next control point \pgfgetlastxy{\xa}{\ya} } ]{ %draw the curve \pgfpathcurveto% { \pgfgettransform{\oldtransform} \pgftransformreset \pgfpoint{\xa}{\ya} \pgfsettransform{\oldtransform} }% {\pgfpoint{0}{0}}% {\pgfpoint{0}{\pgfdecorationsegmentamplitude}}% % set the next control point \pgf@process{\pgfpoint{0}{0}} } \state{final} { %draw the curve \pgfpathcurveto% {\pgfgettransform{\oldtransform} \pgftransformreset \pgfpoint{\xa}{\ya} \pgfsettransform{\oldtransform}}% {\pgfpoint{0}{0}}% {\pgfpoint{0}{\pgfdecorationsegmentamplitude}}% } } \makeatother \begin{tikzpicture}[decoration={test, segment length=3em, amplitude=2em}] \draw[decorate] (0,0) .. controls (3,3) .. (10,0); \end{tikzpicture}
I am aware that it might be possible to to this by setting up a series of named coordinates, and then actually drawing the whole curve in the final segment, but I'd like a solution which is less hacky.
Solution (Thanks to Mark): I was incorrect that
\pgfpoint
automatically applies the current transform. This is actually done by the various drawing commands. To apply the current transform to a point, there is\pgfpointtransformed
, which seems to not be in the documentation? This needs to be used twice, once to apply the transform to the control point when it is generated in segment n, and again to invert the transform for segment n+1, which will be applied by\pgfpathcurveto
. Here is the correct code:\documentclass{article} \usepackage[latin1]{inputenc} \usepackage{tikz} \usetikzlibrary{decorations,intersections} \begin{document} \newdimen\ya \newdimen\xa \makeatletter \pgfdeclaredecoration{test}{initial} { \state{initial}[% width=\pgfdecorationsegmentlength, % next state=curve, % persistent postcomputation={ %store the next control point \pgfgetlastxy{\xa}{\ya} } ]{ \pgfpathmoveto{\pgfpoint{0}{\pgfdecorationsegmentamplitude}} %calculate the next control point \pgf@process{\pgfpointtransformed{\pgfpoint{0}{0}}} } % \state{curve}[% width=\pgfdecorationsegmentlength, next state=curve, persistent postcomputation={ %store the next control point \pgfgetlastxy{\xa}{\ya} } ]{ %draw the curve \pgfpathcurveto% { \pgfgettransform{\oldtransform} \pgftransforminvert \pgfpointtransformed{\pgfpoint{\xa}{\ya}} \pgfsettransform{\oldtransform} }% {\pgfpoint{0}{0}}% {\pgfpoint{0}{\pgfdecorationsegmentamplitude}}% %calculate the next control point \pgf@process{\pgfpointtransformed{\pgfpoint{0}{0}}} } \state{final} { %draw the curve \pgfpathcurveto% {\pgfgettransform{\oldtransform} \pgftransforminvert \pgfpointtransformed{\pgfpoint{\xa}{\ya}} \pgfsettransform{\oldtransform}}% {\pgfpoint{0}{0}}% {\pgfpoint{0}{\pgfdecorationsegmentamplitude}}% %finish up \pgfpathquadraticcurveto% {\pgfpoint{0}{0}} {\pgfpointdecoratedpathlast} } } \makeatother \begin{tikzpicture}[decoration={test, segment length=3em, amplitude=2em}] \draw[decorate] (0,0) .. controls (3,3) .. (10,0); \end{tikzpicture} \end{document}
\pgfpointtransformed{\pgfpoint{0}{0}}
(or the more efficient\pgfpointtransformed{\pgfpointorigin}
) get what you want?\pgfpointtransformed
uses\pgf@process
internally. Maybe you don't want to reset the the transformation matrix but apply it to the point to get the "absolute" coordinate?