Making custom Koch decoration with angle parameter in Tikz

The manual (in the Basic Layer chapter) describes the basics of creating decorations, but there are still some tricks than can be used to make things a bit more efficient:

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{decorations}
\pgfkeys{/pgf/decoration/.cd,
  Koch angle/.store in=\pgfkochangle, Koch angle=85
}
\pgfdeclaredecoration{Koch}{calculate}{
\state{calculate}[width=0pt, next state=draw, persistent precomputation={
  % Exploit the fact that all segment lengths should be the same.
  \pgfmathparse{\pgfdecoratedinputsegmentlength/(2*(1+cos(\pgfkochangle)))}%
  \let\pgfkochsegmentlength=\pgfmathresult%
  \pgfmathparse{\pgfkochsegmentlength*sin(\pgfkochangle)}%
  \let\pgfkochy=\pgfmathresult%
  \pgfmathparse{\pgfkochsegmentlength*(1 + cos(\pgfkochangle))}%
  \let\pgfkochxa=\pgfmathresult%
  \pgfmathparse{\pgfkochsegmentlength*(1 + 2*cos(\pgfkochangle))}%
  \let\pgfkochxb=\pgfmathresult%
}]{}
\state{draw}[width=\pgfdecoratedinputsegmentlength]{
    \pgfpathmoveto{\pgfpointorigin}%
    \pgfpathlineto{\pgfqpoint{\pgfkochsegmentlength pt}{0pt}}%
    \pgfpathlineto{\pgfqpoint{\pgfkochxa pt}{\pgfkochy pt}}%
    \pgfpathlineto{\pgfqpoint{\pgfkochxb pt}{0pt}}%
    \pgfpathlineto{\pgfqpoint{\pgfdecoratedinputsegmentlength}{0pt}}%
}}
\begin{document}
\begin{tikzpicture}
\foreach \a [count=\i] in {60, 72, 85}
  \draw [decoration={Koch, Koch angle=\a}] 
    decorate {decorate {decorate {decorate { decorate {(0,\i*4) -- ++(10,0) }}}}};
\end{tikzpicture}
\end{document}

enter image description here

Although the use of a global counter is not ideal, an order parameter can be implemented to remove the need to have multiple decorate commands:

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{decorations}
\newcount\pgfdecorationorder
\pgfkeys{/pgf/decoration/.cd,
  Koch angle/.store in=\pgfkochangle, Koch angle=85,
  Koch order/.code={\global\pgfdecorationorder=#1}, Koch order=1
}
\pgfdeclaredecoration{Koch}{calculate}{
\state{calculate}[width=0pt, next state=draw, persistent precomputation={
  % Exploit the fact that all segment lengths should be the same.
  \pgfmathparse{\pgfdecoratedinputsegmentlength/(2*(1+cos(\pgfkochangle)))}%
  \let\pgfkochsegmentlength=\pgfmathresult%
  \pgfmathparse{\pgfkochsegmentlength*sin(\pgfkochangle)}%
  \let\pgfkochy=\pgfmathresult%
  \pgfmathparse{\pgfkochsegmentlength*(1 + cos(\pgfkochangle))}%
  \let\pgfkochxa=\pgfmathresult%
  \pgfmathparse{\pgfkochsegmentlength*(1 + 2*cos(\pgfkochangle))}%
  \let\pgfkochxb=\pgfmathresult%
}]{}
\state{draw}[width=\pgfdecoratedinputsegmentlength]{
    \pgfpathmoveto{\pgfpointorigin}%
    \pgfpathlineto{\pgfqpoint{\pgfkochsegmentlength pt}{0pt}}%
    \pgfpathlineto{\pgfqpoint{\pgfkochxa pt}{\pgfkochy pt}}%
    \pgfpathlineto{\pgfqpoint{\pgfkochxb pt}{0pt}}%
    \pgfpathlineto{\pgfqpoint{\pgfdecoratedinputsegmentlength}{0pt}}%
}
\state{final}{
  \global\advance\pgfdecorationorder by -1\relax%
  \ifnum\pgfdecorationorder>0\relax%
    \pgfgetpath\decoratedpath%
    \pgfsetpath\empty%
    \begin{pgfdecoration}{{Koch}{\pgfdecoratedpathlength}}%
      \pgfsetpath\decoratedpath
    \end{pgfdecoration}%
  \fi
}}
\begin{document}
\begin{tikzpicture}
\foreach \i in {1,...,6}
  \draw [decoration={Koch, Koch angle=85, Koch order=\i}] 
    decorate  {(0,\i*5) -- ++(10,0)};
\end{tikzpicture}
\end{document}

enter image description here


Here is a simple solution using the lindenmayer library:

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{lindenmayersystems}
\pgfdeclarelindenmayersystem{A}{\rule{F-> F+F--F+F}}
\begin{document}
\begin{tikzpicture}
  \draw[blue,line cap=round]
  [lindenmayer system={A,axiom=F,order=7,angle=80,step=1mm}]
  lindenmayer system;
\end{tikzpicture}
\end{document}

When angle is 80, the result is correct (order=7):

enter image description here

When the angle is 85, the computational errors accumulate and are clearly visible when there are many iterations (order=6).

enter image description here


I like making fractals with MetaPost, so I couldn't resist programming this one, using a recursive macro, Koch(expr A, B, angl, n), with the extremities of the initial segment, the angle and the required recursivity order as parameters.

Here are the results with the angle parameter set to 60, 72 and 85 (from bottom to top), at order 5, as in Mark Wibrow's example:

vardef Koch(expr A, B, angl, n) =
    if n = 0: 
        draw A -- B;
    else:
        save b, v, C, D, E; pair v, C, D, E;
        v = unitvector(B-A);
        2b * (1 + cosd angl) = abs(B-A);
        C = A + b*v;
        D = .5[A,B] + b * sind angl * v rotated 90;
        E = B - b*v;
        Koch(A, C, angl, n-1); Koch(C, D, angl, n-1); 
        Koch(D, E, angl, n-1); Koch(E, B, angl, n-1); 
    fi
enddef;
beginfig(1);
    k := 0;
    for angl = 60, 72, 85:
        draw image(Koch(origin, (10cm, 0), angl, 5)) shifted (0, k*4cm);
        k := k+1;
    endfor;
endfig;
end.

enter image description here

For the fun, here is the result at order 8 with the angle set to 85 (can take some time):

enter image description here