TikZ:扩展描边

TikZ:扩展描边

我试着画一些螺旋线,然后问这个问题几天前。hpekristiansen 的回答很棒,帮助很大,但由于看图片不清楚螺旋是右旋还是左旋,所以在我想要的环境中看起来有点奇怪。今天,hpekristiansen 提了一个问题自己在这个话题上TikZling 的一个非常有用的答案\foreach。我特别喜欢使用循环绘制各个段的答案。剩下的问题是,我不能使用double。剩下的问题是,我无法使用路径选项,因为它在非白色背景上可见,或者像我的用例围绕螺旋的杆。

解决这个问题的方法是裁剪奇数路径(从第三条开始)与偶数路径相交的部分。不幸的是,\path [clip] Ti 中的Z 只使用路径的中心来剪切某些内容,并且没有设置将被剪切的线宽的选项。因此,我想知道是否可以将给定线宽的路径扩展为形状,就像使用 Adob​​e Illustrator 或 Affinity Designer 等矢量图形软件一样。

扩大描边

当在几个部分(环的左侧部分和环的右侧部分)绘制螺旋时,这将允许使用类似于以下示例的代码:

\documentclass[tikz]{standalone}

\begin{document}
    \begin{tikzpicture}[even odd rule]
        \newcommand{\radiusX}{0.7}
        \newcommand{\radiusY}{1.5}
        \newcommand{\strokeWidth}{0.1}
        \newcommand{\strokeWidthExtra}{0.1}
    
        \newcommand{\background}{({-\radiusX-1},-1) rectangle ({8+\radiusX+1},{2*\radiusY+1})}
        
        \newcommand{\leftArc}{
            (0.5, 0) 
                -- (0, 0) 
                arc (-90:-270:{\radiusX} and {\radiusY}) 
                -- ++(0, -\strokeWidth) 
                arc (90:270:{\radiusX-\strokeWidth} and {\radiusY-\strokeWidth}) 
                -- ++(0.5,0) 
                -- ++(0,-\strokeWidth) 
                -- cycle
        }
        
        \newcommand{\leftArcBig}{
            ({0.5+\strokeWidthExtra}, -\strokeWidthExtra) 
                -- ++({-0.5-\strokeWidthExtra}, 0) 
                arc (-90:-270:{\radiusX+\strokeWidthExtra} and {\radiusY+\strokeWidthExtra}) 
                -- ++(0, {-\strokeWidth-2*\strokeWidthExtra}) 
                arc (90:270:{\radiusX-\strokeWidth-\strokeWidthExtra} and {\radiusY-\strokeWidth-\strokeWidthExtra}) 
                -- ++({0.5+\strokeWidthExtra},0) 
                -- ++(0,{-\strokeWidth+2*\strokeWidthExtra}) 
                -- cycle
        }
        
        \newcommand{\rightArc}{
            (-0.5,0) 
                -- (0,0) 
                arc (-90:90:{\radiusX} and {\radiusY}) 
                -- ++(0,-\strokeWidth) 
                arc (90:-90:{\radiusX-\strokeWidth} and {\radiusY-\strokeWidth}) 
                -- ++(-0.5,0) 
                -- ++(0,-{\strokeWidth}) 
                -- cycle
        }
        
        \newcommand{\rightArcBig}{
            (-{0.5-\strokeWidthExtra},-{\strokeWidthExtra}) 
                -- ++({0.5+\strokeWidthExtra},0) 
                arc (-90:90:{\radiusX+\strokeWidthExtra} and {\radiusY+\strokeWidthExtra}) 
                -- ++(0,{-\strokeWidth-2*\strokeWidthExtra}) 
                arc (90:-90:{\radiusX-\strokeWidth-\strokeWidthExtra} and {\radiusY-\strokeWidth-\strokeWidthExtra}) 
                -- ++({-0.5-\strokeWidthExtra},0) 
                -- ++(0,{-\strokeWidth-2*\strokeWidthExtra}) 
                -- cycle
        }
        
        \shade[clip, top color = gray, bottom color = lightgray] \background;
            
        \begin{scope}
            \fill [black] \rightArc;
            \clip \rightArcBig \background;
                
            \fill [black] \leftArc;
        \end{scope}
        
        \begin{scope}[xshift = 2cm]
            \fill [yellow] \rightArc;
            \fill [yellow, fill opacity = 0.3] \rightArcBig;
            \fill [red] \leftArc;
            \fill [red, fill opacity = 0.3] \leftArcBig;
        \end{scope}
        
        \begin{scope}[xshift = 6cm]
            \fill [black] \leftArc;
            \clip \leftArcBig \background;
                
            \fill [black] \rightArc;
        \end{scope}
        
        \begin{scope}[xshift = 8cm]
            \fill [yellow] \leftArc;
            \fill [yellow, fill opacity = 0.3] \leftArcBig;
            \fill [red] \rightArc;
            \fill [red, fill opacity = 0.3] \rightArcBig;
        \end{scope}
        
    \end{tikzpicture}
