# Format line properties when it appears behind another object in TikZ

I am using the tikz-3dplot-circleofsphere package to draw circles of a sphere using tikz-3dplot. Consider the following MWE from the documentation:

\documentclass{standalone}
\usepackage{tikz-3dplot-circleofsphere}
\begin{document}
\centering
\def\r{3}
\tdplotsetmaincoords{60}{125}
\begin{tikzpicture}[tdplot_main_coords]
\draw[tdplot_screen_coords,thin,black!30] (0,0,0) circle (\r);
\foreach \a in {-75,-60,...,75}
{\tdplotCsDrawLatCircle[thin,black!29]{\r}{\a}}
\foreach \a in {0,15,...,165}
{\tdplotCsDrawLonCircle[thin,black!29]{\r}{\a}}

\tdplotCsDrawGreatCircle[red,thick,/.style={opacity=0}]{4}{105}{-23.5}
\end{tikzpicture}
\end{document}


which generates the following image

How can I get the dashed red line to appear when it is only behind the sphere. I.e. in this case I have chosen a radii for the red circle that is larger than that of the sphere.

• would this help? Jun 28, 2020 at 14:49
• @M.AlJumaily, the linked post appears to address exactly what I'm asking, though It's not clear how to use it in this case.
– Sid
Jun 28, 2020 at 23:24

Updated version

My answer is not the shortest and maybe some of the elements that I use are already defined in a library; I am not aware of.

The drawing is made in three steps: 1) the 3D point of view, 2) the sphere and 3) the circles (two on the sphere and a third one lying in the plane of the small circle on the sphere and having the same center). Now the sphere is constructed through meridians and parallels, i.e. circles, too.

Every circle is constructed as a pic to have a cleaner code.

The hidden points on the sphere are detected by computing the inner product between the viewer vector and the position vector of the point. See the function opacityOnS.

The points inside the sphere or behind the sphere are detected by a mixture of inner products and norm comparisons. See the function behindS.

Some more explanations about the elements are used in these three steps.

1. In TikZ, the coordinate plane Oxy corresponds to the screen and Oz points outside the screen, towards the viewer.

2. The observer is represented by the vector defined by \tox, \toy, and \toz introduced through the key view with arguments the longitude and the latitude. When both are zero, the vector is (0,0,1).

3. The various circles have similar codes as pic objects. I preferred to redefine them depending on the number of arguments needed. But all these functions make the file tikZSphere.sty rather long.

4. The opacity and its opposite are controlled through the keys unseenS and seenS.

5. The sphere radius is controlled through the key radiusS. The default value is 1.

Remark. I don't address the question regarding how two circles on the sphere intersect. I imagine a solution, but it is clumsy; I need to define the circles (or parts of them) as whole elements (through the \path command) to be able to use the option name path afterward. Maybe some developments as those proposed by user121799 in Embed a graph on a Sphere with TikZ? would be useful, but they are above my knowledge.

The code

\documentclass[11pt, border=1.5cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc, math}
\usepackage{tikZSphere}

\begin{document}

% meridians and parallels
\begin{scope}[black!50, seenS=.7, unseenS=.25, very thin]
\foreach \l in {15, 30, ..., 360}{ \path pic {meridian={\l}}; }
\foreach \l in {-75, -60, ..., 75}{ \path pic {parallel={\l}}; }
\end{scope}

\path[unseenS=.32] pic[magenta, thick] {bigCircleOnS={80:70}};
\path[unseenS=.32] pic[blue, thick] {circleOnS={80:70 at distance -.85}};
\path[unseenS=.25] pic[blue, thick] {circle3d={80:70:4 at distance -.85}};

\path[seenS=.9] pic[orange] {axesForS={1.75}};
\end{tikzpicture}
\end{document}


And the file tikZsphere.sty:

