pgf-tikz / pgf

A Portable Graphic Format for TeX
https://pgf-tikz.github.io/
1.12k stars 106 forks source link

Intersections are incorrect under transformations #889

Open AndreC75 opened 4 years ago

AndreC75 commented 4 years ago

The problem is explained here: How to make so that the calculation of an intersection is correct during a change of scale?

\documentclass{article}
\usepackage{tikz}   
\usetikzlibrary{calc,intersections}
\usepackage{esvect}

\begin{document}

Faux 

 \begin{tikzpicture}[x={(-1,0)},scale=0.9]
\coordinate (O) at (0,0);
\coordinate (Od) at ($(O)+(0.4,0)$);
\coordinate (Og) at ($(O)-(0.4,0)$);

\draw[dashed] ($(O)-(0,3.5)$) -- ($(O) +(0,1.5)$);
\draw[blue,thick,fill=green!40] (Og) --++(-4,0)coordinate(bout) coordinate[pos=0.8](Of)--++(0,-0.2) --++(3.7,-0.3) --++(0,-1.5)coordinate(Ob)-|(Og);
\draw[blue,thick,fill=green!40] (Od) --++(0.3,0)coordinate(Oa) -- (Ob-|Oa) -| (Od);
\draw[fill] (Od) coordinate(A) circle (0.05) node[above left]{$A$};
\draw[fill] (Og|-Ob) coordinate(B) circle (0.05) node[below right]{$B$};
\draw[latex-,ultra thick] (Of)node[above left]{$\vv{F}$}node[below]{$C$} -- ++(0,1.5);

\draw[thin, red,opacity=0.3,fill] ($(A) +(2,0.6)$) -- (A) --($(A) +(2,-0.6)$);
\draw[name path =aa, dashed,red] (A) --++(-5,-1.5);
\draw[ultra thick, red, -latex] (A) -- ++(3,0.9)node[above]{$\vv{R_A}$};
\draw[thin, red,opacity=0.3,fill] ($(B) +(-2,0.6)$) -- (B) --($(B) +(-2,-0.6)$);
\draw[ultra thick, red, -latex] (B) -- ++(-3,0.9)node[above]{$\vv{R_B}$};
\draw[name path =bb, dashed,red] (B) --++(-5,1.5);

\coordinate [name intersections={of=aa and bb, by=I}];
\draw [dashed, blue] (I)node[below]{$I$} --++(0,3);

\draw[thin,-latex] ($(O)+(0,1)$)coordinate(oo) -- node[above]{$x_{lim}$} (oo-|I);
\draw[-latex] (O) -- (-5,0) node[below]{$\vv{x}$};
\draw[-latex] (O) -- (0,2) node[right]{$\vv{y}$};
\end{tikzpicture}

Correct

 \begin{tikzpicture}[x={(-1,0)}]
\coordinate (O) at (0,0);
\coordinate (Od) at ($(O)+(0.4,0)$);
\coordinate (Og) at ($(O)-(0.4,0)$);

\draw[dashed] ($(O)-(0,3.5)$) -- ($(O) +(0,1.5)$);
\draw[blue,thick,fill=green!40] (Og) --++(-4,0)coordinate(bout) coordinate[pos=0.8](Of)--++(0,-0.2) --++(3.7,-0.3) --++(0,-1.5)coordinate(Ob)-|(Og);
\draw[blue,thick,fill=green!40] (Od) --++(0.3,0)coordinate(Oa) -- (Ob-|Oa) -| (Od);
\draw[fill] (Od) coordinate(A) circle (0.05) node[above left]{$A$};
\draw[fill] (Og|-Ob) coordinate(B) circle (0.05) node[below right]{$B$};
\draw[latex-,ultra thick] (Of)node[above left]{$\vv{F}$}node[below]{$C$} -- ++(0,1.5);

