PGF 装饰:如何记住装饰状态之间的点?

PGF 装饰:如何记住装饰状态之间的点?

我想制作一个装饰,其中连续的装饰段由贝塞尔曲线连接,该曲线的第一个控制点相对于第一个装饰段定义,而第二个控制点相对于第二个装饰段定义。举一个简单的例子,下面是我尝试制作的装饰,它创建了一系列曲线:

  1. 从与生成路径偏移装饰幅度的点开始,即装饰段的起点n,即(0, \pgfdecorationsegmentamplitude)装饰线段坐标处的点n
  2. 将生成路径上的第一个控制点置于(0,0)装饰段坐标的点处n
  3. 将生成路径上的第二个控制点置于(0,0)装饰段坐标的点处n+1
  4. 结束于与起点类似的点,但在曲线上的下一个位置,即(0, \pgfdecorationsegmentamplitude)装饰段坐标中的点n+1

这样应该会形成一系列非常尖锐的点,其两侧开始共线。连续点之间的曲线应该是平滑的。

编辑:这是在直线上明确绘制的期望结果(即不使用装饰)。生成路径是虚线。但是,请注意,这个期望结果只是一个例子;我真正想要的只是一种将点从装饰自动机的一个步骤传递到下一个步骤的机制。

\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}

在此处输入图片描述

曲线实际上需要绘制在段 $n+1$ 中,此时可以计算出第二个控制点和端点。起点不是问题,因为它是路径的当前端点,因此不需要在 中指定\pgfpathcurveto。困难在于如何通过第一个控制点,该控制点是在 的坐标系中计算的n,细分n+2

我知道persistent precomputationpersistent postcomputation选项,它们可用于执行结果将持续到下一个状态的代码,但我不知道如何将点位置放入代码中persistent postcomputation,该代码在定义状态坐标变换的 TEX 组之外执行。我尝试\pgfpoint在状态代码中调用,它可以使用变换来计算\pgf@x\pgf@y,然后尝试使用捕获值。但是,这不起作用,我认为这是因为在 TEX 组内设置的和的值是该 TEX 组的局部值。我尝试使用使值成为全局的。这在我的测试用例中有效\pgfgetlastxypersistent postcomputation\pgf@x\pgf@y\pgf@process

\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}

在此处输入图片描述

然而,在装饰的背景下,\pgf@process引发了一个Undefined control sequence错误。

编辑:Mark Wibrow 指出,需要\makeatletter在装饰定义之外。更改后,\pgf@process可以正常运行,并且似乎该点已被捕获persistent postcalculation。但是,捕获的是该点在线段参考系中的坐标n,即 (0,0)。(我已通过将值更改为其他值来确认该点确实被捕获)。然后,当我在段中引用它时n+1,它在坐标中为(0,0)n+1,尽管我\pgftransformreset在声明点之前使用了。也许我对变换的实现方式有些不理解?我认为调用将\pgfpoint当前变换矩阵应用于您提供的坐标,然后相应地设置\pgf@x和的值\pgf@y。如果这是真的,为什么我得到的点仍然是 (0,0)?为什么不起作用\pgftransformreset?无论我是否使用它,我都会得到完全相同的结果。

(我也进一步简化了示例代码;它还保留了我更复杂的原始设计的一些残余。)

\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}

在此处输入图片描述

我知道可以通过设置一系列命名坐标,然后在最后一段中实际绘制整条曲线来实现这一点,但我想要一个不那么复杂的解决方案。

解决方案(感谢 Mark):我错了,\pgfpoint自动应用当前变换。这实际上是由各种绘图命令完成的。要将当前变换应用于某个点,\pgfpointtransformed,但似乎文档中没有?这需要使用两次,一次是在段中生成控制点时将变换应用于控制点n,并再次反转段的变换n+1,它将被应用\pgfpathcurveto。以下是正确代码:

\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}

在此处输入图片描述

答案1

这里有几个问题。

首先,您需要将 放在\makeatletter装修声明 之前。

在处理状态代码时,它已被解析,并且已将现行类别代码 12 分配给该@字符。因此,当运行修饰时,TeX 不会查找\pgf@processbut \pgf,因此会出现Undefined control sequence错误。

放在\makeatetter修饰声明之前可以消除错误。但我不确定结果路径是否是所需的。

其次,您可能需要将“全局”变换矩阵应用于装饰状态内定义的点以获取“绝对”坐标

 \pgfpointtransformed{\pgfpointorigin} 

会为您完成此事。

请注意,使用坐标节点可能更有效。坐标节点的位置(与所有节点一样)是“绝对”保存的(即,全局变换矩阵应用于指定的任何位置)。由于节点定义了几个不需要的锚点(例如,等),因此可能会有一些开销。但north使用south

\pgfcoordinate{@1}{\pgfpoint{0pt}{0pt}}

装饰状态内部将位置与命名的坐标注释绝对关联@1,并且可以稍后使用以下命令获取确切位置(装饰状态内部或外部):

\pgfpointanchor{@1}{center}

答案2

我认为您不需要所有的前后持续计算来定义控制点,您可以尝试使用段长度和幅度。

以下代码输出:

在此处输入图片描述

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations}
\makeatletter
\pgfdeclaredecoration{test}{initial}{%
\state{initial}[%
    width=\pgfdecorationsegmentlength, %
    next state=curve]{%
    \pgfpathmoveto{\pgfpoint{0}{\pgfdecorationsegmentamplitude}}
    \pgfpathcurveto%
    {\pgfpoint{0pt}{-\pgfdecorationsegmentamplitude/3}}% support a
    {\pgfpoint{\pgfdecorationsegmentlength}{-\pgfdecorationsegmentamplitude/3}}% support b
    {\pgfpoint{\pgfdecorationsegmentlength}{\pgfdecorationsegmentamplitude}}% coordinate
}
\state{curve}[%
    width=\pgfdecorationsegmentlength,
    next state=curve]{%
 %draw the curve
  \pgfpathcurveto%
  {\pgfpoint{0pt}{-\pgfdecorationsegmentamplitude/3}}% support a
  {\pgfpoint{\pgfdecorationsegmentlength}{-\pgfdecorationsegmentamplitude/3}}% support b
  {\pgfpoint{\pgfdecorationsegmentlength}{\pgfdecorationsegmentamplitude}}% coordinate
}
\state{final}{%
  \pgfpathcurveto%
  {\pgfpoint{0pt}{-\pgfdecorationsegmentamplitude/3}}% support a
  {\pgfpoint{\pgfdecorationsegmentlength}{-\pgfdecorationsegmentamplitude/3}}% support b
  {\pgfpointadd{\pgfpointdecoratedpathlast}{\pgfpoint{0pt}{\pgfdecorationsegmentamplitude}}}% coordinate
}
}
\makeatother
\begin{document}
\begin{tikzpicture}[decoration={test, segment length=2em, amplitude=5em}]
    \draw[decorate] (0,0) .. controls (3,3) .. (10,0);
    \draw[dotted] (0,0) .. controls (3,3) .. (10,0);
\end{tikzpicture}
\end{document}

相关内容