\tikzset{%
view/.style 2 args={%
z={({-sin(#1)}, {-cos(#1)*sin(#2)})},
x={({cos(#1)}, {-sin(#1)*sin(#2)})},
y={(0, {cos(#2)})},
evaluate={%
\tox={sin(#1)*cos(#2)};
\toy={sin(#2)};
\toz={cos(#1)*cos(#2)};
}
}
}

\pgfkeys{/tikz/.cd,
seenS/.store in=\seenS,
seenS=1
}
\pgfkeys{/tikz/.cd,
unseenS/.store in=\unseenS,
unseenS=.2
}
\pgfkeys{/tikz/.cd,
}
\pgfkeys{/tikz/.cd,
samplesCOnS/.store in=\samplesCOnS,
samplesCOnS=36
}

\tikzmath{%  opacities and samples
function opacityOnS(\px, \py, \pz) {%
\res = \px*\tox + \py*\toy + \pz*\toz; % inner product of posV and obsrerver
if \res>0 then {return \seenS;} else {return \unseenS;};
};
function behindS(\px, \py, \pz, \r) {%
\sppo = \px*\tox + \py*\toy + \pz*\toz; % inner product of pos.vect and obsrerver
\npsq = \px*\px + \py*\py + \pz*\pz; % norm of pos.vect^2
\nvsq = \npsq -\sppo*\sppo; % norm of pos,vect's projection^2
if \sppo<0 then {%
if \nvsq -\r*\r<.05 then {return \unseenS;} else {return \seenS;};
} else {%
if \npsq -\r*\r<.05 then {return \unseenS;} else {return \seenS;};
};
};
}

\tikzset{
pics/axesForS/.style={%
code={
\tikzmath{%
real \b;
}
\foreach \i in {.1, .2, ..., \b}{%
\draw[opacity={behindS(\i-.05, 0, 0, \radiusS)}] (\i-.1, 0, 0) -- (\i, 0, 0);
\path (\b, 0, 0) ++(.4, 0, 0) node[scale=.9] {$x$};
\draw[opacity={behindS(0, \i-.05, 0, \radiusS)}] (0, \i-.1, 0) -- (0, \i, 0);
\path (0, \b, 0) ++(0, .4, 0) node[scale=.9] {$y$};
\draw[opacity={behindS(0, 0, \i-.05, \radiusS)}] (0, 0, \i-.1) -- (0, 0, \i);
\path (0, 0, \b) ++(0, 0, .4) node[scale=.9] {$z$};
}
}
},
pics/meridian/.style = {% longitude, number of points
code={
\tikzmath{
real \pax, \pay, \paz, \pbx, \pby, \pbz, \cosl, \sinl;
integer \N;
\N = int(\samplesCOnS/2);
}
\foreach \k [evaluate=\k as \bz using {180*(\k/\N -.5)},
evaluate=\k as \az using {180*((\k-1)/\N -.5)}] in {1, ..., \N}{
\tikzmath{
\pax = \cosl*cos(\az);
\paz = \sinl*cos(\az);
\pbx = \cosl*cos(\bz);
\pbz = \sinl*cos(\bz);
}
\draw[opacity={opacityOnS(\pax, \pay, \paz)}]
(\pax, \pay, \paz) -- (\pbx, \pby, \pbz);
}
}
},
pics/parallel/.style = {% latitude, number of points
code={
\tikzmath{
integer \N;
real \pax, \pay, \paz, \pbx, \pby, \pbz, \cosl, \sinl;
}
\foreach \j [evaluate=\j as \by using {360*(\j/\N)},
evaluate=\j as \ay using {360*((\j-1)/\N)}] in {1, ..., \N}{
\tikzmath{
\pax = cos(\ay)*\cosl;
\paz = sin(\ay)*\cosl;
\pbx = cos(\by)*\cosl;
\pbz = sin(\by)*\cosl;
}
\draw[opacity={opacityOnS(\pbx, \sinl, \pbz)}]
(\pax, \sinl, \paz) -- (\pbx, \sinl, \pbz);
}
}
}
}

%%%% other circles

\tikzmath{
function Cx(\t) {
return \r*\ux*cos(\t) + \r*\vx*sin(\t) + \d*\nx;
};
function Cy(\t) {
return \r*\vy*sin(\t) + \d*\ny;
};
function Cz(\t) {
return \r*\uz*cos(\t) + \r*\vz*sin(\t) + \d*\nz;
};
}
\tikzset{
pics/circle3d/.style args={#1:#2:#3 at distance #4}{%
code={
\tikzmath{
integer \N;
\nx = cos(#2)*sin(#1);
\ny = sin(#2);
\nz = cos(#2)*cos(#1);
\ux = cos(#1);
\uz = -sin(#1);
\vx = -sin(#2)*sin(#1);
\vy = cos(#2);
\vz = -sin(#2)*cos(#1);
\d = #4;
\r = #3;
}
\foreach \j [evaluate=\j as \t using {360*(\j/\N)},
evaluate=\j as \s using {360*((\j-1)/\N)}] in {1, ..., \N}{
\tikzmath{
\pax = Cx(\s);
\pay = Cy(\s);
\paz = Cz(\s);
\pbx = Cx(\t);
\pby = Cy(\t);
\pbz = Cz(\t);
}
\draw[opacity={%
(\pax, \pay, \paz) -- (\pbx, \pby, \pbz);
}
}
},
pics/bigCircleOnS/.style args={#1:#2}{%
code={
\tikzmath{
integer \N;
\nx = cos(#2)*sin(#1);
\ny = sin(#2);
\nz = cos(#2)*cos(#1);
\ux = cos(#1);
\uz = -sin(#1);
\vx = -sin(#2)*sin(#1);
\vy = cos(#2);
\vz = -sin(#2)*cos(#1);
\d = 0;
}
\foreach \j [evaluate=\j as \t using {360*(\j/\N)},
evaluate=\j as \s using {360*((\j-1)/\N)}] in {1, ..., \N}{
\tikzmath{
\pax = Cx(\s);
\pay = Cy(\s);
\paz = Cz(\s);
\pbx = Cx(\t);
\pby = Cy(\t);
\pbz = Cz(\t);
}
\draw[opacity={opacityOnS((\pax+\pbx)/2, (\pay+\pby)/2, (\paz+\pbz)/2)}]
(\pax, \pay, \paz) -- (\pbx, \pby, \pbz);
}
}
},
pics/bigCircleOnS/.default={0:90},
pics/circleOnS/.style args={#1:#2 at distance#3}{%
code={
\tikzmath{
integer \N;
\nx = cos(#2)*sin(#1);
\ny = sin(#2);
\nz = cos(#2)*cos(#1);
\ux = cos(#1);
\uz = -sin(#1);
\vx = -sin(#2)*sin(#1);
\vy = cos(#2);
\vz = -sin(#2)*cos(#1);
\d = #3;
}
\foreach \j [evaluate=\j as \t using {360*(\j/\N)},
evaluate=\j as \s using {360*((\j-1)/\N)}] in {1, ..., \N}{
\tikzmath{
\pax = Cx(\s);
\pay = Cy(\s);
\paz = Cz(\s);
\pbx = Cx(\t);
\pby = Cy(\t);
\pbz = Cz(\t);
}
\draw[opacity={opacityOnS((\pax+\pbx)/2, (\pay+\pby)/2, (\paz+\pbz)/2)}]
(\pax, \pay, \paz) -- (\pbx, \pby, \pbz);
}
}
}


The image from the initial version of the answer

• Could you extend the red and purple lines so that their radii are larger than the sphere? I specifically, want to see dashing when the line goes behind the sphere.
– Sid
Nov 14, 2020 at 11:30
• "Yes" for the circles' radii. It is sufficient to apply a homothety in the plane of the circle when the points are defined. Nov 14, 2020 at 16:11
• As for the "going behind" when the curve is not placed on the sphere, this is another story. The program, as it is, cannot handle this situation. Probably, one should consider the segment form a small segment of the curve to the viewer and study the intersection with the sphere... Nov 14, 2020 at 16:14
• Hi @Sid. Working on something else, I came out with a way to answering your question. Are you still interested? The code follows the idea I was talking about in the message above. Feb 12, 2021 at 14:50
• Hi @Daniel, yes I am still interested in the question. Might also be useful for other Stack users. Would you be able to upload it?
– Sid
Feb 13, 2021 at 17:44

This is just for the part about getting the dashed line behind the sphere. I don't know where the tikz-3dplot-circleofsphere is from so I can't test with that. Instead, I've drawn the outline of a sphere (which is an ellipse) and used that.

This uses the spath3 library to split the orbital path where it intersects with the boundary path, and then renders the pieces with different styles.

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

\begin{document}

\begin{tikzpicture}

\path[
rotate=45,
spath/save=boundary
] (0,0) circle[
];

\path[
spath/save=orbital,
y = {(1,0,0)},
x = {(0,0,1)}

\tikzset{
spath/remove empty components={orbital},
spath/split at intersections with={orbital}{boundary},
spath/join components={orbital}{1,4},
spath/get components of={orbital}\cpts
}

\draw[
red,
ultra thick,
dashed,
spath/use=\getComponentOf\cpts{1}
];

\draw[
blue,
ultra thick,
spath/use=boundary];

\draw[
red,
ultra thick,
spath/use=\getComponentOf\cpts{2}
];

\end{tikzpicture}
\end{document}