TikZ-PGF Path Clipping – How to Reverse Clip on Custom Path Defined by Ellipse Intersections

path-clippingtikz-pgf

Suppose I have two ellipses with known locations, major and minor axes, and rotation angles. What I would like to do is define a custom path that primarily uses one ellipse but follows the other at their intersection. A picture is helpful here:

custom path for two ellipses

So I want my custom path to start at A, follow the black ellipse clockwise to B, then follow the pink ellipse back to A. I know I'll need to use TikZ to compute the intersections for me automatically (I have some experience with this). As for creating the path itself, this Batman post indicates I'd have to use \pgfpatharcto commands: Using TikZ, how to draw an elliptical arc starting from point A to point B with the origin as its center where both radii are given?

Once I get this path, I want to be able to use it for reverse clipping, as described here: How can I invert a 'clip' selection within TikZ?. I also have experience doing this, but I don't understand how I would use it with the lower level pgf commands used to define the path.

This isn't the exact problem I am working on. Generally, I will have a number of shapes and I want to use clipping and reverse clipping to draw isolated regions. So this question really boils down to: how do I create a custom path that can be used with reverse clipping?

Best Answer

well, this will not be a short answer. In fact, it will be a very long answer, but will solve the problems of this type in a generic way. Here are some examples of what you can do with this proposal.

Exemplo 1 Exemplo 2

Libraries and packages

We used a feature of tikz called decorations.markings to recreate the path with nodes. Below is the statement:

