# Square-tiling a torus

I want to use tikz to draw something similar to this:

That is, we tile the tore with squares, we cut it open, twist it and then glue it back again.

The idea would be to draw 4 torii: a simple square-tiled torus, a cut-open torus, this cut-open torus twisted and then this new tore glued back again.

An example of a cut-open torus would be this:

For the basic torus, I drawed the following:

\begin{tikzpicture}
\useasboundingbox (-3,-1.5) rectangle (3,1.5);
\begin{scope}
\fill[ball color=blue, opacity=0.15] (0,0) ellipse (3 and 1.5);
\draw (0,0) ellipse (3 and 1.5);
\clip (0,-1.8) ellipse (3 and 2.5);
\draw(0,2.2) ellipse (3 and 2.5);
\clip (0,2.2) ellipse (3 and 2.5);
\draw (0,-2.2) ellipse (3 and 2.5);
\fill[white] (0,-2.2) ellipse (3 and 2.5);
\end{scope}
\end{tikzpicture}


However, I dont know how to square-tile it nor do I know how to "cut" it like in the video.

How could I do it? (I think that this could be easier in assymptote than in Tikz but since every other figure I did in this document was based on the torus I drawed in the MWE, I would like to stay in tikz for this drawing.)

• Maybe help: tex.stackexchange.com/a/482503/31034
– user31034
Commented Apr 18, 2019 at 15:24
• I would use asymptote. It is better at rendering 3d graphics than tikz. Commented Apr 27, 2019 at 10:17

To give you a start. This draws the open torus and also has the information when a point is on the visible patch: when the torus parameter v is between vcrit1 and vcrit2, where vcrit1 and vcrit2 depend on the torus parameter u and the view angle theta. More explanations can be found in this post.

\documentclass[tikz,border=3.14mm]{standalone}
\usepackage{tikz-3dplot}
\begin{document}
\tdplotsetmaincoords{60}{0}
\tikzset{declare function={torusx(\u,\v,\R,\r)=cos(\u)*(\R + \r*cos(\v));
torusy(\u,\v,\R,\r)=(\R + \r*cos(\v))*sin(\u);
torusz(\u,\v,\R,\r)=\r*sin(\v);
vcrit1(\u,\th)=atan(tan(\th)*sin(\u));% first critical v value
vcrit2(\u,\th)=180+atan(tan(\th)*sin(\u));% second critical v value
disc(\th,\R,\r)=((pow(\r,2)-pow(\R,2))*pow(cot(\th),2)+%
pow(\r,2)*(2+pow(tan(\th),2)))/pow(\R,2);% discriminant
umax(\th,\R,\r)=ifthenelse(disc(\th,\R,\r)>0,asin(sqrt(abs(disc(\th,\R,\r)))),0);
}}

