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}
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}
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):
When the angle is 85, the computational errors accumulate and are clearly visible when there are many iterations (order=6).
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.
For the fun, here is the result at order 8 with the angle set to 85 (can take some time):