I threw this together pretty quickly, The idea is that the inverse stereographic projection of a line always is a circle which intersects the north pole.
This is tikz, so it will not handle large numbers, nor will it compute heavy duty calculations fast.
I would recommend doing the math in lua, and outputting to something like tikz or metapost.
This is my best way to do it now, but when I develop better lua infrastructure, then this will be obsolete. For instance, there is no perspective, and we cannot set an arbitrary camera position.
Just to help you get started:
\documentclass[
tikz
,border = 3.14mm
]{standalone}
\usetikzlibrary{arrows.meta}
\usepackage{tikz-3dplot}
% #1 - x
% #2 - y
% returns xi
\pgfmathdeclarefunction{inversestereographicprojectionxi}{2}{%
\pgfmathparse{2*#1/(1+(#1)^2+(#2)^2)}%
}
% #1 - x
% #2 - y
% returns eta
\pgfmathdeclarefunction{inversestereographicprojectioneta}{2}{%
\pgfmathparse{2*#2/(1+(#1)^2+(#2)^2)}%
}
% #1 - x
% #2 - y
% returns zeta
\pgfmathdeclarefunction{inversestereographicprojectionzeta}{2}{%
\pgfmathparse{(-1+(#1)^2+(#2)^2)/(1+(#1)^2+(#2)^2)}%
}
\begin{document}
\foreach \y in {1,...,24} {
\pgfmathsetmacro{\azimuth}{100}
\pgfmathsetmacro{\elevation}{30}
\pgfmathsetmacro{\x}{1.5}
\pgfmathsetmacro{\y}{\y}
\tdplotsetmaincoords{90-\elevation}{\azimuth}
\begin{tikzpicture}[tdplot_main_coords,very thin,scale = 2]
\useasboundingbox (-3,-3) rectangle (3,3);
% x, y and z axes (pre-clip)
\draw[-latex]
(-2.5,0,0) -- (2.5,0,0)
node[pos=1,below]
{$\scriptstyle x,\xi,\mbox{\scriptsize Re}(z)$};
\draw[-latex]
(0,-1.5,0) -- (0,1.5,0)
node[pos=1,below]
{$\scriptstyle y,\eta,\mbox{\scriptsize Im}(z)$};
\draw[-latex]
(0,0,-1.5) -- (0,0,1.5);
\begin{scope}
% clip and blank out sphere
\clip[
tdplot_screen_coords
,postaction = {
%tdplot_screen_coords
,fill
,white
}
] (0,0) circle [radius = {1}];
% x, y and z axes (post-clip)
\draw[densely dashed]
(-2.5,0,0) -- (1,0,0);
\draw[densely dashed]
(0,-1.5,0) -- (0,1,0);
\draw[densely dashed]
(0,0,-1.5) -- (0,0,1);
% outer circle
\draw[tdplot_screen_coords]
(0,0) circle [radius = {1}];
\draw[densely dashed]
(\azimuth:1) arc [
start angle = {\azimuth}
,end angle = {\azimuth+180}
,radius = {1}
];
\draw (\azimuth:1) arc [
start angle = {\azimuth}
,end angle = {\azimuth-180}
,radius = {1}
];
% origin
\coordinate (O) at (0,0);
% point on sphere
\coordinate (P') at
(
{inversestereographicprojectionxi(\x,\y)}
,{inversestereographicprojectioneta(\x,\y)}
,{inversestereographicprojectionzeta(\x,\y)}
)
;
% stereographic projection
% of point on sphere
\coordinate (P) at (\x,\y);
% north pole
\coordinate (N) at (0,0,1);
% constructions which don't exceed the clip
\draw[densely dashed,domain = -20:20,variable = \t,samples=100] plot (
{inversestereographicprojectionxi(\x,\t)}
,{inversestereographicprojectioneta(\x,\t)}
,{inversestereographicprojectionzeta(\x,\t)}
) -- cycle;
\draw
(N) -- (0,0,1.5)
(1,0,0) -- (2.5,0,0)
(0,1,0) -- (0,1.5,0)
;
\end{scope}
% constructions which exceed the clip
\draw
(N) -- (P)
;
% points
\pgfmathsetmacro{\pointradius}{0.025}
\begin{scope}[tdplot_screen_coords]
\fill
(P') circle[radius = \pointradius]
node[above right]{$\scriptstyle P$};
\fill
(P) circle[radius = \pointradius]
node[right]{$\scriptstyle z$};
\fill
(N) circle[radius = \pointradius]
node[above left]{$\scriptstyle N$};
\end{scope}
\end{tikzpicture}
}
\end{document}
I will attach the gif, but I sincerely recommend that you play around with it yourself and make any aesthetic improvements that you see fit. This is a conceptual proof-of-concept construct, and could definitely be improved for formal teaching. I would call it "good enough" as is, but it is not what I would produce if I were going all in.
The gif also uses a low dpi and is a bit choppy due to only taking 24 samples. I just don't want to sell it as something it's not, hence my heavy disclaimer here.