\draw[thin, red,opacity=0.3,fill] ($(A) +(2,0.6)$) -- (A) --($(A) +(2,-0.6)$);
\draw[name path =aa, dashed,red] (A) --++(-5,-1.5);
\draw[ultra thick, red, -latex] (A) -- ++(3,0.9)node[above]{$\vv{R_A}$};
\draw[thin, red,opacity=0.3,fill] ($(B) +(-2,0.6)$) -- (B) --($(B) +(-2,-0.6)$);
\draw[ultra thick, red, -latex] (B) -- ++(-3,0.9)node[above]{$\vv{R_B}$};
\draw[name path =bb, dashed,red] (B) --++(-5,1.5);

\coordinate [name intersections={of=aa and bb, by=I}];
\draw [dashed, blue] (I)node[below]{$I$} --++(0,3);

\draw[thin,-latex] ($(O)+(0,1)$)coordinate(oo) -- node[above]{$x_{lim}$} (oo-|I);
\draw[-latex] (O) -- (-5,0) node[below]{$\vv{x}$};
\draw[-latex] (O) -- (0,2) node[right]{$\vv{y}$};
\end{tikzpicture}
\end{document}
hmenke commented 4 years ago

Please use the issue template and provide a minimal example.

muzimuzhi commented 4 years ago

Considering the following mwe:

Brief outline of the bug

In a scaled scope, name intersections={of=...} used as coordinate/node option creates intersections with wrong coordinates.

Similar question: https://tex.stackexchange.com/questions/218210/rotating-tikzpicture-messes-up-intersections.

Minimal working example (MWE)

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

\begin{document}
Faux 

\begin{tikzpicture}[scale=0.9]
  \draw[name path=aa] (0, 0) -- ++(3, 3);
  \draw[name path=bb] (0, 3) -- ++(3, -3);

  \coordinate[name intersections={of=aa and bb}] (x);
  % or one of 
  % \path coordinate[name intersections={of=aa and bb}] (x);
  % \path node[name intersections={of=aa and bb}] (x) {};
  % \node[name intersections={of=aa and bb}] (x) {};
  \fill (intersection-1) circle (2pt);
\end{tikzpicture}

Correct

\begin{tikzpicture}[scale=0.9]
  \draw[name path=aa] (0, 0) -- ++(3, 3);
  \draw[name path=bb] (0, 3) -- ++(3, -3);

  \path[name intersections={of=aa and bb}] coordinate (x);
  \fill (intersection-1) circle (2pt);
\end{tikzpicture}

\end{document}

image

hmenke commented 4 years ago

Looks like the intersection are calculated in the untransformed system (which makes a lot of sense) but then the transformation is not applied to the result again.

muzimuzhi commented 4 years ago

I don't know why but it seems the transformation is applied to intersection coordinates twice.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{tikz}
\usetikzlibrary{intersections, scopes}

\begin{document}
\makeatletter

Faux\par
\begin{tikzpicture}[scale=0.9]
  \draw[name path=aa] (0, 0) -- ++(3, 3);
  \draw[name path=bb] (0, 3) -- ++(3, -3);

  \path { coordinate[name intersections={of=aa and bb}] (x)};
  % print "macro:->{1.0}{0.0}{0.0}{1.0}{34.5696.pt}{34.5696.pt}"
  \node {\expandafter\meaning\csname pgf@sh@nt@intersection-1\endcsname};
  \fill (intersection-1) circle (2pt);
\end{tikzpicture}

Attempt\par
\begin{tikzpicture}[scale=0.9]
  \draw[name path=aa] (0, 0) -- ++(3, 3);
  \draw[name path=bb] (0, 3) -- ++(3, -3);

  % added {[reset cm] ...}
  \path {[reset cm] coordinate[name intersections={of=aa and bb}] (x)};
  % print "macro:->{1.0}{0.0}{0.0}{1.0}{38.41093pt}{38.41093pt}"
  \node {\expandafter\meaning\csname pgf@sh@nt@intersection-1\endcsname};
  \fill (intersection-1) circle (2pt);
\end{tikzpicture}

Correct\par
\begin{tikzpicture}[scale=0.9]
  \draw[name path =aa] (0, 0) -- ++(3, 3);
  \draw[name path =bb] (0, 3) -- ++(3, -3);

  \path[name intersections={of=aa and bb}] coordinate (x);
  % print "macro:->{1.0}{0.0}{0.0}{1.0}{38.41093pt}{38.41093pt}"
  \node {\expandafter\meaning\csname pgf@sh@nt@intersection-1\endcsname};
  \fill (intersection-1) circle (2pt);
