3

I know there's already topics on this subject (for example here and here). But, as a result of the excellent solutions brought here and here to other questions, I wonder if there's a way to do that in this framework.

  • It depends on what your question precisely is. It is certainly possible to shade the visible and hidden surfaces in a different color. However, it might require to do certain things "by hand". The idea would be to use the pgfplots (!) library fillbetween and shade the area bounded by some path and the boundary of the sphere. But you'd have to decide yourself whether or not the area is partially hidden. – user121799 Jun 21 '18 at 16:09
  • @marmot I was hoping you would see this discussion ;-). I don't have example of using fillbetween in 3D. I looked a bit \fill[intersection segments={of=A and B,sequence={R2 -- L2 -- ...}}]; but in 3D it's hard to use (especially to find the sequence) and, as you mentioned it, I don't know if there's an automatic way to do it. – kipgon Jun 21 '18 at 17:12
  • 1
    I made an update below in which I believe to have automatized a lot of things. But there is one thing that cannot be automatized without further input: which area to fill. A closed contour on a sphere always encloses two areas, and it is a matter of choice which one should be called inner. Any idea how to make this choice? – user121799 Jun 22 '18 at 18:39
4

In principle it is possible to do that with the fillbetween library. The technical challenge is that TikZ find too many intersections and the number of intersections depends on the number of samples used in order to draw the path of the sphere. So in principle you can draw arbitrary shapes on a sphere (not just arcs!) and distinguish between hidden and visible lines and surfaces. While for lines it simply works with the spherical smooth style, for the surfaces one unfortunately has to fiddle around by hand. Here is an example:

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{intersections}
\usepackage{pgfplots} 
\pgfplotsset{compat=1.16}
\usepgfplotslibrary{fillbetween}
\usepackage{tikz-3dplot}

\pgfkeys{/tikz/.cd,
    hidden opacity/.store in=\HiddenOpacity,
    hidden opacity=0.3,
}

\makeatletter

% from https://tex.stackexchange.com/a/375604/121799

