# Perspectives in 3D graphics with tikz: practical application

## Objective

Below is a plot rendered in Mathematica. The goal is to reproduce this figure using tikz. After perusing many useful posts (such as Tikz: drawing in perspective), the solution is elusive.

## Question

How can tikz be used to reproduce this figure?

## Best effort

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}
\begin{tikzpicture}
%
\def\length{3.5}
\def\dot{1.25}
% origin
\coordinate (org) at (0,0,0);
% offsets
\coordinate (yhat) at (0, 0, -\length);
\coordinate (xhat) at (\length, 0, 0);
\coordinate (zhat) at (0, \length, 0);
% row 1
\coordinate (a) at (org);
\coordinate (b) at ( $(a) + (xhat)$ );
\coordinate (c) at ( $(b) + (xhat)$ );
% row 2
\coordinate (d) at ( $(a) + (yhat)$ );
\coordinate (e) at ( $(d) + (xhat)$ );
\coordinate (f) at ( $(e) + (xhat)$ );
% row 3
\coordinate (g) at ( $(a) + (zhat)$ );
\coordinate (h) at ( $(g) + (xhat)$ );
\coordinate (i) at ( $(h) + (xhat)$ );
% row 4
\coordinate (j) at ( $(a) + (yhat) + (zhat)$ );
\coordinate (k) at ( $(j) + (xhat)$ );
\coordinate (l) at ( $(k) + (xhat)$ );
%
% lines
%
% long segments
\draw[black]    (a) -- (c);
\draw[black]    (g) -- (i);
\draw[black]    (j) -- (l);
% short segments
% solids
\draw[black]    (a) -- (g);
\draw[black]    (b) -- (h);
\draw[black]    (c) -- (i);
\draw[black]    (g) -- (j);
\draw[black]    (h) -- (k);
\draw[black]    (i) -- (l);
\draw[black]    (c) -- (f);
\draw[black]    (f) -- (l);
% dashed
\draw[gray!50,dotted] (a) -- (d);
\draw[gray!50,dotted] (d) -- (j);
\draw[gray!50,dotted] (d) -- (f);
\draw[gray!50,dotted] (b) -- (e);
\draw[gray!50,dotted] (e) -- (k);
%
% labels
%
% bottom nodes
\newcounter{k}
\setcounter{k}{0}
\foreach \n in {a, b, c}
\stepcounter{k}
\node at (\n)[below] {\number\value{k}};
%
% gray
\setcounter{k}{3}
\foreach \n in {d, e, f}
\stepcounter{k}
\node at (\n)[right,gray,fill=white] {\number\value{k}};
%
\setcounter{k}{6}
\foreach \n in {g, h, i}
\stepcounter{k}
\node at (\n)[left,fill=white] {\number\value{k}};
%
\setcounter{k}{9}
\foreach \n in {j, k, l}
\stepcounter{k}
\node at (\n)[above] {\number\value{k}};
%
% dots
%
% black nodes
\foreach \n in {a, c, f, g, i, j, l}
\node at (\n)[circle,fill,inner sep=\dot pt]{};
%
% red nodes
\foreach \n in {b, h, k}
\node at (\n)[circle,red,fill,inner sep=\dot pt]{};
%
% black nodes - hidden
\foreach \n in {d, e}
\node at (\n)[circle,black!50,fill,inner sep=\dot pt]{};
%
% red nodes - hidden
\foreach \n in {e}
\node at (\n)[circle,red!50,fill,inner sep=\dot pt]{};
%
\end{tikzpicture}
\end{document}


## Critique

1. In the Mathematica image, every line is parallel to at least one other line. Not so in the tikz effort. For example 7-10 is not parallel to 9-12.

2. The default viewpoint in Mathematica produces a pleasing perspective which I have failed to reproduce in tikz.

3. The visual gravity of the first image surpases that of the second image.

