Decorate path precisely on intersection(s) with TikZ - avoiding crossing lines

Here's a solution. It's fully automatic and should well even for curved lines. The notation is avoid intersection={name of other path}{drawing options}. It finds the list of intersection points, then executes a decoration that measures the distance to the next intersection point and when gets close, does an evasive maneuver. Using the options avoid intersect amplitude, avoid intersect width, and avoid intersect offset you can adjust the evasive maneuver.

It's not perfect. It is very slow (on my computer, the two examples take 1.4 seconds and 3.8 seconds respectively), which may be unavoidable. Also, if you include the smooth option in the second example below it breaks with a "dimension too large" error. I have no idea why. I think the output looks very good.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations}
\usetikzlibrary{intersections}

\makeatletter
\tikzset{
  avoid intersection amplitude/.store in=\avint@amplitude,
  avoid intersection width/.code={\edef\avint@width{\dimexpr#1/2}},
  avoid intersection offset/.store in=\avint@offset,
  avoid intersection has corners/.code={\pgfdecoratepathhascornerstrue}
}
\def\avint@amplitude{5pt}
\def\avint@width{5pt}
\def\avint@offset{0pt}

\pgfdeclaredecoration{avoidintersect}{initial}{
  \state{initial}[width=\pgfdecoratedinputsegmentlength/100,next state=measure]
  {
    \gdef\avint@intersectionnumber{1}
    \pgfpathlineto{\pgfpointorigin}
  }

  \state{measure}[width=\pgfdecoratedinputsegmentlength/100,next state=wait,auto corner on length=2pt,
               persistent postcomputation=\let\pgf@decorate@next@state\avint@smuggle@pgf@decorate@next@state]
  {
    \pgfpathlineto{\pgfpointorigin}
    \pgfgettransform\avint@temptransform
    \pgftransforminvert
    \pgfpointintersectionsolution{\avint@intersectionnumber}
    \pgf@pos@transform{\pgf@x}{\pgf@y}
    \pgfmathveclen{\pgf@x}{\pgf@y}
    \pgfsettransform\avint@temptransform
    \xdef\avint@waitcycles{\the\numexpr\dimexpr\pgfmathresult pt-\avint@width*3-\avint@offset\relax/\dimexpr\pgfdecoratedinputsegmentlength/100\relax}
    \ifnum\avint@waitcycles>50\relax\gdef\avint@waitcycles{50}\fi
    \global\let\avint@smuggle@pgf@decorate@next@state\pgf@decorate@next@state        
    \ifdim\pgfmathresult pt<\dimexpr\avint@width+\avint@offset\relax
        \gdef\avint@smuggle@pgf@decorate@next@state{zig}
    \else
        \ifdim\pgfmathresult pt<\dimexpr\avint@width*2+\avint@offset\relax
            \gdef\avint@smuggle@pgf@decorate@next@state{measure}    
        \fi
    \fi        
  }

  \state{wait}[width=\pgfdecoratedinputsegmentlength/100,next state=measure,repeat state=\avint@waitcycles,auto corner on length=2pt]{
    \pgfpathlineto{\pgfpointorigin}
  } 

  \state{zig}[width=\avint@width, next state=zag]{
    \pgfpathcurveto{\pgfqpoint{\avint@width}{0cm}}{\pgfqpoint{0pt}{\avint@amplitude}}{\pgfqpoint{\avint@width}{\avint@amplitude}}
  }
  \state{zag}[width=\avint@width, next state=measure,
              persistent postcomputation=\let\pgf@decorate@next@state\avint@smuggle@pgf@decorate@next@state]{
    \pgfpathcurveto{\pgfqpoint{\avint@width}{\avint@amplitude}}{\pgfqpoint{0pt}{0cm}}{\pgfqpoint{\avint@width}{0pt}}
    \xdef\avint@intersectionnumber{\the\numexpr\avint@intersectionnumber+1}
    \global\let\avint@smuggle@pgf@decorate@next@state\pgf@decorate@next@state
    \ifnum\avint@intersectionnumber>\pgfintersectionsolutions
      \gdef\avint@smuggle@pgf@decorate@next@state{done}
    \fi
  }

  \state{done}[width=\pgfdecoratedinputsegmentlength/100,auto corner on length=2pt]{
    \pgfpathlineto{\pgfpointorigin}
  }

  \state{final}{\pgfpathlineto{\pgfpointdecoratedpathlast}}
}    

\tikzset{
    avoid intersection/.code 2 args={
        \pgfkeysalso{name path=avint@temp,draw=none}
        \expandafter\def\expandafter\tikz@postactions\expandafter{\tikz@postactions
            \pgfintersectionsortbyfirstpath
            \tikz@intersect@namedpaths
            \pgfintersectionofpaths
                {\pgfsetpath\tikz@intersect@path@name@avint@temp}
                {\expandafter\pgfsetpath\csname tikz@intersect@path@name@#1\endcsname}
            \begin{pgfdecoration}{{avoidintersect}{\pgfdecoratedpathlength}}
            \pgfsetpath\tikz@intersect@path@name@avint@temp
            \end{pgfdecoration}
            \pgfgetpath\avint@temp@path
            \pgfusepath{discard}
            \draw[/utils/exec={\tikz@addmode{\pgfsyssoftpath@setcurrentpath\avint@temp@path}},#2](0,0)rectangle (0,0);%
        }
    }
}
\makeatother

\begin{document}
\begin{tikzpicture}
    \coordinate (a) at (-1,0.5);
    \coordinate (b) at (8,0.5);
    \coordinate (c) at (3,-0.5);
    \draw[ultra thick, name path=sine, domain=-1:8, smooth, samples=50] plot (\x,{sin(\x r)});
    \draw[avoid intersection={sine}{white,double=black},avoid intersection has corners] (a) -- (c) |- (b);
\end{tikzpicture}

\begin{tikzpicture}
    \coordinate (a) at (-1,0.5);
    \coordinate (b) at (8,0.5);
    \coordinate (c) at (3,-0.5);
    \draw[ultra thick,name path=line] (a) -- (c) |- (b);
    \draw[avoid intersection={line}{white,double=black},avoid intersection offset=1.5pt,domain=-1:8,samples=50] plot (\x,{sin(\x r)});
\end{tikzpicture}

\end{document}

Here's the output:

You can see that the line join works fine: enter image description here


Here's a technique using the spath3 library. It works as follows:

  1. Split the over path where it intersects the under path.
  2. Insert gaps in the over path at these points.
  3. Splice in an arc into these gaps (joining it to the existing path so the joins are seamless).
  4. Split the under path where it intersects with the new over path.
  5. Insert small gaps in the under path at these points.

The development version (on github -- soon to be on CTAN) contains a version of the splicing code that ensures that the arc is always "upright". This will be on CTAN fairly soon.

Here's the result:

Bridges over sine waves

\documentclass{article}
%\url{https://tex.stackexchange.com/q/334483/86}
\usepackage{tikz}
\usetikzlibrary{spath3,intersections}


\begin{document}
\begin{tikzpicture}
\coordinate (a) at (-1,0.5);
\coordinate (b) at (8,0.5);
\coordinate (c) at (3,-0.5);
\path[
  ultra thick,
  spath/save=sine,
  domain=-1:8,
  smooth,
  samples=50
] plot (\x,{sin(\x r)});
\path[spath/save=over] (a) -- (c) |- (b);

\path[spath/save=arc] (0,0) arc[radius=1cm, start angle=180, delta angle=-180];

\tikzset{
  spath/split at intersections with={over}{sine},
  spath/insert gaps after components={over}{8pt},
  spath/join components with={over}{arc},
  spath/split at intersections with={sine}{over},
  spath/insert gaps after components={sine}{4pt},
}

\draw[spath/use=sine];
\draw[spath/use=over];
\end{tikzpicture}
\end{document}