\tikzset{
    split/.style = {
        , name path = #1
        , /utils/exec={\setcounter{cnt}{0}}
        , postaction = {
            , decorate
            , decoration={
                markings
                , mark = between positions 0 and 1 step 2pt with {
                    \node [
                        circle
                        , minimum size = 0pt
                        , inner sep = .2pt
                        , /utils/exec={\stepcounter{cnt} \setpointsof{#1}{\thecnt}}
                    ] (#1-\thecnt) {};
                }
            }
        }
    }
}

We also use the library intersections to find the points of intersection between paths.

Furthermore, we use the package etoolbox that provides the means to store values ​​in a very specific and also features flow control (if, while, etc.).

Macros

First I will explain the macros.

The next two macros store and retrieve the number of points of intersection between two paths.

% #1: path 1
% #2: path 2
% #3: intersect points
\newcommand{\setintersectpointsof}[3]{\csxdef{intersectpointsof#1and#2}{#3}}

% #1: path 1
% #2: path 2
\newcommand{\getintersectpointsof}[2]{\csuse{intersectpointsof#1and#2}}

The next two macros store and retrieve the number of points of a path.

% #1: path
\newcommand{\pointsof}[1]{\csuse{pointsof#1}}

% #1: path
% #2: intersect points
\newcommand{\setpointsof}[2]{\csnumgdef{pointsof#1}{#2}}

The next two macros store and retrieve values ​​related to indexed variables. Something close to the vectors in programming.

% #1: name of path
% #2: name of the paths
% #3: index of the data
% #4: data
\newcommand\setstorageof[4]{\csnumgdef{#1in#2#3}{#4}}

% #1: name of the path
% #1: name of the paths
% #2: index of the data
\newcommand\getstorageof[3]{\csuse{#1in#2#3}}

The next macro identifies the intersection points in each path. Therefore, it is also used a margin of error for fine tuning.

% #1: first path
% #2: second path
% #3: margin of error
\newcommand{\getintersectionsof}[3]{

    \path [
        name intersections = {of = #1 and #2, name = j, total=\t, sort by = #1}
        , /utils/exec = {\xdef\total{\t}}
    ];

    \setintersectpointsof{#1}{#2}{\total}
    \pgfmathsetlengthmacro{\marginoferror}{#3}

    \foreach \q in {1, ..., \getintersectpointsof{#1}{#2}} {
        \pgfextractx{\xref}{\pgfpointanchor{j-\q}{center}}
        \pgfextracty{\yref}{\pgfpointanchor{j-\q}{center}}

        \foreach \p in {#1, #2} {

            \foreach \r in {1, ..., \pointsof{\p}}{
                \pgfextractx{\xone}{\pgfpointanchor{\p-\r}{center}}
                \pgfextracty{\yone}{\pgfpointanchor{\p-\r}{center}}

                \ifboolexpr{
                    test {\ifdimless{\xref - \marginoferror}{\xone}} and test {\ifdimless{\xone}{\xref + \marginoferror}}
                    and
                    test {\ifdimless{\yref - \marginoferror}{\yone}} and test {\ifdimless{\yone}{\yref + \marginoferror}}
                }{
                    \setstorageof{\p}{#1#2}{\q}{\r}
                }{
                }
            }
        }
    }
}

The next two macros build the paths taken between intersections. Can be made directly or reverse with respect to the recreation paths made by the markings.

% #1: working path
% #2: first element
% #3: last element
% #4: resulting path
% #5: next position of resulting path
\newcommand{\constructreversepath}[5]{
    \pgfmathtruncatemacro{\k}{#2}   
    \csedef{#4}{};
    \unlessboolexpr{test{\ifnumequal{\k}{#3}}}{
        \stepcounter{innercnt}
        \node (#4-\theinnercnt) at (#1-\k.center) {};
        \csxdef{#4}{\csuse{#4} (#4-\theinnercnt)};
        \pgfmathtruncatemacro{\k}{\k - 1}
        \ifnumless{\k}{1}{
            \pgfmathtruncatemacro{\k}{\pointsof{#1} - \k}
        }{
            \ifnumgreater{\k}{\pointsof{#1}}{
                \pgfmathtruncatemacro{\k}{\k - \pointsof{#1}}
            }{
            }
        }
    }
    \stepcounter{innercnt}
    \node (#4-\theinnercnt) at (#1-\k.center) {};
    \csedef{#4}{\csuse{#4} (#4-\theinnercnt)};
}

% #1: working path
% #2: first element
% #3: last element
% #4: resulting path
% #5: next position of resulting path
\newcommand{\constructdirectpath}[5]{
    \pgfmathtruncatemacro{\k}{#2}
    \csedef{#4}{};
    \unlessboolexpr{test{\ifnumequal{\k}{#3}}}{
        \stepcounter{innercnt}
        \node (#4-\theinnercnt) at (#1-\k.center) {};
        \csxdef{#4}{\csuse{#4} (#4-\theinnercnt)};
        \pgfmathtruncatemacro{\k}{\k + 1}
        \ifnumless{\k}{1}{
            \pgfmathtruncatemacro{\k}{\pointsof{#1} - \i}
        }{
            \ifnumgreater{\k}{\pointsof{#1}}{
                \pgfmathtruncatemacro{\k}{\k - \pointsof{#1}}
            }{
            }
        }
    }
    \stepcounter{innercnt}
    \node (#4-\theinnercnt) at (#1-\k.center){};
    \csedef{#4}{\csuse{#4} (#4-\theinnercnt)};
}

And the next macro creates macros that store the full path between two points of intersection using the two previous macros according to the input option (direct or reverse).

% #1: first path 
% #2: direct or reverse first path
% #3: second path
% #4: direct or reverse second path
% #5: index of first point
% #6: index of second point
% #7: name of resulting path
\newcommand{\constructpath}[7]{
    \setcounter{innercnt}{0}

    \pgfmathtruncatemacro{\indexone}{\getstorageof{#1}{#1#3}{#5}}
    \pgfmathtruncatemacro{\indextwo}{\getstorageof{#1}{#1#3}{#6}}

    \ifstrequal{#2}{direct}{
        \constructdirectpath{#1}{\indexone}{\indextwo}{#7}{\theinnercnt}
    }{
        \constructreversepath{#1}{\indexone}{\indextwo}{#7}{\theinnercnt}
    }

    \pgfmathtruncatemacro{\indexone}{\getstorageof{#3}{#1#3}{#5}}
    \pgfmathtruncatemacro{\indextwo}{\getstorageof{#3}{#1#3}{#6}}

    \ifstrequal{#4}{direct}{
        \constructdirectpath{#3}{\indextwo}{\indexone}{#7}{\theinnercnt}
    }{
        \constructreversepath{#3}{\indextwo}{\indexone}{#7}{\theinnercnt}
    }

    \setpointsof{#7}{\theinnercnt}
    \csxdef{#7}{}
    \foreach \k in {1, ..., \theinnercnt}{
        \csxdef{#7}{\csuse{#7} (#7-\k)}
    }
}

And that's it. Below I show, step by step, how to implement the proposed solution to the problem.

Structure

Let us begin making clear the basic structure of the document with packages, libraries, variables and counters. All subsequent changes will be made ​​within the environment tikzpicture.

\documentclass{standalone}

\usepackage{etoolbox}

\usepackage{tikz}
\usetikzlibrary{decorations.markings}
\usetikzlibrary{intersections}
\usetikzlibrary{shapes.geometric}

\newcounter{cnt}
\newcounter{innercnt}

\newdimen\xone
\newdimen\yone
\newdimen\xref
\newdimen\yref

%
% here you must put the tikzset and macros.
%

\begin{document}
    \begin{tikzpicture}

        %
        % here you must put all subsequent code
        %

    \end{tikzpicture}
\end{document}

Step by step

First let's create the three ellipses that form the basis of our paths.

\path node [ellipse, split = path01, minimum width = 2cm, minimum height = 1cm] {};
\path node [ellipse, split = path02, minimum width = 2cm, minimum height = 1cm, rotate = 60] {};
\path node [ellipse, split = path03, minimum width = 2cm, minimum height = 1cm, rotate = 120] {};

And we will get:

Ellipses base

So we need the points of intersection between the ellipses. In this case, we are working with the first horizontal ellipse and the ellipse rotated 60 degrees. I put the indexes of each of the intersections to show how each can be referenced.

Intersections

And now we need to build the paths that we use between the points of intersection. In the image, the paths were painted to highlight the change. But the code below does not.

\constructpath{path01}{direct}{path02}{reverse}{1}{2}{path-1-2-1}
\constructpath{path01}{direct}{path02}{reverse}{2}{3}{path-1-2-2}
\constructpath{path01}{direct}{path02}{reverse}{3}{4}{path-1-2-3}
\constructpath{path01}{direct}{path02}{reverse}{4}{1}{path-1-2-4}

\path [name path = path-1-2-1] plot [smooth cycle] coordinates {\csuse{path-1-2-1}};
\path [name path = path-1-2-2] plot [smooth cycle] coordinates {\csuse{path-1-2-2}};
\path [name path = path-1-2-3] plot [smooth cycle] coordinates {\csuse{path-1-2-3}};
\path [name path = path-1-2-4] plot [smooth cycle] coordinates {\csuse{path-1-2-4}};

Paths between points of intersection

We do the same for the remaining ellipse.

\getintersectionsof{path01}{path03}{0.8pt}

\constructpath{path01}{direct}{path03}{reverse}{1}{2}{path-1-3-1}
\constructpath{path01}{direct}{path03}{reverse}{2}{3}{path-1-3-2}
\constructpath{path01}{direct}{path03}{reverse}{3}{4}{path-1-3-3}
\constructpath{path01}{direct}{path03}{reverse}{4}{1}{path-1-3-4}

\path [name path = path-1-3-1] plot [smooth cycle] coordinates {\csuse{path-1-3-1}};
\path [name path = path-1-3-2] plot [smooth cycle] coordinates {\csuse{path-1-3-2}};
\path [name path = path-1-3-3] plot [smooth cycle] coordinates {\csuse{path-1-3-3}};
\path [name path = path-1-3-4] plot [smooth cycle] coordinates {\csuse{path-1-3-4}};

Now we define the final paths using the paths already prepared earlier.

\getintersectionsof{path02}{path-1-3-1}{0.8pt}
\getintersectionsof{path02}{path-1-3-3}{0.8pt}
\constructpath{path02}{direct}{path-1-3-1}{direct}{1}{2}{path-1-2-3-3}
\constructpath{path02}{direct}{path-1-3-3}{direct}{1}{2}{path-1-2-3-4}

\getintersectionsof{path03}{path-1-2-2}{0.8pt}
\getintersectionsof{path03}{path-1-2-4}{0.8pt}
\constructpath{path03}{direct}{path-1-2-2}{reverse}{1}{2}{path-1-2-3-5}
\constructpath{path03}{direct}{path-1-2-4}{reverse}{1}{2}{path-1-2-3-6}

And now just paint the interest areas.

\fill [orange!80] plot [smooth cycle] coordinates {\csuse{path-1-2-3-1}};
\fill [orange!80] plot [smooth cycle] coordinates {\csuse{path-1-2-3-4}};
\fill [orange!80] plot [smooth cycle] coordinates {\csuse{path-1-2-3-5}};

And TAH-DAH!!!

Final result