• Please add a minimal example of what you tried so far. – CarLaTeX Nov 20 '18 at 17:55
• At least the wording "solution is elusive" is amusing, and almost helps one to forget that this might be a "do-that-for-me" question. ;-) Yes, TikZ can definitely do that for you, and even better, this stellar answer provides you with all the means to set up the corresponding three point perspective coordinate system. – user121799 Nov 20 '18 at 18:03
• I honestly do not understand your statement "In the Mathematica image, every line is parallel to at least one other line. Not so in the tikz effort. For example 7-10 is not parallel to 9-12." If you look at the very left and right planes in the mathematica output, you see that their shapes are different, i.e. their edges are not parallel. This is, of course, what the perspective view gives you. On the other hand, in the TikZ output 7-10 and 9-12 appear to be parallel. (And as for frustration saturation, well, I do not think any user of this site is responsible for this.) – user121799 Nov 20 '18 at 19:47

With Max' stellar coordinate system it is really straightforward.

\documentclass[tikz,border=3.14mm]{standalone}
\usepackage{tikz-3dplot}

\makeatletter

% Initialize H matrix for perspective view
\pgfmathsetmacro\H@tpp@ba{0}\pgfmathsetmacro\H@tpp@bb{1}\pgfmathsetmacro\H@tpp@bc{0}%\pgfmathsetmacro\H@tpp@bd{0}
\pgfmathsetmacro\H@tpp@ca{0}\pgfmathsetmacro\H@tpp@cb{0}\pgfmathsetmacro\H@tpp@cc{1}%\pgfmathsetmacro\H@tpp@cd{0}
\pgfmathsetmacro\H@tpp@da{0}\pgfmathsetmacro\H@tpp@db{0}\pgfmathsetmacro\H@tpp@dc{0}%\pgfmathsetmacro\H@tpp@dd{1}

%Initialize H matrix for main rotation
\pgfmathsetmacro\H@rot@ba{0}\pgfmathsetmacro\H@rot@bb{1}\pgfmathsetmacro\H@rot@bc{0}%\pgfmathsetmacro\H@rot@bd{0}
\pgfmathsetmacro\H@rot@ca{0}\pgfmathsetmacro\H@rot@cb{0}\pgfmathsetmacro\H@rot@cc{1}%\pgfmathsetmacro\H@rot@cd{0}
%\pgfmathsetmacro\H@rot@da{0}\pgfmathsetmacro\H@rot@db{0}\pgfmathsetmacro\H@rot@dc{0}\pgfmathsetmacro\H@rot@dd{1}