%along z axis
\define@key{z sphericalkeys}{radius}{\def\myradius{#1}}
\define@key{z sphericalkeys}{theta}{\def\mytheta{#1}}
\define@key{z sphericalkeys}{phi}{\def\myphi{#1}}
\tikzdeclarecoordinatesystem{z spherical}{% 
    \setkeys{z sphericalkeys}{#1}%
    \pgfmathsetmacro{\Xtest}{cos(90-\tdplotmaintheta)*cos(\tdplotmainphi-90)*cos(\mytheta)*cos(\myphi)
    +cos(90-\tdplotmaintheta)*sin(\tdplotmainphi-90)*cos(\mytheta)*sin(\myphi)
    +sin(90-\tdplotmaintheta)*sin(\mytheta)}
    % \Xtest is the projection of the coordinate on the normal vector of the visible plane
    \pgfmathsetmacro{\ntest}{ifthenelse(\Xtest<0,0,1)}
    \ifnum\ntest=0
    \xdef\MCheatOpa{\HiddenOpacity}
    \else
    \xdef\MCheatOpa{1}
    \fi
    %\typeout{\mytheta,\tdplotmaintheta;\myphi,\tdplotmainphi:\ntest}
    \pgfpointxyz{\myradius*cos(\mytheta)*cos(\myphi)}{%
    \myradius*cos(\mytheta)*sin(\myphi)}{\myradius*sin(\mytheta)}}

%%%%%%%%%%%%%%%%%
% define "new" plot handler
\tikzoption{spherical smooth}[]{\let\tikz@plot@handler=\pgfplothandlersphericalcurveto}


\pgfdeclareplothandler{\pgfplothandlersphericalcurveto}{}{%
  point macro=\pgf@plot@curveto@handler@spherical@initial,
  jump macro=\pgf@plot@smooth@next@spherical@moveto,
  end macro=\pgf@plot@curveto@handler@spherical@finish
}

\def\pgf@plot@smooth@next@spherical@moveto{%
  \pgf@plot@curveto@handler@spherical@finish%
  \global\pgf@plot@startedfalse%
  \global\let\pgf@plotstreampoint\pgf@plot@curveto@handler@spherical@initial%
}

\def\pgf@plot@curveto@handler@spherical@initial#1{%
  \pgf@process{#1}%
  \ifx\tikz@textcolor\pgfutil@empty%
  \else
  \pgfsetstrokecolor{\tikz@textcolor}
  \fi
  \pgf@xa=\pgf@x%
  \pgf@ya=\pgf@y%
  \pgf@plot@first@action{\pgfqpoint{\pgf@xa}{\pgf@ya}}%
  \xdef\pgf@plot@curveto@first{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}%
  \global\let\pgf@plot@curveto@first@support=\pgf@plot@curveto@first%
  \global\let\pgf@plotstreampoint=\pgf@plot@curveto@handler@spherical@second%
}

\def\pgf@plot@curveto@handler@spherical@second#1{%
  \pgf@process{#1}%
  \xdef\pgf@plot@curveto@second{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}%
  \global\let\pgf@plotstreampoint=\pgf@plot@curveto@handler@spherical@third%
  \global\pgf@plot@startedtrue%
}

\def\pgf@plot@curveto@handler@spherical@third#1{%
  \pgf@process{#1}%
  \xdef\pgf@plot@curveto@current{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}%
  % compute difference vector:
  \pgf@xa=\pgf@x%
  \pgf@ya=\pgf@y%
  \pgf@process{\pgf@plot@curveto@first}
  \advance\pgf@xa by-\pgf@x%
  \advance\pgf@ya by-\pgf@y%
  % compute support directions:
  \pgf@xa=\pgf@plottension\pgf@xa%
  \pgf@ya=\pgf@plottension\pgf@ya%
  % first marshal:
  \pgf@process{\pgf@plot@curveto@second}%
  \pgf@xb=\pgf@x%
  \pgf@yb=\pgf@y%
  \pgf@xc=\pgf@x%
  \pgf@yc=\pgf@y%
  \advance\pgf@xb by-\pgf@xa%
  \advance\pgf@yb by-\pgf@ya%
  \advance\pgf@xc by\pgf@xa%
  \advance\pgf@yc by\pgf@ya%
  \@ifundefined{MCheatOpa}{}{%
  \pgf@plotstreamspecial{\pgfsetstrokeopacity{\MCheatOpa}}}
  \edef\pgf@marshal{\noexpand\pgfsetstrokeopacity{\noexpand\MCheatOpa}
  \noexpand\pgfpathcurveto{\noexpand\pgf@plot@curveto@first@support}%
    {\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}{\noexpand\pgf@plot@curveto@second}
    \noexpand\pgfusepathqstroke
    \noexpand\pgfpathmoveto{\noexpand\pgf@plot@curveto@second}}%
  {\pgf@marshal}%
  %\pgfusepathqstroke%
  % Prepare next:
  \global\let\pgf@plot@curveto@first=\pgf@plot@curveto@second%
  \global\let\pgf@plot@curveto@second=\pgf@plot@curveto@current%
  \xdef\pgf@plot@curveto@first@support{\noexpand\pgfqpoint{\the\pgf@xc}{\the\pgf@yc}}%
}

\def\pgf@plot@curveto@handler@spherical@finish{%
  \ifpgf@plot@started%
    \pgfpathcurveto{\pgf@plot@curveto@first@support}{\pgf@plot@curveto@second}{\pgf@plot@curveto@second}%
  \fi%
}
\makeatother


\begin{document}
\pgfmathsetmacro{\RadiusSphere}{3}


\begin{tikzpicture}
\shade[name path=sphere,ball color = gray!40, opacity = 0.5] 
plot[smooth,domain=-180:180] ({\RadiusSphere*cos(\x)},{\RadiusSphere*sin(\x)});


\tdplotsetmaincoords{42}{205}
\begin{scope}[tdplot_main_coords,samples=60]
%
% \draw[-latex,orange] (0,0,0) -- (z spherical cs: radius=\RadiusSphere,
% phi={\tdplotmainphi-90},theta={90-\tdplotmaintheta});
% \draw[-latex] (0,0,0) -- (\RadiusSphere,0,0) node[below]{$x$};
% \draw[-latex] (0,0,0) -- (0,\RadiusSphere,0) node[left]{$y$};
% \draw[-latex] (0,0,0) -- (0,0,\RadiusSphere) node[left]{$z$};

\draw plot[spherical smooth,variable=\x,domain=-180:180] 
(z spherical cs: radius=\RadiusSphere,phi={50*sin(\x)},theta={40*cos(\x)});
% I modified the plot handler in order to change the opacity along the paths
% therefore I need to redraw it if I want to use it in fills :-(
\path[name path=circle,fill=blue,opacity=0.2]  plot[smooth,variable=\x,domain=-180:180] 
(z spherical cs: radius=\RadiusSphere,phi={50*sin(\x)},theta={40*cos(\x)});

% main technical challenge: TikZ finds tons of intersections instead of 2
% \draw[red, name intersections={of=sphere and circle,name=i, total=\t}] 
% \foreach \s in {1,...,\t}{node[fill,circle,scale=0.3,label=above:\s] at (i-\s) {}} 
% \pgfextra{\typeout{\t}};

% 
\path [%draw,yellow,ultra thick,opacity=1,
    fill=blue,opacity=0.4,
    name path=visible surface,
    intersection segments={
        of=circle and sphere,
        sequence={A5--B0[reverse]--B7[reverse]}
    }];

\end{scope}
\end{tikzpicture}
\end{document}

enter image description here

You can increase the samples and change the viewing angle, but this comes at the prize that the number of intersections changes and you have to find the relevant paths again. For instance, for

\shade[name path=sphere,ball color = gray!40, opacity = 0.5] 
plot[smooth,domain=-180:180,samples=120] ({\RadiusSphere*cos(\x)},{\RadiusSphere*sin(\x)}); % now 120 samples
\tdplotsetmaincoords{52}{200}

the number of intersections found by TikZ is 5 and you need to do

  \path [%draw,yellow,ultra thick,opacity=1,
    fill=blue,opacity=0.4,
    name path=visible surface,
    intersection segments={
        of=circle and sphere,
        sequence={A4--B0[reverse]--B5[reverse]}
    }];

to get

enter image description here

If you get sick of adjusting these things by hand, switch to asymptote ;-)

MINI-UPDATE: A first version without fillbetween.

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{intersections,decorations.markings}
\usepackage{tikz-3dplot}
\tikzset{endmark/.style={postaction={decorate,decoration={markings,
     mark=at position 0 with {\coordinate (X0); },
     mark=at position 1 with {\coordinate (X1); }}}}}

\pgfkeys{/tikz/.cd,
    hidden opacity/.store in=\HiddenOpacity,
    hidden opacity=0.3,
}

\makeatletter

\xdef\prevhidden@toggle{0}
\xdef\spherical@plot@start{0}
% the spherical coordinates are from https://tex.stackexchange.com/a/375604/121799
% but the routine got modified to
% (i) decide whether a point is "visible" or "hidden"
% (ii) 

%along z axis
\define@key{z sphericalkeys}{radius}{\def\myradius{#1}}
\define@key{z sphericalkeys}{theta}{\def\mytheta{#1}}
\define@key{z sphericalkeys}{phi}{\def\myphi{#1}}
\tikzdeclarecoordinatesystem{z spherical}{% 
    \setkeys{z sphericalkeys}{#1}%
    \pgfmathsetmacro{\Xtest}{cos(90-\tdplotmaintheta)*cos(\tdplotmainphi-90)*cos(\mytheta)*cos(\myphi)
    +cos(90-\tdplotmaintheta)*sin(\tdplotmainphi-90)*cos(\mytheta)*sin(\myphi)
    +sin(90-\tdplotmaintheta)*sin(\mytheta)}
    % \Xtest is the projection of the coordinate on the normal vector of the visible plane
    \pgfmathsetmacro{\ntest}{ifthenelse(\Xtest<0,0,1)}
    \ifnum\ntest=0
      \xdef\MCheatOpa{\HiddenOpacity}
      %\typeout{point\space hidden}
      \ifnum\prevhidden@toggle=1 % previous point was also hidden
          \xdef\hid@path{\hid@path (z spherical cs: radius={\myradius},phi={\myphi},theta={\mytheta})}
        \else % finish visible path
         \ifnum\spherical@plot@start=1 % this is the first point of the plot
          %\typeout{start}
         \else
          \ifnum\thevis@paths=0
            \xdef\lst@vis@paths{{\vis@path}} 
           \else
            \xdef\lst@vis@paths{\lst@vis@paths,{\vis@path}} 
          \fi
          \stepcounter{vis@paths}
         \fi  % and start a new hidden path      
         \xdef\hid@path{(z spherical cs: radius={\myradius},phi={\myphi},theta={\mytheta})}
        \fi
        \xdef\prevhidden@toggle{1}
    \else
        \xdef\MCheatOpa{1}
        %\typeout{point\space visible}
        \ifnum\prevhidden@toggle=0 % previous point was also visible
           \xdef\vis@path{\vis@path (z spherical cs: radius={\myradius},phi={\myphi},theta={\mytheta})}
        \else % finish hidden path
          \ifnum\spherical@plot@start=1
           %\typeout{start}
          \else
           \ifnum\thehid@paths=0
             \xdef\lst@hid@paths{{\hid@path}} 
           \else
             \xdef\lst@hid@paths{\lst@hid@paths,{\hid@path}} 
           \fi 
           \stepcounter{hid@paths}
          \fi % and start a new visible path
          \xdef\vis@path{(z spherical cs: radius={\myradius},phi={\myphi},theta={\mytheta})}
        \fi
        \xdef\prevhidden@toggle{0}
    \fi
    %\typeout{\mytheta,\tdplotmaintheta;\myphi,\tdplotmainphi:\ntest}
    \pgfpointxyz{\myradius*cos(\mytheta)*cos(\myphi)}{%
    \myradius*cos(\mytheta)*sin(\myphi)}{\myradius*sin(\mytheta)}}

%%%%%%%%%%%%%%%%%
% define "new" plot handler
\newcounter{vis@paths}
\newcounter{hid@paths}
\tikzoption{spherical smooth}[]{\xdef\lst@vis@paths{}
\xdef\lst@hid@paths{}
\xdef\hid@path{}
\xdef\vis@path{}
\let\tikz@plot@handler=\pgfplothandlersphericalcurveto}


\pgfdeclareplothandler{\pgfplothandlersphericalcurveto}{}{%
  point macro=\pgf@plot@curveto@handler@spherical@initial,
  jump macro=\pgf@plot@smooth@next@spherical@moveto,
  end macro=\pgf@plot@curveto@handler@spherical@finish
}

\def\pgf@plot@smooth@next@spherical@moveto{%
  \pgf@plot@curveto@handler@spherical@finish%
  \global\pgf@plot@startedfalse%
  \global\let\pgf@plotstreampoint\pgf@plot@curveto@handler@spherical@initial%
}

\def\pgf@plot@curveto@handler@spherical@initial#1{%
  \pgf@process{#1}%
  \ifx\tikz@textcolor\pgfutil@empty%
  \else
  \pgfsetstrokecolor{\tikz@textcolor}
  \fi
  \setcounter{vis@paths}{0}
  \setcounter{hid@paths}{0}
  \xdef\spherical@plot@start{1}
  \pgf@xa=\pgf@x%
  \pgf@ya=\pgf@y%
  \pgf@plot@first@action{\pgfqpoint{\pgf@xa}{\pgf@ya}}%
  \xdef\pgf@plot@curveto@first{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}%
  \global\let\pgf@plot@curveto@first@support=\pgf@plot@curveto@first%
  \global\let\pgf@plotstreampoint=\pgf@plot@curveto@handler@spherical@second%
}

\def\pgf@plot@curveto@handler@spherical@second#1{%
  \xdef\spherical@plot@start{0}
  \pgf@process{#1}%
  \xdef\pgf@plot@curveto@second{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}%
  \global\let\pgf@plotstreampoint=\pgf@plot@curveto@handler@spherical@third%
  \global\pgf@plot@startedtrue%
}

\def\pgf@plot@curveto@handler@spherical@third#1{%
  \pgf@process{#1}%
  \xdef\pgf@plot@curveto@current{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}%
  % compute difference vector:
  \pgf@xa=\pgf@x%
  \pgf@ya=\pgf@y%
  \pgf@process{\pgf@plot@curveto@first}
  \advance\pgf@xa by-\pgf@x%
  \advance\pgf@ya by-\pgf@y%
  % compute support directions:
  \pgf@xa=\pgf@plottension\pgf@xa%
  \pgf@ya=\pgf@plottension\pgf@ya%
  % first marshal:
  \pgf@process{\pgf@plot@curveto@second}%
  \pgf@xb=\pgf@x%
  \pgf@yb=\pgf@y%
  \pgf@xc=\pgf@x%
  \pgf@yc=\pgf@y%
  \advance\pgf@xb by-\pgf@xa%
  \advance\pgf@yb by-\pgf@ya%
  \advance\pgf@xc by\pgf@xa%
  \advance\pgf@yc by\pgf@ya%
  \edef\pgf@marshal{\noexpand\pgfsetstrokeopacity{\noexpand\MCheatOpa}
  \noexpand\pgfpathcurveto{\noexpand\pgf@plot@curveto@first@support}%
    {\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}{\noexpand\pgf@plot@curveto@second}
    \noexpand\pgfusepathqstroke
    \noexpand\pgfpathmoveto{\noexpand\pgf@plot@curveto@second}}%
  {\pgf@marshal}%
  %\pgfusepathqstroke%
  % Prepare next:
  \global\let\pgf@plot@curveto@first=\pgf@plot@curveto@second%
  \global\let\pgf@plot@curveto@second=\pgf@plot@curveto@current%
  \xdef\pgf@plot@curveto@first@support{\noexpand\pgfqpoint{\the\pgf@xc}{\the\pgf@yc}}%
}

\def\pgf@plot@curveto@handler@spherical@finish{%
  \ifpgf@plot@started%
    \pgfpathcurveto{\pgf@plot@curveto@first@support}{\pgf@plot@curveto@second}{\pgf@plot@curveto@second}%
  \fi%
  \ifnum\prevhidden@toggle=1
    \xdef\lstvispaths{\lst@vis@paths}
    \ifx\hid@path\empty
    \else
      %\typeout{closing\space hidden}
      \ifx\lst@hid@paths\empty
       \xdef\lst@hid@paths{{\hid@path}}
      \else
       \xdef\lst@hid@paths{\lst@hid@paths,{\hid@path}}
      \fi 
      \foreach \X [count=\Y] in \lst@hid@paths
      {\xdef\my@len{\Y}}
      %\typeout{\my@len\space hidden\space patches}
      \xdef\lsthidpaths{}     
      \foreach \X [count=\Y] in \lst@hid@paths
      {\ifnum\Y=1 % save the first stretch
        \xdef\tmppath{\X}
        \ifnum\my@len=1
          \xdef\lsthidpaths{{\X}}
        \fi  
        \else
        \ifnum\Y=\my@len       
          \ifnum\Y=2
           \xdef\lsthidpaths{{\X\tmppath}}
          \else
           \xdef\lsthidpaths{\lsthidpaths,{\X\tmppath}}
          \fi
           %\typeout{adding:{\tmppath\X}}
           %\typeout{result:\lsthidpaths}
        \else
         \xdef\lsthidpaths{\lsthidpaths,{\X}}
        \fi
       \fi}
       %\typeout{hidden\space paths:\lst@hid@paths}
    \fi
  \else
    \xdef\lsthidpaths{\lst@hid@paths}
    \ifx\vis@path\empty
    \else
      %\typeout{closing\space visible}
      \xdef\lst@vis@paths{\lst@vis@paths,{\vis@path}}
      \foreach \X [count=\Y] in \lst@vis@paths
      {\xdef\my@len{\Y}}
      %\typeout{\my@len\space hidden\space coordinates:\vis@path}
      \xdef\lstvispaths{}     
      \foreach \X [count=\Y] in \lst@vis@paths
      {\ifnum\Y=1 % save the first stretch
        \xdef\tmppath{\X}
        \ifnum\my@len=1
          \xdef\lstvispaths{{\X}}
        \fi  
        \else
        \ifnum\Y=\my@len       
         \ifnum\my@len=1
          \xdef\lstvispaths{{\X}}
         \else
          \ifnum\Y=2
           \xdef\lstvispaths{{\X\tmppath}}
          \else
           \xdef\lstvispaths{\lstvispaths,{\X\tmppath}}
          \fi
           %\typeout{adding:{\tmppath\X}}
           %\typeout{result:\lstvispaths}
         \fi     
        \else
         \xdef\lstvispaths{\lstvispaths,{\X}}
        \fi
       \fi}
    \fi
  \fi 
}
\makeatother

\newcommand{\FillVisibleSurfaces}[1][]{\xdef\numvis{0}
\foreach \X [count=\Y] in \lstvispaths
{\ifx\X\empty
\else
\xdef\numvis{\Y}
\fi}
\ifnum\numvis=0%
\else
\foreach \X [count=\Y] in \lstvispaths
{%\typeout{processing\space \X}
\path[endmark] plot[tdplot_main_coords,samples=60] coordinates {\X};
\fill[#1] let
\p1=(X0),\p2=(X1),\n1={mod(atan2(\y1,\x1)+720,360)},\n2={mod(atan2(\y2,\x2)+720,360)} 
in % \pgfextra{\typeout{visible:\space start:\n1,end:\n2}}
plot[tdplot_main_coords,samples=60] coordinates {\X} -- (X1) arc(\n2:\n1:\RadiusSphere);
}
\fi
}
\newcommand{\FillHiddenSurfaces}[1][]{\xdef\numhid{0}
\foreach \X [count=\Y] in \lsthidpaths
{\ifx\X\empty
\else
\xdef\numhid{\Y}
\fi}
\ifnum\numhid=0%
\else
\foreach \X [count=\Y] in \lsthidpaths
{
\path[endmark] plot[tdplot_main_coords,samples=60] coordinates {\X};
\fill[#1] let
\p1=(X0),\p2=(X1),\n1={mod(atan2(\y1,\x1)+720,360)},\n2={mod(atan2(\y2,\x2)+720,360)} 
in %\pgfextra{\typeout{hidden\space start:\n1,end:\n2}}
plot[tdplot_main_coords,samples=60] coordinates {\X} -- (X1) arc(\n2:\n1:\RadiusSphere)
--cycle;
}
\fi
}


\begin{document}
\pgfmathsetmacro{\RadiusSphere}{3}

\foreach \X in {0,10,...,350}
{\begin{tikzpicture}
\shade[name path=sphere,ball color = gray!40, opacity = 0.5] 
plot[smooth,domain=-180:180,samples=120] ({\RadiusSphere*cos(\x)},{\RadiusSphere*sin(\x)});


\tdplotsetmaincoords{110}{\X}
\begin{scope}[tdplot_main_coords,samples=60]
%
% \draw[-latex,orange] (0,0,0) -- (z spherical cs: radius=\RadiusSphere,
% phi={\tdplotmainphi-90},theta={90-\tdplotmaintheta});
% \draw[-latex] (0,0,0) -- (\RadiusSphere,0,0) node[below]{$x$};
% \draw[-latex] (0,0,0) -- (0,\RadiusSphere,0) node[left]{$y$};
% \draw[-latex] (0,0,0) -- (0,0,\RadiusSphere) node[left]{$z$};

\draw plot[spherical smooth,variable=\x,domain=-180:180] 
(z spherical cs: radius=\RadiusSphere,phi={50*sin(\x)},theta={40*cos(\x)});
\end{scope}
% note that these commands need to be placed *outside* the tdplot_main_coords scope
\FillHiddenSurfaces[blue,opacity=0.2]
\FillVisibleSurfaces[blue,opacity=0.5]
\end{tikzpicture}
}
\end{document}

enter image description here

I believe to have solved several issues with tons of \if statements. However, clearly the above animation does not seem right. (Notice that the extra bits appearing on the bottom and on the right are due to the conversion to gif, they are not present in the pdf.) But this is because on a sphere the "interior" of a closed surface is not well defined, rather there are two options. And always filling the smaller area does also not seem right because people may want to fill the larger area. Does anybody reading this have an idea how to define that mathematically or even in TikZ?

Finally I'd like to present a "cheating" solution, which is almost as powerful as the above but does not require to modify the plot handler and works with any \draw commands, not just plot. Just draw the thing twice and let the coordinate system decide whether the point is visible. If not, return a point on the boundary of the circle defining the sphere.

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{intersections,decorations.markings}
\usepackage{tikz-3dplot}

\makeatletter

%along z axis
\define@key{z sphericalkeys}{radius}{\def\myradius{#1}}
\define@key{z sphericalkeys}{theta}{\def\mytheta{#1}}
\define@key{z sphericalkeys}{phi}{\def\myphi{#1}}
\tikzdeclarecoordinatesystem{z spherical visible}{% 
    \setkeys{z sphericalkeys}{#1}%
    \pgfmathsetmacro{\Xtest}{cos(90-\tdplotmaintheta)*cos(\tdplotmainphi-90)*cos(\mytheta)*cos(\myphi)
    +cos(90-\tdplotmaintheta)*sin(\tdplotmainphi-90)*cos(\mytheta)*sin(\myphi)
    +sin(90-\tdplotmaintheta)*sin(\mytheta)}
    % \Xtest is the projection of the coordinate on the normal vector of the visible plane
    \pgfmathsetmacro{\ntest}{ifthenelse(\Xtest<0,0,1)}
    \ifnum\ntest=0
      \pgfmathsetmacro{\myx}{\myradius*cos(\mytheta)*cos(\myphi)*\raarot
      +\myradius*cos(\mytheta)*sin(\myphi)*\rabrot+\myradius*sin(\mytheta*\racrot}
      \pgfmathsetmacro{\myy}{\myradius*cos(\mytheta)*cos(\myphi)*\rbarot
      +\myradius*cos(\mytheta)*sin(\myphi)*\rbbrot+\myradius*sin(\mytheta*\rbcrot}  
      \pgfpoint{\RadiusSphere*cos(atan2(\myy,\myx))*1cm}{\RadiusSphere*sin(atan2(\myy,\myx))*1cm}
    \else
      \pgfpointxyz{\myradius*cos(\mytheta)*cos(\myphi)}{%
      \myradius*cos(\mytheta)*sin(\myphi)}{\myradius*sin(\mytheta)}
    \fi
}

\tikzdeclarecoordinatesystem{z spherical invisible}{% 
    \setkeys{z sphericalkeys}{#1}%
    \pgfmathsetmacro{\Xtest}{cos(90-\tdplotmaintheta)*cos(\tdplotmainphi-90)*cos(\mytheta)*cos(\myphi)
    +cos(90-\tdplotmaintheta)*sin(\tdplotmainphi-90)*cos(\mytheta)*sin(\myphi)
    +sin(90-\tdplotmaintheta)*sin(\mytheta)}
    % \Xtest is the projection of the coordinate on the normal vector of the visible plane
    %\typeout{\raarot,\rbarot,\rabrot,\rbbrot,\racrot, \rbcrot}
    \pgfmathsetmacro{\ntest}{ifthenelse(\Xtest<0,0,1)}
    \ifnum\ntest=1
      \pgfmathsetmacro{\myx}{\myradius*cos(\mytheta)*cos(\myphi)*\raarot
      +\myradius*cos(\mytheta)*sin(\myphi)*\rabrot+\myradius*sin(\mytheta*\racrot}
      \pgfmathsetmacro{\myy}{\myradius*cos(\mytheta)*cos(\myphi)*\rbarot
      +\myradius*cos(\mytheta)*sin(\myphi)*\rbbrot+\myradius*sin(\mytheta*\rbcrot}  
      \pgfpoint{\RadiusSphere*cos(atan2(\myy,\myx))*1cm}{\RadiusSphere*sin(atan2(\myy,\myx))*1cm}
    \else
      \pgfpointxyz{\myradius*cos(\mytheta)*cos(\myphi)}{%
      \myradius*cos(\mytheta)*sin(\myphi)}{\myradius*sin(\mytheta)}
    \fi
}

%%%%%%%%%%%%%%%%%

\makeatother
% decoration
\begin{document}
\pgfmathsetmacro{\RadiusSphere}{3}

\foreach \X in {0,10,...,350}
{\begin{tikzpicture}
\path[use as bounding box] ({-1.2*\RadiusSphere},{-1.2*\RadiusSphere}) rectangle
({1.2*\RadiusSphere},{1.2*\RadiusSphere});
\shade[ball color = gray!40, opacity = 0.5]  (0,0) circle (\RadiusSphere);

\tdplotsetmaincoords{110}{\X}
\begin{scope}[tdplot_main_coords,samples=60]
%
% \draw[-latex,orange] (0,0,0) -- (z spherical cs: radius=\RadiusSphere,
% phi={\tdplotmainphi-90},theta={90-\tdplotmaintheta});
% \draw[-latex] (0,0,0) -- (\RadiusSphere,0,0) node[below]{$x$};
% \draw[-latex] (0,0,0) -- (0,\RadiusSphere,0) node[left]{$y$};
% \draw[-latex] (0,0,0) -- (0,0,\RadiusSphere) node[left]{$z$};
\pgfmathtruncatemacro{\Dis}{ifthenelse(\X<50,1,0)+ifthenelse(\X>130,1,0)}
\ifnum\Dis=0
\else
\draw[opacity=0.3,fill opacity=0.2,fill=blue] plot[smooth,variable=\x,domain=-180:180] 
(z spherical invisible cs: radius=\RadiusSphere,phi={50*sin(\x)},theta={40*cos(\x)});
\fi

\pgfmathtruncatemacro{\Dis}{ifthenelse(\X<230,1,0)+ifthenelse(\X>320,1,0)}
\ifnum\Dis=0
\else
\draw[fill opacity=0.5,fill=blue] plot[smooth,variable=\x,domain=-180:180] 
(z spherical visible cs: radius=\RadiusSphere,phi={50*sin(\x)},theta={40*cos(\x)});
\fi

%(z spherical cs: {\RadiusSphere},{50*sin(\x)},{40*cos(\x)});
\end{scope}

\end{tikzpicture}
}
\end{document}

enter image description here

This works fine unless the hidden surface contains the center of the circle. In this case one should just not draw that surface. Perhaps there is a way to avoid removing these surfaces by hand.

  • One way could be to use the convexity of the surface but that’s not really a general solution (both area could be concave for example). I think the only universal way to discriminate surfaces is to provide an internal (are external) point. – kipgon Jun 24 '18 at 14:01
  • @kipgon I agree, that's the correct mathematical definition. But I also guess it is an unsolved problem in TikZ to tell whether a point is inside a given surface. (I have seen a metapost solution, though.) – user121799 Jun 24 '18 at 14:04
  • @kipgon I found another, arguably much simpler way. It is not perfect (yet), though. – user121799 Jun 24 '18 at 14:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.