\end{document}

剪切路径

答案1

不是一个真正的答案。你问的是是否有办法构造路径的包络。答案是没有内置或简单的方法来实现这一点。更糟糕的是,有一个分析证明,没有简单而通用的方法。为了理解证明,回想一下所有 TiZ 能做的就是构造贝塞尔曲线。请注意,这并不是说没有不那么简单的方法。事实上,MetaPost 和朋友们有这样的例程,这说明原则上是可行的。

另一个能够做到这一点的工具是查看器。好吧,让我们让查看器做这些脏活。这允许人们以另一种方式解决问题,这在概念上与这个帖子:淡入淡出。不太方便,至少不是下面的实现,但这是一个原理证明。基本上,您可以将灰度转换为透明度,从而使黑线或白线透明。此对象可以放在任意背景之上。(我是否已经提到过这种实现不方便?)

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations.pathreplacing,fadings}%
\begin{document}
\begin{tikzfadingfrompicture}[name=custom fade]%
\tikzset{path decomposition/.style={%
    postaction={decoration={show path construction,
    lineto code={
      \draw[#1]  (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);
    },
    curveto code={
      \draw[#1]  (\tikzinputsegmentfirst) .. controls
        (\tikzinputsegmentsupporta) and (\tikzinputsegmentsupportb)
        ..(\tikzinputsegmentlast) ;
    },
    closepath code={
      \draw[#1]  (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast) {closepath};} }
    ,decorate}},
    cv/.style={black, double=white,line width=0.6mm,double distance=1.2mm}}
\draw[cv,samples=201,domain=-2*pi:2*pi,smooth,
 path decomposition={cv,shorten <=-0.05pt,shorten >=-0.05pt}]
 plot (\x, {cos(10*\x r)} , {sin(10*\x r)} );
\end{tikzfadingfrompicture}%
\begin{tikzpicture}
  \shade[clip, top color = gray!50!black, bottom color = gray!10] 
       (0,-2) rectangle (6,2);
 \path[path fading=custom fade,fit fading=false,
      fill=black] (0,-2) rectangle (8,2);
\end{tikzpicture}
\end{document}

在此处输入图片描述

答案2

纯属巧合,我一直在研究一些可能对您有帮助的代码。它旨在在交叉点处分割路径。

它基于我的spath3卡坦github) 库,它提供了一个用于在路径定义之后但在路径固定之前操作路径的结构。

这绝对是实验性的代码,可能会发生变化,但对于它是否有意义以及如何使它变得有用的反馈将会很有用。

\documentclass{article}

\usepackage{xparse}
\usepackage{tikz}
\usepackage{spath3}
\usetikzlibrary{intersections,hobby,patterns}

\ExplSyntaxOn

\tikzset{
  append~ spath/.code={
    \spath_get_current_path:n {current path}
    \spath_append:nn { current path } { #1 }
    \spath_set_current_path:n { current path }
  },
  set~ spath/.code={
    \spath_set_current_path:n { #1 }
    \spath_get:nnN {#1} {final point} \l__spath_tmpa_tl
    \tl_set:Nx \l__spath_tmpa_tl
    {
      \exp_not:c {tikz@lastx}=\tl_item:Nn \l__spath_tmpa_tl {1}
      \exp_not:c {tikz@lasty}=\tl_item:Nn \l__spath_tmpa_tl {2}
      \exp_not:c {tikz@lastxsaved}=\tl_item:Nn \l__spath_tmpa_tl {1}
      \exp_not:c {tikz@lastysaved}=\tl_item:Nn \l__spath_tmpa_tl {2}
    }
    \tl_use:N \l__spath_tmpa_tl
  },
  shorten~spath~at~end/.code~ 2~ args={
    \spath_shorten:nn {#1} {#2}
  },
  shorten~spath~at~start/.code~ 2~ args ={
    \spath_reverse:n {#1}
    \spath_shorten:nn {#1} {#2}
    \spath_reverse:n {#1}
  },
  shorten~spath~both~ends/.code~ 2~ args={
    \spath_shorten:nn {#1} {#2}
    \spath_reverse:n {#1}
    \spath_shorten:nn {#1} {#2}
    \spath_reverse:n {#1}
  },
  globalise~ spath/.code={
    \spath_globalise:n {#1}
  },
  translate~ spath/.code~ n~ args={3}{
    \spath_translate:nnn {#1}{#2}{#3}
  },
  split~ at~ self~ intersections/.code~ 2~ args={
    \use:c {tikz@addmode}{
      \group_begin:
      \spath_get_current_path:n {spath split tmpa}
      \spath_split_at_self_intersections:nnn {spath split tmpa} {#1} {#2}
      \group_end:
    }
  },
  split~ at~ intersections/.code~ n~ args={5}{
    \spath_split_at_intersections:nnnnn {#1}{#2}{#3}{#4}{#5}
  }
}


\tl_new:N \l__spath_shorten_fa_tl
\tl_new:N \l__spath_shorten_path_tl
\tl_new:N \l__spath_shorten_last_tl
\int_new:N \l__spath_shorten_int
\fp_new:N \l__spath_shorten_x_fp
\fp_new:N \l__spath_shorten_y_fp

\cs_new_nopar:Npn \spath_shorten:nn #1#2
{
  \group_begin:
  \spath_get:nnN {#1} {final action} \l__spath_shorten_fa_tl
  \spath_get:nnN {#1} {path} \l__spath_shorten_path_tl
  \tl_reverse:N \l__spath_shorten_path_tl

  \tl_clear:N \l__spath_shorten_last_tl
  \tl_if_eq:NNTF \l__spath_shorten_fa_tl \g__spath_curveto_tl
  {
    \int_set:Nn \l__spath_shorten_int {3}
  }
  {
    \int_set:Nn \l__spath_shorten_int {1}
  }

  \prg_replicate:nn { \l__spath_shorten_int }
  {
    \tl_put_right:Nx \l__spath_shorten_last_tl
    {
      {\tl_head:N \l__spath_shorten_path_tl}
    }
    \tl_set:Nx \l__spath_shorten_path_tl {\tl_tail:N \l__spath_shorten_path_tl}
    \tl_put_right:Nx \l__spath_shorten_last_tl
    {
      {\tl_head:N \l__spath_shorten_path_tl}
    }
    \tl_set:Nx \l__spath_shorten_path_tl {\tl_tail:N \l__spath_shorten_path_tl}
    \tl_put_right:Nx \l__spath_shorten_last_tl
    {
      \tl_head:N \l__spath_shorten_path_tl
    }
    \tl_set:Nx \l__spath_shorten_path_tl {\tl_tail:N \l__spath_shorten_path_tl}
  }

  \tl_put_right:Nx \l__spath_shorten_last_tl
  {
    {\tl_item:Nn \l__spath_shorten_path_tl {1}}
    {\tl_item:Nn \l__spath_shorten_path_tl {2}}
  }
  \tl_put_right:NV \l__spath_shorten_last_tl \g__spath_moveto_tl
  
  \tl_reverse:N \l__spath_shorten_path_tl

  \fp_set:Nn \l__spath_shorten_x_fp
  {
    \dim_to_fp:n {\tl_item:Nn \l__spath_shorten_last_tl {4}}
    -
    \dim_to_fp:n {\tl_item:Nn \l__spath_shorten_last_tl {1}}
  }
  
  \fp_set:Nn \l__spath_shorten_y_fp
  {
    \dim_to_fp:n {\tl_item:Nn \l__spath_shorten_last_tl {5}}
    -
    \dim_to_fp:n {\tl_item:Nn \l__spath_shorten_last_tl {2}}
  }

  \fp_set:Nn \l__spath_shorten_len_fp
  {
    sqrt( \l__spath_shorten_x_fp * \l__spath_shorten_x_fp +  \l__spath_shorten_y_fp *  \l__spath_shorten_y_fp )
  }

  \fp_set:Nn \l__spath_shorten_len_fp
  {
    (\l__spath_shorten_len_fp - #2)/ \l__spath_shorten_len_fp
  }

  \tl_reverse:N \l__spath_shorten_last_tl
  
  \tl_if_eq:NNTF \l__spath_shorten_fa_tl \g__spath_curveto_tl
  {
    \fp_set:Nn \l__spath_shorten_len_fp
    {
      1 - (1 -\l__spath_shorten_len_fp)/3
    }
    \spath_split_curve:VVNN \l__spath_shorten_len_fp \l__spath_shorten_last_tl
    \l__spath_shorten_lasta_tl
    \l__spath_shorten_lastb_tl
  }
  {
    \spath_split_line:VVNN \l__spath_shorten_len_fp \l__spath_shorten_last_tl
    \l__spath_shorten_lasta_tl
    \l__spath_shorten_lastb_tl
  }

  \prg_replicate:nn {3}
  {
    \tl_set:Nx \l__spath_shorten_lasta_tl {\tl_tail:N \l__spath_shorten_lasta_tl}
  }

  \tl_put_right:NV \l__spath_shorten_path_tl \l__spath_shorten_lasta_tl

  \tl_gset_eq:NN \l__spath_smuggle_tl \l__spath_shorten_path_tl
  \group_end:

  \spath_clear:n {#1}
  \spath_put:nnV {#1} {path} \l__spath_smuggle_tl
}

\cs_generate_variant:Nn \spath_shorten:nn {Vn, VV}
\cs_generate_variant:Nn \spath_reverse:n {V}
\cs_generate_variant:Nn \spath_append_no_move:nn {VV}
\cs_generate_variant:Nn \spath_prepend_no_move:nn {VV}

\cs_new_nopar:Npn \spath_intersect:nn #1#2
{
  \spath_get:nnN {#1} {path} \l__spath_tmpa_tl
  \spath_get:nnN {#2} {path} \l__spath_tmpb_tl
  \pgfintersectionofpaths%
  {%
    \pgfsetpath\l__spath_tmpa_tl
  }{%
    \pgfsetpath\l__spath_tmpb_tl
  }
}

\cs_generate_variant:Nn \spath_intersect:nn {VV, Vn}

\cs_new_nopar:Npn \spath_split_line:nnNN #1#2#3#4
{
  \group_begin:
  \tl_gclear:N \l__spath_smuggle_tl
  \tl_set_eq:NN \l__spath_tmpa_tl \g__spath_moveto_tl
  \tl_put_right:Nx \l__spath_tmpa_tl {
    {\tl_item:nn {#2} {2}}
    {\tl_item:nn {#2} {3}}
  }
  \tl_put_right:NV \l__spath_tmpa_tl \g__spath_lineto_tl
  \tl_put_right:Nx \l__spath_tmpa_tl
  {
    {\fp_to_dim:n
    {
      (1 - #1) * \tl_item:nn {#2} {2} + (#1) * \tl_item:nn {#2} {5}
    }}
    {\fp_to_dim:n
    {
      (1 - #1) * \tl_item:nn {#2} {3} + (#1) * \tl_item:nn {#2} {6}
    }}
  }
  \tl_gset_eq:NN \l__spath_smuggle_tl \l__spath_tmpa_tl
  \group_end:
  \tl_set_eq:NN #3 \l__spath_smuggle_tl
  \group_begin:
  \tl_gclear:N \l__spath_smuggle_tl
  \tl_set_eq:NN \l__spath_tmpa_tl \g__spath_moveto_tl
  \tl_put_right:Nx \l__spath_tmpa_tl
  {
    {\fp_to_dim:n
    {
      (1 - #1) * \tl_item:nn {#2} {2} + (#1) * \tl_item:nn {#2} {5}
    }}
    {\fp_to_dim:n
    {
      (1 - #1) * \tl_item:nn {#2} {3} + (#1) * \tl_item:nn {#2} {6}
    }}
  }
  \tl_put_right:NV \l__spath_tmpa_tl \g__spath_lineto_tl
  \tl_put_right:Nx \l__spath_tmpa_tl {
    {\tl_item:nn {#2} {5}}
    {\tl_item:nn {#2} {6}}
  }
  \tl_gset_eq:NN \l__spath_smuggle_tl \l__spath_tmpa_tl
  \group_end:
  \tl_set_eq:NN #4 \l__spath_smuggle_tl
}

\cs_generate_variant:Nn \spath_split_line:nnNN {nVNN, VVNN}

\int_new:N \l__spath_split_int
\int_new:N \l__spath_splitat_int
\fp_new:N \l__spath_split_fp
\bool_new:N \l__spath_split_bool
\tl_new:N \l__spath_split_path_tl
\tl_new:N \l__spath_split_patha_tl
\tl_new:N \l__spath_split_pathb_tl
\tl_new:N \l__spath_split_intoa_tl
\tl_new:N \l__spath_split_intob_tl
\dim_new:N \l__spath_splitx_dim
\dim_new:N \l__spath_splity_dim

\cs_new_nopar:Npn \spath_split_at:nnnn #1#2#3#4
{
  \group_begin:
  \int_set:Nn \l__spath_splitat_int {\fp_to_int:n {floor(#2) + 1}}
  \fp_set:Nn \l__spath_split_fp {#2 - floor(#2)}
  \int_zero:N \l__spath_split_int
  \bool_set_true:N \l__spath_split_bool

  \spath_get:nnN {#1} {path} \l__spath_split_path_tl
  \tl_clear:N \l__spath_split_patha_tl

  \dim_zero:N \l__spath_splitx_dim
  \dim_zero:N \l__spath_splity_dim

  \bool_until_do:nn {
    \tl_if_empty_p:N \l__spath_split_path_tl
    ||
    \int_compare_p:n { \l__spath_splitat_int == \l__spath_split_int  }
  }
  {
    \tl_set:Nx \l__spath_tmpc_tl {\tl_head:N \l__spath_split_path_tl}
    \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
    \tl_case:Nn \l__spath_tmpc_tl
    {
      \g__spath_lineto_tl
      {
        \int_incr:N \l__spath_split_int
      }
      \g__spath_curvetoa_tl
      {
        \int_incr:N \l__spath_split_int
      }
    }
    \int_compare:nT { \l__spath_split_int < \l__spath_splitat_int  }
    {
      \tl_put_right:NV \l__spath_split_patha_tl \l__spath_tmpc_tl
      
      \tl_put_right:Nx \l__spath_split_patha_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \dim_set:Nn \l__spath_splitx_dim {\tl_head:N \l__spath_split_path_tl}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \tl_put_right:Nx \l__spath_split_patha_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \dim_set:Nn \l__spath_splity_dim {\tl_head:N \l__spath_split_path_tl}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
    }
  }

  \tl_clear:N \l__spath_split_pathb_tl
  \tl_put_right:NV \l__spath_split_pathb_tl \g__spath_moveto_tl
  \tl_put_right:Nx \l__spath_split_pathb_tl
  {
    {\dim_use:N \l__spath_splitx_dim}
    {\dim_use:N \l__spath_splity_dim}
  }
  \tl_case:Nn \l__spath_tmpc_tl
  {
    \g__spath_lineto_tl
    {
      \tl_put_right:NV \l__spath_split_pathb_tl \l__spath_tmpc_tl
      \tl_put_right:Nx \l__spath_split_pathb_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \tl_put_right:Nx \l__spath_split_pathb_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \spath_split_line:VVNN \l__spath_split_fp \l__spath_split_pathb_tl
      \l__spath_split_intoa_tl
      \l__spath_split_intob_tl

      \prg_replicate:nn {3} {
        \tl_set:Nx \l__spath_split_intoa_tl {\tl_tail:N \l__spath_split_intoa_tl}
      }

      \tl_put_right:NV \l__spath_split_patha_tl \l__spath_split_intoa_tl
      \tl_put_right:NV \l__spath_split_intob_tl \l__spath_split_path_tl
    }
    \g__spath_curvetoa_tl
    {
      \tl_put_right:NV \l__spath_split_pathb_tl \l__spath_tmpc_tl
      \tl_put_right:Nx \l__spath_split_pathb_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \tl_put_right:Nx \l__spath_split_pathb_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \prg_replicate:nn {2} {
        
        \tl_put_right:Nx \l__spath_split_pathb_tl
        { \tl_head:N \l__spath_split_path_tl }
        \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
        
        \tl_put_right:Nx \l__spath_split_pathb_tl
        {{ \tl_head:N \l__spath_split_path_tl }}
        \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
        \tl_put_right:Nx \l__spath_split_pathb_tl
        {{ \tl_head:N \l__spath_split_path_tl }}
        \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      }

      \spath_split_curve:VVNN \l__spath_split_fp \l__spath_split_pathb_tl
      \l__spath_split_intoa_tl
      \l__spath_split_intob_tl

      \prg_replicate:nn {3} {
        \tl_set:Nx \l__spath_split_intoa_tl {\tl_tail:N \l__spath_split_intoa_tl}
      }

      \tl_put_right:NV \l__spath_split_patha_tl \l__spath_split_intoa_tl
      \tl_put_right:NV \l__spath_split_intob_tl \l__spath_split_path_tl
    }
  }

  \spath_gclear_new:n {#3}
  \spath_gput:nnV {#3} {path} \l__spath_split_patha_tl
  \spath_gclear_new:n {#4}
  \spath_gput:nnV {#4} {path} \l__spath_split_intob_tl
  \group_end:
}

\cs_generate_variant:Nn \spath_split_at:nnnn {VVnn, Vnnn}

\cs_new_nopar:Npn \spath_explode_into_list:nn #1#2
{
  \tl_clear_new:c {l__spath_list_#2}

  \int_zero:N \l__spath_tmpa_int
  \spath_map_segment_inline:nn {#1} {
    \tl_if_eq:NNF ##1 \g__spath_moveto_tl
    {
      \spath_clear_new:n {#2 _ \int_use:N \l__spath_tmpa_int}
      \spath_put:nnV  {#2 _ \int_use:N \l__spath_tmpa_int} {path} ##2
      \tl_put_right:cx {l__spath_list_#2} {{#2 _ \int_use:N \l__spath_tmpa_int}}
      \int_incr:N \l__spath_tmpa_int
    }
  }
}

\tl_new:N \spathselfintersectioncount

\tl_new:N \l__spath_split_tmpa_tl
\tl_new:N \l__spath_split_path_a_tl
\tl_new:N \l__spath_split_path_b_tl
\tl_new:N \l__spath_split_join_a_tl
\tl_new:N \l__spath_split_join_b_tl
\tl_new:N \l__spath_split_first_tl
\tl_new:N \l__spath_split_second_tl

\tl_new:N \l__spath_split_one_tl
\tl_set:Nn \l__spath_split_one_tl {1}
\tl_new:N \l__spath_split_I_tl
\tl_set:Nn \l__spath_split_I_tl {I}

\int_new:N \l__spath_split_count_int
\int_new:N \l__spath_split_intersection_int
\seq_new:N \l__spath_split_segments_seq
\seq_new:N \l__spath_split_segments_processed_seq
\seq_new:N \l__spath_split_segments_middle_seq

\seq_new:N \l__spath_split_joins_seq
\seq_new:N \l__spath_split_joins_processed_seq
\seq_new:N \l__spath_split_joins_middle_seq

\seq_new:N \l__spath_split_intersections_seq

\bool_new:N \l__spath_split_join_bool

% We'll run this on each segment
%
% Arguments:
%  1. Path to split
%  2. Prefix for name of new paths
%  3. List of how to split at intersections
%     A - don't split first path at intersection
%     B - don't split second path at intersection
%     C - split both paths at intersection
%
\cs_new_nopar:Npn \spath_split_at_self_intersections:nnn #1#2#3
{
  \group_begin:
  % The third argument says whether to rejoin segments at the intersections
  \seq_set_split:Nnn \l__spath_split_intersections_seq {} {#3}
  % Clone the path as we'll mess around with it
  \spath_clone:nn {#1} {spath split tmp}
  % Clear the sequence of joining information
  % The join information says whether to rejoin a segment to its predecessor
  \seq_clear:N \l__spath_split_joins_seq
  % Check the last action to see if it is a close path
  \spath_get:nnN {spath split tmp} {final action} \l__spath_split_tmpa_tl
  \tl_if_eq:NNTF \l__spath_split_tmpa_tl \g__spath_closepath_tl
  {
    % Last action is a close, so mark it as needing rejoining
    \seq_put_right:Nn \l__spath_split_joins_seq {1}
  }
  {
    % Last action is not a close, so mark it as needing rejoining
    \seq_put_right:Nn \l__spath_split_joins_seq {0}
  }
  % Remove close paths
  \spath_open_path:n {spath split tmp}
  % Separate into segments (creates a token list)
  \spath_explode_into_list:nn {spath split tmp}{split segments}
  % so convert to a sequence
  \seq_set_split:NnV \l__spath_split_segments_seq {} \l__spath_list_splitsegments

  % Iterate over the number of terms in the sequence, adding the
  % rejoining information
  \int_step_inline:nnnn {1} {1} {\seq_count:N \l__spath_split_segments_seq - 1}
  {
    \seq_put_right:Nn \l__spath_split_joins_seq {1}
  }
  
  % Clear a couple of auxiliaries
  \seq_clear:N \l__spath_split_segments_processed_seq
  \seq_clear:N \l__spath_split_joins_processed_seq
  \int_zero:N \l__spath_split_count_int
  \int_zero:N \l__spath_split_intersection_int

  % Iterate over the sequence
  \bool_while_do:nn
  {
    !\seq_if_empty_p:N \l__spath_split_segments_seq
  }
  {
    % Remove the left-most items for consideration
    \seq_pop_left:NN \l__spath_split_segments_seq \l__spath_split_path_a_tl
    \seq_pop_left:NN \l__spath_split_joins_seq \l__spath_split_join_a_tl

    % Clear some sequences, these will hold any pieces we create from splitting our path under consideration except for the first piece
    \seq_clear:N \l__spath_split_segments_middle_seq
    \seq_clear:N \l__spath_split_joins_middle_seq

    % Put the rejoining information in the processed sequence
    \seq_put_right:NV \l__spath_split_joins_processed_seq \l__spath_split_join_a_tl
    
    % Iterate over the rest of the segments
    \int_step_inline:nnnn {1} {1} {\seq_count:N \l__spath_split_segments_seq}
    {
      % Store the next segment for intersection
      \tl_set:Nx \l__spath_split_path_b_tl {\seq_item:Nn \l__spath_split_segments_seq {##1}}
      % Get the next joining information
      \tl_set:Nx \l__spath_split_join_b_tl {\seq_item:Nn \l__spath_split_joins_seq {##1}}
      % And put it onto our saved stack of joins
      \seq_put_right:NV \l__spath_split_joins_middle_seq \l__spath_split_join_b_tl
      
      % Sort intersections along the first path
      \pgfintersectionsortbyfirstpath
      % Find the intersections of these segments
      \spath_intersect:VV \l__spath_split_path_a_tl \l__spath_split_path_b_tl

      % If we get intersections
      \int_compare:nTF {\pgfintersectionsolutions > 0}
      {
        % Find the times of the first intersection (which will be the first along the segment we're focussing on)
        \pgfintersectiongetsolutiontimes{1}{\l__spath_split_first_tl}{\l__spath_split_second_tl}

        % Ignore intersections that are very near end points
        \bool_if:nT {
          \fp_compare_p:n {
            \l__spath_split_first_tl < .99
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_first_tl > .01
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_second_tl < .99
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_second_tl > .01
          }
        }
        {
          % We have a genuine intersection
          \int_incr:N \l__spath_split_intersection_int
        }

        % Do we split the first path?
        \bool_if:nT {
          \fp_compare_p:n {
            \l__spath_split_first_tl < .99
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_first_tl > .01
          }
        }
        {
          % Split the first path at the intersection
          \spath_split_at:VVnn \l__spath_split_path_a_tl \l__spath_split_first_tl {split \int_use:N \l__spath_split_count_int}{split \int_eval:n { \l__spath_split_count_int + 1}}

          % Put the latter part into our temporary sequence
          \seq_put_left:Nx \l__spath_split_segments_middle_seq {split \int_eval:n{ \l__spath_split_count_int + 1}}
          % Mark this intersection in the joining information
          % Label the breaks as "IA#" and "IB#"
          \seq_put_left:Nx \l__spath_split_joins_middle_seq {IA \int_use:N  \l__spath_split_intersection_int }
          
          % Replace our segment under consideration by the initial part
          \tl_set:Nx \l__spath_split_path_a_tl {split \int_use:N \l__spath_split_count_int }
          % Increment our counter
          \int_incr:N \l__spath_split_count_int
          \int_incr:N \l__spath_split_count_int
        }

        % Do we split the second path?
        \bool_if:nTF {
          \fp_compare_p:n {
            \l__spath_split_second_tl < .99
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_second_tl > .01
          }
        }
        {
          % Split the second segment at the intersection point
          \spath_split_at:VVnn \l__spath_split_path_b_tl \l__spath_split_second_tl {split \int_use:N \l__spath_split_count_int}{split \int_eval:n { \l__spath_split_count_int + 1}}

          % Add these segments to our list of segments we've considered
          \seq_put_right:Nx \l__spath_split_segments_middle_seq {split \int_eval:n{ \l__spath_split_count_int}}
          \seq_put_right:Nx \l__spath_split_segments_middle_seq {split \int_eval:n{ \l__spath_split_count_int + 1}}
          \seq_put_right:Nx \l__spath_split_joins_middle_seq {IB \int_use:N \l__spath_split_intersection_int}
          
          % Increment the counter
          \int_incr:N \l__spath_split_count_int
          \int_incr:N \l__spath_split_count_int
        }
        {
          % If we didn't split the second segment, we just put the second segment on the list of segments we've considered
          \seq_put_right:NV \l__spath_split_segments_middle_seq \l__spath_split_path_b_tl
        }

      }
      {
        % If we didn't split the second segment, we just put the second segment on the list of segments we've considered
        \seq_put_right:NV \l__spath_split_segments_middle_seq \l__spath_split_path_b_tl
      }

    }
    % Having been through the loop for our segment under consideration, we replace the segment list since some of them might have been split and add any remainders of the segment under consideration
    \seq_set_eq:NN \l__spath_split_segments_seq \l__spath_split_segments_middle_seq
    \seq_set_eq:NN \l__spath_split_joins_seq \l__spath_split_joins_middle_seq

    % We add the initial segment to our sequence of dealt with segments
    \seq_put_right:NV \l__spath_split_segments_processed_seq \l__spath_split_path_a_tl
  }

  \seq_clear:N \l__spath_split_segments_seq
  
  \tl_set:Nx \l__spath_split_path_a_tl {\seq_item:Nn \l__spath_split_segments_processed_seq {1}}
  
  \int_step_inline:nnnn {2} {1} {\seq_count:N \l__spath_split_segments_processed_seq}
  {
    % Get the next path and joining information
    \tl_set:Nx \l__spath_split_path_b_tl {\seq_item:Nn \l__spath_split_segments_processed_seq {##1}}
    \tl_set:Nx \l__spath_split_join_b_tl {\seq_item:Nn \l__spath_split_joins_processed_seq {##1}}

    % Do we join this to our previous path?
    \bool_set_false:N \l__spath_split_join_bool

    % If it came from when we split the original path, join them
    \tl_if_eq:NNT \l__spath_split_join_b_tl \l__spath_split_one_tl
    {
      \bool_set_true:N \l__spath_split_join_bool
    }

    % Is this a labelled intersection?
    \tl_set:Nx \l__spath_split_tmpa_tl {\tl_head:N \l__spath_split_join_b_tl}
    \tl_if_eq:NNT \l__spath_split_tmpa_tl \l__spath_split_I_tl
    {
      % Strip off the "I" prefix
      \tl_set:Nx \l__spath_split_tmpa_tl {\tl_tail:N \l__spath_split_join_b_tl}

      % Next letter is "A" or "B"
      \tl_set:Nx \l__spath_split_join_b_tl {\tl_head:N \l__spath_split_tmpa_tl}

      % Remainder is the intersection index
      \int_compare:nTF {\tl_tail:N \l__spath_split_tmpa_tl <= \seq_count:N \l__spath_split_intersections_seq}
      {
        \tl_set:Nx \l__spath_split_join_a_tl {\seq_item:Nn \l__spath_split_intersections_seq {\tl_tail:N \l__spath_split_tmpa_tl}}
      }
      {
        % Default is to rejoin neither segment
        \tl_set:Nn \l__spath_split_join_a_tl {C}
      }

      \tl_if_eq:NNT \l__spath_split_join_a_tl \l__spath_split_join_b_tl
      {
        \bool_set_true:N \l__spath_split_join_bool
      }
      
    }

    \bool_if:NTF \l__spath_split_join_bool
    {
      % Yes, so append it
      \spath_append_no_move:VV \l__spath_split_path_a_tl \l__spath_split_path_b_tl
    }
    {
      % No, so put the first path onto the stack
      \seq_put_right:NV \l__spath_split_segments_seq \l__spath_split_path_a_tl

      % Swap out the paths
      \tl_set_eq:NN \l__spath_split_path_a_tl \l__spath_split_path_b_tl
    }
  }

  % Do we need to add the first path to the last?
  \tl_set:Nx \l__spath_split_join_a_tl {\seq_item:Nn \l__spath_split_joins_processed_seq {1}}

  \tl_if_eq:NNTF \l__spath_split_join_a_tl \l__spath_split_one_tl
  {
    \tl_set:Nx \l__spath_split_path_b_tl {\seq_item:Nn \l__spath_split_segments_processed_seq {1}}
    \spath_prepend_no_move:VV \l__spath_split_path_b_tl \l__spath_split_path_a_tl
    
  }
  {
    \seq_put_right:NV \l__spath_split_segments_seq \l__spath_split_path_a_tl
  }

  % Put our paths into a list
  \int_zero:N \l__spath_split_count_int
  \seq_map_inline:Nn \l__spath_split_segments_seq
  {
    \int_incr:N \l__spath_split_count_int
    \spath_gclone:nn {##1} {#2~\int_use:N \l__spath_split_count_int}
  }
  \tl_gset:NV \spathselfintersectioncount \l__spath_split_count_int
  \group_end:
}

\ExplSyntaxOff

\begin{document}

\begin{tikzpicture}[use Hobby shortcut]

\shade[left color=cyan, right color=magenta, shading angle=90] (-.5,-.2) rectangle (7.5,2.2);
\fill[pattern=bricks, pattern color=white] (-.5,-.2) rectangle (7.5,2.2);

\path
[
split at self intersections={coil}{AAAAAAAAAAAAAAAA}
] ([out angle=0]0,0)
.. +(.85,1) .. +(.25,2) .. +(-.35,1) .. ++(.5,0)
.. +(.85,1) .. +(.25,2) .. +(-.35,1) .. ++(.5,0)
.. +(.85,1) .. +(.25,2) .. +(-.35,1) .. ++([in angle=180].5,0)
;

\foreach \k in {1,..., \spathselfintersectioncount} {
  \tikzset{shorten spath both ends={coil \k}{2pt}, globalise spath=coil \k}
}

\foreach \k in {1,..., 4} {
  \draw[set spath=coil \k];
}

\foreach[evaluate=\l as \xshift using \l*.5cm] \l in {0,...,10} {
  \foreach \k in {5,..., 9} {
    \draw[translate spath={coil \k}{\xshift pt}{0pt},set spath=coil \k];
  }
}

\draw[translate spath={coil 10}{5cm}{0pt},set spath=coil 10];

\end{tikzpicture}
\end{document}

显然,其中的绝大部分最终都会进入包装中spath3,而关键部分在tikzpicture最后。它的作用是获取基本路径并在其自相交处将其分割。然后缩短这些部分以创建间隙。然后可以重复使用这些部分(通过平移)来创建线圈。结果是下图,背景显示double这里没有任何花招。

线圈在交叉处分裂

相关内容