\end{tikzpicture}

\end{document}
hmenke commented 4 years ago

Not sure if this is really a fix:

diff --git a/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryintersections.code.tex b/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryintersections.code.tex
index cf94f923..ca0e7a1c 100644
--- a/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryintersections.code.tex
+++ b/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryintersections.code.tex
@@ -104,7 +104,7 @@
         \pgfmathloop%
         \ifnum\pgfmathcounter>\pgfintersectionsolutions\relax%
         \else%
-          \path[reset cm]\pgfextra{\pgftransformshift{\pgfpointintersectionsolution{\pgfmathcounter}}}%
+          \path\pgfextra{\pgftransformshift{\pgfpointintersectionsolution{\pgfmathcounter}}}%
             coordinate (\tikz@intersect@@name-\pgfmathcounter);
         \repeatpgfmathloop%
         \ifx\tikz@intersect@by\pgfutil@empty%
diff --git a/tex/generic/pgf/libraries/pgflibraryintersections.code.tex b/tex/generic/pgf/libraries/pgflibraryintersections.code.tex
index 24571576..c920dbae 100644
--- a/tex/generic/pgf/libraries/pgflibraryintersections.code.tex
+++ b/tex/generic/pgf/libraries/pgflibraryintersections.code.tex
@@ -352,7 +352,9 @@
     \pgf@iflinesintersect{#1}{#2}{#3}{#4}%
     {%
         \pgfextract@process\pgf@intersect@solution@candidate{%
+            \pgftransforminvert
             % pgf@x and pgf@y are already assigned by \pgf@iflinesintersect
+            \pgfpointtransformed{\pgfqpoint{\pgf@x}{\pgf@y}}%
         }%
         \pgf@ifsolution@duplicate{\pgf@intersect@solution@candidate}{%
             % ah - we a duplicate. Apparently, we have a hit on an

However, this also fixes the following example:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{intersections}
\begin{document}

\def\pathA{%
    \pgfpathmoveto{\pgfpointorigin}%
    \pgfpathlineto{\pgfqpoint{30pt}{30pt}}%
}

\def\pathB{%
    \pgfpathmoveto{\pgfqpoint{0pt}{30pt}}%
    \pgfpathlineto{\pgfqpoint{30pt}{0pt}}%
}

\begin{pgfpicture}
    \pgftransformscale{0.9}
    \pgftransformrotate{30}
    \pathA\pgfusepath{stroke}%
    \pathB\pgfusepath{stroke}%
    \pgfintersectionofpaths\pathA\pathB
    \pgfpathcircle{\pgfpointintersectionsolution{1}}{2pt}
    \pgfusepath{stroke}%
\end{pgfpicture}

\end{document}
hmenke commented 4 years ago

Definitely a candidate for regression. Let's see.

muzimuzhi commented 4 years ago

Just a reminder, the corresponding commit https://github.com/hmenke/pgf/commit/5d5b9976fec17668b187d9375b33d1d93b0dc88d to hmenke/pgf has not been merged to pgf-tikz/pgf yet.

sergiud commented 4 years ago

This change seems to have broken one of my figures.

Before 3.1.6: before-1

After 3.1.6: after-1

\usetikzlibrary{pgfplots.colorbrewer}

\pgfdeclarelayer{background}%
\pgfsetlayers{background,main}%
\begin{tikzpicture}
[
  %rotate=22.5,
  segment/.style={arrows={{Circle[length=4pt]}-{Circle[length=4pt]}},very thick},
  segments/.style={very thick,mark=*,mark size=(4pt-\pgflinewidth)*0.5},
  first/.style={Dark2-A},
  second/.style={Dark2-B},
  %scale=1.5,
  every path/.style={scale=1.25},
  declare function={
    angle=22.5;
    segment_anchor=angle+45;
  },
]
  \draw[densely dotted,Dark2-A] (0,0) -- +(angle:0.5) coordinate (A);
  \draw[segment,first] (A)
    node[anchor=-segment_anchor] {\(A\)}
    --
    %node[sloped,below] {\(l_1\)}
    +(angle:1.2)
    coordinate (B)
    node[anchor=-segment_anchor] {\(B\)}
    ;

  \draw[segment,second]
    ($(B)+(2.25,0)$) coordinate (C)
    % Make sure prime does not produce additiona horizontal space such that the
    % anchors of letters with and without a prime are exactly at the same
    % position.
    node[anchor=180+segment_anchor] {\(B\mathrlap{'}\)}
    --
    %node[sloped,below] {\(l_2\)}
    +(-22.5:1) coordinate (D)
    node[anchor=180+segment_anchor] {\(A\mathrlap{'}\)}
    ;
  \draw[densely dotted,second] (D) -- +(-angle:.5) coordinate (E);

  \path[name path=BC] (B) -- (angle:5) coordinate (AB ext);
  \path[name path=CD] (C) -- +(-angle:-3) coordinate (CD ext);

  \path[name intersections={of=BC and CD,by=inter}];

  \draw[first,dashed] (B) -- (inter);
  \draw[second,dashed] (inter) -- (C);

  \colorlet{angle}{Dark2-E}

  \draw (D)
    let \p1 = ($(C)-(inter)$) in
    pic
    [
      draw=Dark2-C,
      pattern=north east lines,
      pattern color=Dark2-C,
      semithick,
      angle radius={0.3*veclen(\x1,\y1)}
    ]
    {angle=B--inter--C}
    ;

  \begin{pgfonlayer}{background}
    \draw (B)
      let \p1 = ($(inter)-(C)$) in
      pic [Dark2-B,draw=.,fill=.!25,angle radius={0.3*veclen(\x1,\y1)}]
        {angle=B--C--D}
      ;

    \draw (inter)
      let \p1 = ($(inter)-(B)$) in
      pic [Dark2-A,draw=.,fill=.!25,angle radius={0.3*veclen(\x1,\y1)}]
        {angle=A--B--C}
      ;
  \end{pgfonlayer}

  \draw[dashed] (B) -- (C);

  \begin{scope}[outer sep=.25ex]
    \node[Dark2-A,anchor=90+angle/2] at (B) {\(\alpha_1\)};
    \node[Dark2-B,anchor=90-angle/2] at (C) {\(\alpha_2\)};
    \node[anchor=south,Dark2-C] at (inter) {\(\alpha_3\)};
  \end{scope}

  % The intersection of extended lines
  \draw[mark=x,very thick,Dark2-H,scale=1.5] plot coordinates {(inter)};
\end{tikzpicture}
hmenke commented 4 years ago

@sergiud Make a new issue. I'll revert and push a hotfix.

Also, even though in this case it is sort of clear what's the issue, please always provide a minimal working example.

sergiud commented 4 years ago

@hmenke Done (#929).

muzimuzhi commented 3 years ago

Problem restatement and analysis

There are actually two sub-issues: (transformation matrix is denoted by T below and assuming T is not the identity matrix)

When using pgf, the T is firstly applied in \pgfintersectionofpaths (implementation) and then in any path construction commands that process \pgfpointintersectionsolution{<num>} as a point.

My proposal is to prefix \pgftransformreset to \pgfpointintersectionsolution:

diff --git a/tex/generic/pgf/libraries/pgflibraryintersections.code.tex b/tex/generic/pgf/libraries/pgflibraryintersections.code.tex
index 24571576..a398e063 100644
--- a/tex/generic/pgf/libraries/pgflibraryintersections.code.tex
+++ b/tex/generic/pgf/libraries/pgflibraryintersections.code.tex
@@ -37,6 +37,7 @@
         \ifnum#1>\pgfintersectionsolutions\relax%
             \pgfpoint@intersect@solution@orgin%
         \else%
+            \pgftransformreset
             \csname pgfpoint@intersect@solution@#1\endcsname%
         \fi%
     \fi%

In front layer (using tikz), name path=<name> stores collected soft path to <name>, hence the fix for pgf doesn't work. In this case, T is firstly applied when generating the soft path, and then in line \path[reset cm] \pgfextra{...} coordinate (...); in the definition of use intersections.

Since reset cm will only reset T immediately when \tikz@transform is \relax, and node sets \tikz@transform to \pgfutil@empty at beginning, when used as node options, the above reset cm will not reset T.

My proposal is to directly use \pgftransformreset:

diff --git a/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryintersections.code.tex b/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryintersections.code.tex
index cf94f923..dfd38f03 100644
--- a/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryintersections.code.tex
+++ b/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryintersections.code.tex
@@ -104,7 +104,7 @@
         \pgfmathloop%
         \ifnum\pgfmathcounter>\pgfintersectionsolutions\relax%
         \else%
-          \path[reset cm]\pgfextra{\pgftransformshift{\pgfpointintersectionsolution{\pgfmathcounter}}}%
+          \path\pgfextra{\pgftransformreset\pgftransformshift{\pgfpointintersectionsolution{\pgfmathcounter}}}%
             coordinate (\tikz@intersect@@name-\pgfmathcounter);
         \repeatpgfmathloop%
         \ifx\tikz@intersect@by\pgfutil@empty%

I will report the \tikz@transform problem in another issue (see #963 ), and when it is fixed the tikz layer of current issue would be auto fixed.

Example

An example containing all three examples given in https://github.com/pgf-tikz/pgf/issues/889#issuecomment-650716923, https://github.com/pgf-tikz/pgf/issues/889#issuecomment-654631897, and #929. ```tex \documentclass{article} \usepackage{tikz} \usetikzlibrary{intersections} \begin{document} % tikz example 1, % from 3rd comment of issue #889 \begin{tikzpicture}[x=30pt,y=30pt] \draw (0,0) grid[step=10pt] (1,1); \tikzset{scale=.8,rotate=30,shift={(5pt,10pt)}} \draw[name path=aa] (0,0) -- (1,1); \draw[name path=bb] (0,1) -- (1,0); \path[name intersections={of=aa and bb}]; \draw (intersection-1) circle (2pt); \path node[name intersections={of=aa and bb}] {}; \draw[red] (intersection-1) circle (2pt); \end{tikzpicture} % tikz example 2, transformations are set by "every path/.style={...}", % from issue #929 \begin{tikzpicture}[x=30pt,y=30pt] \draw (0,0) grid[step=10pt] (1,1); \tikzset{every path/.style={scale=.8,rotate=30,shift={(5pt,10pt)}}} \draw[name path=aa] (0,0) -- (1,1); \draw[name path=bb] (0,1) -- (1,0); \path[name intersections={of=aa and bb}]; \draw (intersection-1) circle (2pt); \path node[name intersections={of=aa and bb}] {}; \draw[red] (intersection-1) circle (2pt); \end{tikzpicture} % pgf example, % from https://github.com/pgf-tikz/pgf/issues/889#issuecomment-654631897 \def\pathA{% \pgfpathmoveto{\pgfpointorigin}% \pgfpathlineto{\pgfqpoint{30pt}{30pt}}% } \def\pathB{% \pgfpathmoveto{\pgfqpoint{0pt}{30pt}}% \pgfpathlineto{\pgfqpoint{30pt}{0pt}}% } \begin{pgfpicture} \pgfpathgrid[stepx=10pt,stepy=10pt]{\pgfqpoint{0pt}{0pt}}{\pgfqpoint{30pt}{30pt}} \pgftransformscale{.8} \pgftransformrotate{30} \pgftransformshift{\pgfqpoint{5pt}{10pt}} \pathA\pgfusepath{stroke} \pathB\pgfusepath{stroke} \pgfintersectionofpaths\pathA\pathB \pgfpathcircle{\pgfpointintersectionsolution{1}}{2pt} \pgfusepath{stroke} \pgfsetcolor{red} \pgfpathcircle{\pgfqpoint{15pt}{15pt}}{2pt} \pgfusepath{stroke} \end{pgfpicture} \end{document} ``` ![image](https://user-images.githubusercontent.com/6376638/102693871-9a681600-4258-11eb-907e-25b1eda1bc07.png)