\begin{tikzpicture}[tdplot_main_coords]
\pgfmathsetmacro{\R}{4}
\pgfmathsetmacro{\r}{1}
\begin{scope}[local bounding box=torus]
\path[tdplot_screen_coords]
({-(\R+\r)*1cm-\pgflinewidth},{-(\r+\R*cos(\tdplotmaintheta))*1cm-\pgflinewidth})
rectangle ({(\R+\r)*1cm+\pgflinewidth},{(\r+\R*cos(\tdplotmaintheta))*1cm+\pgflinewidth});
\clip
plot[smooth,variable=\x,domain={vcrit1(310,\tdplotmaintheta)}:{vcrit2(310,\tdplotmaintheta)},samples=71]
({torusx(310,\x,\R,\r)},{torusy(310,\x,\R,\r)},{torusz(310,\x,\R,\r)})
--
plot[smooth,variable=\x,domain={vcrit2(280,\tdplotmaintheta)}:{vcrit1(280,\tdplotmaintheta)},samples=71]
({torusx(280,\x,\R,\r)},{torusy(280,\x,\R,\r)},{torusz(280,\x,\R,\r)})
-| cycle (torus.south west) rectangle (torus.north east)
;
\draw[thick,fill=gray,even odd rule,fill opacity=0.2]
plot[variable=\x,domain=0:360,smooth,samples=71]
({torusx(\x,vcrit1(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit1(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit1(\x,\tdplotmaintheta),\R,\r)})
plot[variable=\x,
domain={-180+umax(\tdplotmaintheta,\R,\r)}:{-umax(\tdplotmaintheta,\R,\r)},smooth,samples=51]
({torusx(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)})
plot[variable=\x,
domain={umax(\tdplotmaintheta,\R,\r)}:{180-umax(\tdplotmaintheta,\R,\r)},smooth,samples=51]
({torusx(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)})
;
\draw[thick] plot[variable=\x,
domain={-180+umax(\tdplotmaintheta,\R,\r)/2}:{-umax(\tdplotmaintheta,\R,\r)/2},smooth,samples=51]
({torusx(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)});
\end{scope}
\draw[thick]
plot[smooth,variable=\x,domain=0:360,samples=71]
({torusx(310,\x,\R,\r)},{torusy(310,\x,\R,\r)},{torusz(310,\x,\R,\r)})
plot[smooth,variable=\x,domain={vcrit2(280,\tdplotmaintheta)}:{vcrit1(280,\tdplotmaintheta)},samples=71]
({torusx(280,\x,\R,\r)},{torusy(280,\x,\R,\r)},{torusz(280,\x,\R,\r)});
\foreach \X in {60,80,100}
{\draw[thick] plot[smooth,variable=\x,domain=280:-50,samples=71]
({torusx(\x,\X+\x/18,\R,\r)},{torusy(\x,\X+\x/18,\R,\r)},{torusz(\x,\X+\x/18,\R,\r)});}
\end{tikzpicture}
\end{document}


You may want to add a few more grid lines, which can be drawn as in the last loop. You will have to stop them according to their visibility. This can be automatized using the methods of this answer. First an ordinary grid:

\documentclass[tikz,border=3.14mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\tikzset{declare function={torusx(\u,\v,\R,\r)=cos(\u)*(\R + \r*cos(\v));
torusy(\u,\v,\R,\r)=(\R + \r*cos(\v))*sin(\u);
torusz(\u,\v,\R,\r)=\r*sin(\v);
vcrit1(\u,\th)=atan(tan(\th)*sin(\u));% first critical v value
vcrit2(\u,\th)=180+atan(tan(\th)*sin(\u));% second critical v value
vtest(\u,\v,\az,\el)=sin(-vcrit1(\u-\az,\el)+\v);
disc(\th,\R,\r)=((pow(\r,2)-pow(\R,2))*pow(cot(\th),2)+%
pow(\r,2)*(2+pow(tan(\th),2)))/pow(\R,2);% discriminant
umax(\th,\R,\r)=ifthenelse(disc(\th,\R,\r)>0,asin(sqrt(abs(disc(\th,\R,\r)))),0);
}}
\pgfplotsset{visible stretch/.style={restrict expr to domain={vtest(atan2(rawy,rawx),%
ifthenelse(veclen(rawx,rawy)>\R,asin(rawz/\r),180-asin(rawz/\r)),\pgfkeysvalueof{/pgfplots/view/az},\pgfkeysvalueof{/pgfplots/view/el})}{-0.05:1.1}},
hidden stretch/.style={restrict expr to
domain={vtest(atan2(rawy,rawx),%
ifthenelse(veclen(rawx,rawy)>\R,asin(rawz/\r),180-asin(rawz/\r)),\pgfkeysvalueof{/pgfplots/view/az},\pgfkeysvalueof{/pgfplots/view/el})}{-1.1:0.05}}}
\begin{document}
\begin{tikzpicture}
\pgfmathsetmacro{\R}{4}
\pgfmathsetmacro{\r}{1}
\begin{axis}[colormap/blackwhite,
view={40}{60},axis lines=none]
%\typeout{el=\pgfkeysvalueof{/pgfplots/view/el},az=\pgfkeysvalueof{/pgfplots/view/az}}
samples=61, point meta=z+sin(2*y),
domain=0:330,y domain=0:360,
z buffer=sort]
({torusx(x,y,\R,\r)},
{torusy(x,y,\R,\r)},
{torusz(x,y,\R,\r)});
\pgfplotsinvokeforeach{0,30,...,330}
({torusx(x,#1,\R,\r)},
{torusy(x,#1,\R,\r)},
{torusz(x,#1,\R,\r)});}

\pgfplotsinvokeforeach{0,30,...,330}
({torusx(#1,x,\R,\r)},
{torusy(#1,x,\R,\r)},
{torusz(#1,x,\R,\r)});}
\end{axis}
\end{tikzpicture}
\end{document}


\documentclass[tikz,border=3.14mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\tikzset{declare function={torusx(\u,\v,\R,\r)=cos(\u)*(\R + \r*cos(\v));
torusy(\u,\v,\R,\r)=(\R + \r*cos(\v))*sin(\u);
torusz(\u,\v,\R,\r)=\r*sin(\v);
vcrit1(\u,\th)=atan(tan(\th)*sin(\u));% first critical v value
vcrit2(\u,\th)=180+atan(tan(\th)*sin(\u));% second critical v value
vtest(\u,\v,\az,\el)=sin(-vcrit1(\u-\az,\el)+\v);
disc(\th,\R,\r)=((pow(\r,2)-pow(\R,2))*pow(cot(\th),2)+%
pow(\r,2)*(2+pow(tan(\th),2)))/pow(\R,2);% discriminant
umax(\th,\R,\r)=ifthenelse(disc(\th,\R,\r)>0,asin(sqrt(abs(disc(\th,\R,\r)))),0);
}}
\pgfplotsset{visible stretch/.style={restrict expr to domain={vtest(atan2(rawy,rawx),%
ifthenelse(veclen(rawx,rawy)>\R,asin(rawz/\r),180-asin(rawz/\r)),\pgfkeysvalueof{/pgfplots/view/az},\pgfkeysvalueof{/pgfplots/view/el})}{-0.05:1.1}},
hidden stretch/.style={restrict expr to
domain={vtest(atan2(rawy,rawx),%
ifthenelse(veclen(rawx,rawy)>\R,asin(rawz/\r),180-asin(rawz/\r)),\pgfkeysvalueof{/pgfplots/view/az},\pgfkeysvalueof{/pgfplots/view/el})}{-1.1:0.05}}}
\begin{document}
\begin{tikzpicture}
\pgfmathsetmacro{\R}{4}
\pgfmathsetmacro{\r}{1}
\begin{axis}[colormap/blackwhite,
view={40}{60},axis lines=none]
%\typeout{el=\pgfkeysvalueof{/pgfplots/view/el},az=\pgfkeysvalueof{/pgfplots/view/az}}
samples=61, point meta=z+sin(2*y),
domain=0:330,y domain=0:360,
z buffer=sort]
({torusx(x,y,\R,\r)},
{torusy(x,y,\R,\r)},
{torusz(x,y,\R,\r)});
\pgfplotsinvokeforeach{0,30,...,330}
({torusx(x,#1+x/12,\R,\r)},
{torusy(x,#1+x/12,\R,\r)},
{torusz(x,#1+x/12,\R,\r)});}

\pgfplotsinvokeforeach{0,30,...,330}

• @Gabriel Just to let you know that I fixed the quadrant problem. The correct way to get v from the raw coordinates is ifthenelse(veclen(rawx,rawy)>\R,asin(rawz/\r),180-asin(rawz/\r)).