5

Without the extremely cool macros by John Kormylo and the very helpful answer by Torbjørn T. I would not have been able to come this far. My ultimate aim is to make it easy to fake 3D spheres with TikZ. This already works quite well, but I wanted to try a different, arguably more direct approach to draw the relevant objects. Here is my MWE.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{shadings}
% parametrizations from https://tex.stackexchange.com/a/410379/121799
\newcommand{\latitudearc}[4]{% #1=label (optional), #2=latitude
%#3 start angle #4 end angle
  \pgfextra{\pgfmathsetmacro{\RX}{\RadiusSphere*cos(#2)} % from https://tex.stackexchange.com/a/410567/121799
  \global\let\RX=\RX % from https://tex.stackexchange.com/a/352269/121799
  \pgfmathsetmacro{\RY}{\RX*sin(\Clat)}%
  \global\let\RY=\RY
  \pgfmathsetmacro{\CY}{\RadiusSphere*sin(#2)*cos(\Clat)}
  \global\let\CY=\CY
  \pgfmathsetmacro{\Xend}{\RX*cos(#4)}
  \global\let\Xend=\Xend
  \pgfmathsetmacro{\Yend}{\CY+\RY*sin(#4)}
  \global\let\Yend=\Yend}%
  plot [domain=#3:#4,smooth,#1] ({\RX*cos(\noexpand\x)},{\CY+\RY*sin(\noexpand\x)})
}
% compute ellipse rotation=\ROT, xradius=\RX, arc angle at equator=\LAT
\newcommand{\longitudearc}[4]{% #1=label (optional), #2=longitude
%#3 start angle #4 end angle
  \pgfextra{\pgfmathsetmacro{\ROT}{atan2(sin(\Clat)*sin(#2-\Clong),cos(#2-\Clong))}% alpha
  \global\let\ROT=\ROT
  \pgfmathsetmacro{\LAT}{asin(cos(\Clat)*cos(\ROT))}% north pole theta_n
  \global\let\LAT=\LAT
  \pgfmathsetmacro{\RX}{\RadiusSphere*tan(\LAT)*tan(\ROT)}% r_x
  \global\let\RX=\RX
  \pgfmathsetmacro{\DELTAX}{-90+\LAT}
  \global\let\DELTAX=\DELTAX
  \pgfmathsetmacro{\Xend}{\RX*cos(\ROT)*cos(#4+\DELTAX)-\RadiusSphere*sin(\ROT)*sin(#4+\DELTAX)}
  \global\let\Xend=\Xend
  \pgfmathsetmacro{\Yend}{\RadiusSphere*cos(\ROT)*sin(#4+\DELTAX)+\RX*sin(\ROT)*cos(#4+\DELTAX)}
  \global\let\Yend=\Yend
  }
  plot[domain=#3:#4,smooth,#1]({\RX*cos(\ROT)*cos(\noexpand\x+\DELTAX)-\RadiusSphere*sin(\ROT)*sin(\noexpand\x+\DELTAX)},
  {\RadiusSphere*cos(\ROT)*sin(\noexpand\x+\DELTAX)+\RX*sin(\ROT)*cos(\noexpand\x+\DELTAX)})
}
\newcommand{\hotspot}[3]{% #1=label (optional), #3=latitude, #2=longitude
  \pgfextra{\pgfmathsetmacro{\RX}{\RadiusSphere*cos(#3)} % from https://tex.stackexchange.com/a/410567/121799
  \global\let\RX=\RX % from https://tex.stackexchange.com/a/352269/121799
  \pgfmathsetmacro{\RY}{\RX*sin(\Clat)}%
  \global\let\RY=\RY
  \pgfmathsetmacro{\CY}{\RadiusSphere*sin(#3)*cos(\Clat)}
  \global\let\CY=\CY
  \pgfmathsetmacro{\Xloc}{\RX*cos(#2)}
  \global\let\Xloc=\Xloc
  \pgfmathsetmacro{\Yloc}{\CY+\RY*sin(#2)}
  \global\let\Yloc=\Yloc}%
}

\begin{document}

\begin{tikzpicture}
\def\RadiusSphere{4}% sphere radius
\def\Clat{20}% point of view latitude
\def\Clong{-90}% point of view longitude
\shade[ball color = gray!40, opacity = 0.5] (0,0) circle (\RadiusSphere);


\hotspot{0}{-120}{-40}
\filldraw (\Xloc,\Yloc) circle (0.2);
\hotspot{0}{-120}{30}
\filldraw (\Xloc,\Yloc) circle (0.2);
\hotspot{0}{-50}{-40}
\filldraw (\Xloc,\Yloc) circle (0.2);
\hotspot{0}{-50}{30}
\filldraw (\Xloc,\Yloc) circle (0.2);


\begin{scope}
\clip[variable=\x] \longitudearc{blue}{-120}{-40}{30} 
%\pgfextra{\hotspot{0}{-50}{-40}} to  (\Xloc,\Yloc)  % <-NEEDED?
\latitudearc{blue}{30}{-120}{-50} %to ({\Xend},{\Yend})  
%\pgfextra{\hotspot{0}{-120}{-40}} to  (\Xloc,\Yloc) % <-NEEDED? 
\longitudearc{blue}{-50}{30}{-40} %to ({\Xend},{\Yend})  
%\pgfextra{\hotspot{0}{-120}{30}} to  (\Xloc,\Yloc)  % <-NEEDED?
\latitudearc{blue}{-40}{-50}{-120} %to ({\Xend},{\Yend})  
%\pgfextra{\hotspot{0}{-50}{30}} to  (\Xloc,\Yloc)  % <-NEEDED?
-- cycle;
\shade[ball color = blue!40, opacity = 0.5] (0,0) circle (\RadiusSphere);
\end{scope}
\end{tikzpicture}
\end{document}

enter image description here

This is not quite what I wanted. However, if I uncomment the \pgfextra commands in the scope at the bottom, i.e. use the scope

\begin{scope}
\clip[variable=\x] \longitudearc{blue}{-120}{-40}{30} 
\pgfextra{\hotspot{0}{-50}{-40}} to  (\Xloc,\Yloc)  % <-NEEDED?
\latitudearc{blue}{30}{-120}{-50} %to ({\Xend},{\Yend})  
\pgfextra{\hotspot{0}{-120}{-40}} to  (\Xloc,\Yloc) % <-NEEDED? 
\longitudearc{blue}{-50}{30}{-40} %to ({\Xend},{\Yend})  
\pgfextra{\hotspot{0}{-120}{30}} to  (\Xloc,\Yloc)  % <-NEEDED?
\latitudearc{blue}{-40}{-50}{-120} %to ({\Xend},{\Yend})  
\pgfextra{\hotspot{0}{-50}{30}} to  (\Xloc,\Yloc)  % <-NEEDED?
-- cycle;
\shade[ball color = blue!40, opacity = 0.5] (0,0) circle (\RadiusSphere);
\end{scope}

instead, I get:

enter image description here

which is almost precisely what I want. (The upper and lower boundaries are incorrect.) That is, by artificially inserting points into the path, I can get the shape bounded by plots. In the upper figure it seems that TikZ for some reason always "completes" the individual plots by returning to the starting points. Now my question is whether or not there is a way to switch that off.

EDIT: Here is a very minimal example.

\documentclass{article}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
\begin{scope}
\node[text width=3.5cm] at (1,5){with cycle};
\path[draw,blue,variable=\x,domain=0:2] (0,0) --  plot ({\x},{\x*\x})
 plot ({1*(2-\x)},{4*(1-\x)})  -- cycle; 
\clip[variable=\x,domain=0:2] (0,0) -- plot ({\x},{\x*\x})
 plot ({1*(2-\x)},{4*(1-\x)}) -- cycle; 
\fill (0,0) circle (1);
\end{scope}
\begin{scope}[transform canvas={xshift=4cm}] 
\node[text width=3.5cm] at (1,5){path closed by hand};
\path[draw,blue,variable=\x,domain=0:2] (0,0) --  plot ({\x},{\x*\x})
 plot ({1*(2-\x)},{4*(1-\x)})  -- (0,0); 
\clip[variable=\x,domain=0:2] (0,0) -- plot ({\x},{\x*\x})
 plot ({1*(2-\x)},{4*(1-\x)}) -- (0,0); 
\fill (0,0) circle (1);
\end{scope}
\begin{scope}[transform canvas={xshift=10cm}] 
\node[text width=3.5cm] at (1,5){a somewhat more complex example};
\path[draw,blue,variable=\x,domain=0:2] (0,0) --  plot ({\x},{\x*\x})
 plot ({1*(2-\x)},{4*(1-\x)})  
 plot ({-\x},{-4+\x*\x})-- (0,0); 
\clip[variable=\x,domain=0:2] (0,0) --  plot ({\x},{\x*\x})
 plot ({1*(2-\x)},{4*(1-\x)})  
 plot ({-\x},{-4+\x*\x})-- (0,0);
\fill (0,0) circle (1);
\draw [red,dashed] (0,0) -- (2,4);
\draw [red,dashed] (0,0) -- (0,-4);
\end{scope}
\end{tikzpicture}
\end{document}

enter image description here

As one can see, the clipping is only correct (or, at least, as I think it should be) in the middle plot where I closed the path by hand. In the right plot, even though I close the path by hand, the clipping is not as it should be, I think. I also added the red dashed lines that seem to suggest that each of the plots form a closed path. But the middle example shows that this is not always case. Is there a way to always have the behavior as in the middle example without adding coordinates by hand?

  • @cfr Well, thanks for your example, I had a very similar one earlier on. As far as I see it, the issue really arises when I construct a path of several plots. And the commands \longitudearc and \latitudearc are very explicit, you really only need the last lines of those, e.g. plot [domain=#3:#4,smooth,#1] ({\RX*cos(\noexpand\x)},{\CY+\RY*sin(\noexpand\x)}). But thanks for looking into this anyway! –  Jan 16 '18 at 02:16
  • @cfr No, because then you'd expect to clip like in the upper figure. I really want to construct a boundary by following the first plot from the lower to the upper end of the domain, then the next one, and so on. –  Jan 16 '18 at 02:38
  • @cfr The issue arises regardless of whether or not I put \cycle at the end of the sequence. The last plot ends (or, more precisely, is supposed to end) where the first one begins. –  Jan 16 '18 at 02:41
  • @cfr I added a very minimal example that seems to suggest that the problem is always there. –  Jan 16 '18 at 05:32
  • In your very minimal example, add -- between the plots of each clip path to get the correct result (the clip operation is like a fill operation). – Paul Gaborit Jan 16 '18 at 06:16
  • @PaulGaborit Thanks! But this seems to be an accident, try \clip (0,0) -- plot[variable=\x,domain=0:2] ({\x},{\x*\x}) -- plot[variable=\x,domain=0:2] ({1*(2-\x)},{4*(1-\x)}) -- plot[variable=\x,domain=-2:0] ({\x+2},{4+(\x+2)*(\x+2)})-- (0,0); instead. Here I put -- before, between, and after the plot. –  Jan 16 '18 at 06:19
  • The result seems correct with the example in your previous comment... What result did you expect? – Paul Gaborit Jan 16 '18 at 06:31
  • @PaulGaborit Yes, you're right! Very helpful! –  Jan 16 '18 at 06:42
  • The filling examples p.170 (pgfmanual, v3.0.1a) are explicit: All unclosed parts of the path are first closed, if necessary. Note: the clip operation always use the nonzero rule. – Paul Gaborit Jan 16 '18 at 06:51
  • @PaulGaborit Sorry, this is not too helpful. In the first example, there is no need to "close" the paths. –  Jan 16 '18 at 14:48
  • Each plot begins by an implicit move to operation if not preceding by -- (p.326, pgfmanual, v3.0.1a). – Paul Gaborit Jan 16 '18 at 15:09
  • @PaulGaborit Yes, thanks! But I still don't quite understand what is considered the first coordinate of the plots. In the "somewhat more complex example", the first coordinate of a given plot is the last coordinate of the previous one. So, according to this, no -- should be necessary. –  Jan 16 '18 at 15:14
  • The implicit move to operation is included even if the coordinates are the same. – Paul Gaborit Jan 16 '18 at 15:54
  • @PaulGaborit Sadly, in the example on the spheres I fail to put -- for reasons that I do not understand. I can put -- (\Xend,\Yend) where \Xend and \Yend are the final coordinates of the last or first coordinates of the next plot, but this does not help. –  Jan 16 '18 at 17:09

1 Answers1

3

Each plot begins by an implicit move to operation if not preceding by -- (p.326, pgfmanual, v3.0.1a). The implicit move to operation is included even if the current point is the same as the first coordinate of the plot.

To get a single closed path, you must link your plot operations by --. But TikZ does not allow to call a macro after a --: you can't use your \latitudearc macro after --.

Here is a solution more in the spirit of TikZ. This solution no longer uses macros but keys to define coordinates (hot spot) or to build arcs (latitude arc and longitude arc).

\documentclass[tikz]{standalone}

\usetikzlibrary{shadings}
\tikzset{
  hot spot/.style n args={2}{% #1=longitude, #2=latitude
    /utils/exec={
      \pgfmathsetmacro{\RX}{\RadiusSphere*cos(#2)} % from https://tex.stackexchange.com/a/410567/121799
      \pgfmathsetmacro{\RY}{\RX*sin(\Clat)}%
      \pgfmathsetmacro{\CY}{\RadiusSphere*sin(#2)*cos(\Clat)}
      \pgfmathsetmacro{\Xloc}{\RX*cos(#1)}
      \pgfmathsetmacro{\Yloc}{\CY+\RY*sin(#1)}
    },
    at={(\Xloc,\Yloc)},
  },
  latitude arc/.style n args={4}{% #1=additional keys for plot, #2=latitude, #3 start longitude, #4 end longitude
    to path={
      \pgfextra{
        \pgfmathsetmacro{\RX}{\RadiusSphere*cos(#2)} % from https://tex.stackexchange.com/a/410567/121799
        \pgfmathsetmacro{\RY}{\RX*sin(\Clat)}%
        \pgfmathsetmacro{\CY}{\RadiusSphere*sin(#2)*cos(\Clat)}
        \pgfmathsetmacro{\Xend}{\RX*cos(#4)}
        \pgfmathsetmacro{\Yend}{\CY+\RY*sin(#4)}
      }%
      -- plot [domain=#3:#4,#1] ({\RX*cos(\x)},{\CY+\RY*sin(\x)}) -- (\tikztotarget)
    },
  },
  longitude arc/.style n args={4}{% #1=additional keys for plot, #2=longitude, #3 start latitude, #4 end latitude
    to path={
      \pgfextra{
        \pgfmathsetmacro{\ROT}{atan2(sin(\Clat)*sin(#2-\Clong),cos(#2-\Clong))}% alpha
        \pgfmathsetmacro{\LAT}{asin(cos(\Clat)*cos(\ROT))}% north pole theta_n
        \pgfmathsetmacro{\RX}{\RadiusSphere*tan(\LAT)*tan(\ROT)}% r_x
        \pgfmathsetmacro{\DELTAX}{-90+\LAT}
        \pgfmathsetmacro{\Xend}{\RX*cos(\ROT)*cos(#4+\DELTAX)-\RadiusSphere*sin(\ROT)*sin(#4+\DELTAX)}
        \pgfmathsetmacro{\Yend}{\RadiusSphere*cos(\ROT)*sin(#4+\DELTAX)+\RX*sin(\ROT)*cos(#4+\DELTAX)}
      }
      --
      plot[domain=#3:#4,variable=\x,#1]({\RX*cos(\ROT)*cos(\x+\DELTAX)-\RadiusSphere*sin(\ROT)*sin(\x+\DELTAX)},
      {\RadiusSphere*cos(\ROT)*sin(\x+\DELTAX)+\RX*sin(\ROT)*cos(\x+\DELTAX)})
      -- (\tikztotarget)
    },
  },
}

\begin{document}
\begin{tikzpicture}
  \def\RadiusSphere{4}% sphere radius
  \def\Clat{20}% point of view latitude
  \def\Clong{-90}% point of view longitude
  \shade[ball color = gray!40, opacity = 0.5] (0,0) circle (\RadiusSphere);


  \coordinate[hot spot={-120}{-40}] (p1);
  \coordinate[hot spot={-120}{30}] (p2);
  \coordinate[hot spot={-50}{-40}] (p3);
  \coordinate[hot spot={-50}{30}] (p4);

  \begin{scope}
    \clip
    (p2) to[latitude arc={}{30}{-120}{-50}]
    (p4) to[longitude arc={}{-50}{30}{-40}]
    (p3) to[latitude arc={}{-40}{-50}{-120}]
    (p1) to[longitude arc={}{-120}{-40}{30}]
    cycle;
    \shade[ball color=blue!40,opacity=.5] (0,0) circle (\RadiusSphere);
  \end{scope}

  \path \foreach \num in {1,...,4}{(p\num) node{p\num}};
\end{tikzpicture}
\end{document}

enter image description here

Paul Gaborit
  • 70,770
  • 10
  • 176
  • 283
  • Just wondering if you and @JohnKormylo want to turn this into a package.... –  Jan 17 '18 at 01:22