\pgfkeys{
/three point perspective/.cd,
p/.code args={(#1,#2,#3)}{
\pgfmathparse{int(round(#1))}
\ifnum\pgfmathresult=0\else
\pgfmathsetmacro\H@tpp@ba{#2/#1}
\pgfmathsetmacro\H@tpp@ca{#3/#1}
\pgfmathsetmacro\H@tpp@da{ 1/#1}
\coordinate (vp-p) at (#1,#2,#3);
\fi
},
q/.code args={(#1,#2,#3)}{
\pgfmathparse{int(round(#2))}
\ifnum\pgfmathresult=0\else
\pgfmathsetmacro\H@tpp@ab{#1/#2}
\pgfmathsetmacro\H@tpp@cb{#3/#2}
\pgfmathsetmacro\H@tpp@db{ 1/#2}
\coordinate (vp-q) at (#1,#2,#3);
\fi
},
r/.code args={(#1,#2,#3)}{
\pgfmathparse{int(round(#3))}
\ifnum\pgfmathresult=0\else
\pgfmathsetmacro\H@tpp@ac{#1/#3}
\pgfmathsetmacro\H@tpp@bc{#2/#3}
\pgfmathsetmacro\H@tpp@dc{ 1/#3}
\coordinate (vp-r) at (#1,#2,#3);
\fi
},
coordinate/.code args={#1,#2,#3}{
\def\tpp@x{#1}
\def\tpp@y{#2}
\def\tpp@z{#3}
},
}

\tikzset{
view/.code 2 args={
\pgfmathsetmacro\rot@main@theta{#1}
\pgfmathsetmacro\rot@main@phi{#2}
% Row 1
\pgfmathsetmacro\H@rot@aa{cos(\rot@main@phi)}
\pgfmathsetmacro\H@rot@ab{sin(\rot@main@phi)}
\pgfmathsetmacro\H@rot@ac{0}
% Row 2
\pgfmathsetmacro\H@rot@ba{-cos(\rot@main@theta)*sin(\rot@main@phi)}
\pgfmathsetmacro\H@rot@bb{cos(\rot@main@phi)*cos(\rot@main@theta)}
\pgfmathsetmacro\H@rot@bc{sin(\rot@main@theta)}
% Row 3
\pgfmathsetmacro\H@m@ca{sin(\rot@main@phi)*sin(\rot@main@theta)}
\pgfmathsetmacro\H@m@cb{-cos(\rot@main@phi)*sin(\rot@main@theta)}
\pgfmathsetmacro\H@m@cc{cos(\rot@main@theta)}
% Set vector values
\pgfmathsetmacro\vec@x@x{\H@rot@aa}
\pgfmathsetmacro\vec@y@x{\H@rot@ab}
\pgfmathsetmacro\vec@z@x{\H@rot@ac}
\pgfmathsetmacro\vec@x@y{\H@rot@ba}
\pgfmathsetmacro\vec@y@y{\H@rot@bb}
\pgfmathsetmacro\vec@z@y{\H@rot@bc}
% Set pgf vectors
\pgfsetxvec{\pgfpoint{\vec@x@x cm}{\vec@x@y cm}}
\pgfsetyvec{\pgfpoint{\vec@y@x cm}{\vec@y@y cm}}
\pgfsetzvec{\pgfpoint{\vec@z@x cm}{\vec@z@y cm}}
},
}

\tikzset{
perspective/.code={\pgfkeys{/three point perspective/.cd,#1}},
perspective/.default={p={(15,0,0)},q={(0,15,0)},r={(0,0,50)}},
}

\tikzdeclarecoordinatesystem{three point perspective}{
\pgfkeys{/three point perspective/.cd,coordinate={#1}}
\pgfmathsetmacro\temp@p@w{\H@tpp@da*\tpp@x + \H@tpp@db*\tpp@y + \H@tpp@dc*\tpp@z + 1}
\pgfmathsetmacro\temp@p@x{(\H@tpp@aa*\tpp@x + \H@tpp@ab*\tpp@y + \H@tpp@ac*\tpp@z)/\temp@p@w}
\pgfmathsetmacro\temp@p@y{(\H@tpp@ba*\tpp@x + \H@tpp@bb*\tpp@y + \H@tpp@bc*\tpp@z)/\temp@p@w}
\pgfmathsetmacro\temp@p@z{(\H@tpp@ca*\tpp@x + \H@tpp@cb*\tpp@y + \H@tpp@cc*\tpp@z)/\temp@p@w}
\pgfpointxyz{\temp@p@x}{\temp@p@y}{\temp@p@z}
}
\tikzaliascoordinatesystem{tpp}{three point perspective}

\makeatother

\begin{document}
\begin{tikzpicture}[line join=round,scale=4]
\begin{scope}[
view={60}{30},
perspective={
p={(-15,0,0)},q={(0,25,0)},r={(0,0,-30)}
},bullet/.style={circle,fill,inner sep=1pt},font=\sffamily]
\foreach \X in {0,1,2}
{\foreach \Y in {0,1}
{\foreach \Z [evaluate=\Z as \L using {int(1+\X+3*\Y+6*\Z)}] in {0,1}
{\ifnum\Z=0
\path (tpp cs:\X,\Y,\Z) node[bullet,label=below:\L] (\L){};
\else
\path (tpp cs:\X,\Y,\Z) node[bullet,label=above:\L] (\L){};
\fi}}}
\draw[dotted] (1)  -- (4) -- (6)
(4) -- (10)  (2) -- (5) -- (11);
\draw (1) -- (3) -- (9) -- (7) -- (1)
(9) -- (12) -- (10) -- (7)
(3) -- (6) -- (12) (2) -- (8) -- (11);
\end{scope}
\end{tikzpicture}
\end{document}


Of course, you will have to adjust the view and/or perspective to your needs.

I really hope that this coordinate system will be part of a package some time soon.

ADDENDUM: You want parallel lines? Well, then you don't need any of these perspective thingies.

\documentclass[tikz,border=3.14mm]{standalone}
\usepackage{tikz-3dplot}

\begin{document}
\tdplotsetmaincoords{60}{30}
\begin{tikzpicture}[tdplot_main_coords,>=latex,line join=bevel,font=\sffamily,
bullet/.style={circle,fill,inner sep=1pt},scale=4]
\foreach \X in {0,1,2}
{\foreach \Y in {0,1}
{\foreach \Z [evaluate=\Z as \L using {int(1+\X+3*\Y+6*\Z)}] in {0,1}
{\ifnum\Z=0
\path (\X,\Y,\Z) node[bullet,label=below:\L] (\L){};
\else
\path (\X,\Y,\Z) node[bullet,label=above:\L] (\L){};
\fi}}}
\draw[dotted] (1)  -- (4) -- (6)
(4) -- (10)  (2) -- (5) -- (11);
\draw (1) -- (3) -- (9) -- (7) -- (1)
(9) -- (12) -- (10) -- (7)
(3) -- (6) -- (12) (2) -- (8) -- (11);
\end{tikzpicture}
\end{document}


• Thanks for this great effort. Can we create a figure such that each line belongs to one of two sets of parallel lines? – dantopa Nov 20 '18 at 19:42
• @dantopa Sure. Parallel lines are much easier. – user121799 Nov 20 '18 at 20:01
• Elegant approach to the problem. My earlier point should have been stated as "a little help from users like you often makes a big difference." – dantopa Nov 20 '18 at 20:21

You can set the 3D coordinate system in ordinary tikz. Without changes it becomes

\begin{tikzpicture}[scale=2]
\foreach \pos [count=\Ind] in {{0,0,1},{1,0,1},{2,0,1},{0,0,0},{1,0,0},{2,0,0},{0,1,1},{1,1,1},{2,1,1},{0,1,0},{1,1,0},{2,1,0}}{
\node[circle,inner sep=1pt,fill=black,label=\ifnum\Ind<7 below\else above\fi:\Ind](p\Ind) at (\pos){};
}
\draw (p1)--(p2)--(p3)--(p6)--(p12)--(p11)--(p10)--(p7)edge(p1)--(p8)edge(p11)edge(p2)--(p9)edge(p12)--(p3);
\draw[dotted] (p4)edge(p1)edge(p10)--(p5)edge(p2)edge(p11)--(p6);
\end{tikzpicture}


Then, changing x and z vectors you can redraw the same picture

\begin{tikzpicture}[x={({cos(20)},{-sin(20)},0)},z={({-sin(40)},{-cos(40)},0)},scale=2]
\foreach \pos [count=\Ind] in {{0,0,1},{1,0,1},{2,0,1},{0,0,0},{1,0,0},{2,0,0},{0,1,1},{1,1,1},{2,1,1},{0,1,0},{1,1,0},{2,1,0}}{
\node[circle,inner sep=1pt,fill=black,label=\ifnum\Ind<7 below\else above\fi:\Ind](p\Ind) at (\pos){};
}
\draw (p1)--(p2)--(p3)--(p6)--(p12)--(p11)--(p10)--(p7)edge(p1)--(p8)edge(p11)edge(p2)--(p9)edge(p12)--(p3);
\draw[dotted] (p4)edge(p1)edge(p10)--(p5)edge(p2)edge(p11)--(p6);
\end{tikzpicture}


This is not really a 3D drawing, just using three 2D vectors to draw the figure.

• This is of course very minimal, and therefore attractive. Note, however, that these are not orthographic projections. In other words, a good reason for using tikz-3dplots and the like is that these are orthographic projections, i.e. an orthogonal rotation followed by a projection. That is, the results are then views (modulo perspective) that can be obtained from real things. If you just adjust x, y and z by hand, this cannot be guaranteed. Of course, this is not necessarily a bad thing, in fact some users as specifically for such frames. – user121799 Nov 21 '18 at 4:37