TikZ-PGF – Creating a Menger Sponge Fractal

tikz-pgf

Ok so I am continuing my construction of fractals in Tikz – another one is here: Tikz Fractal – Cantor Dust. I was hoping someone would be able to help me create another, somewhat more complicated, fractal. It's called the Menger Sponge and is essentially a '3D' (although defining fractals as such is a little contradictory) Sierpinski Carpet. It looks like this:

enter image description here

Where you start with the unit cube, and recursively remove the centre 9th of each face all the way through the cube. A better, more precise description is here: https://en.wikipedia.org/wiki/Menger_sponge.

I'm thinking it should be possible with a 'lindenmayer system' but I have only a small amount of experience with those and I've never used it for 3D shapes.

After using Nancy-N's solution, I get this:

enter image description here

(notice the little lines around each smaller cube).

Best Answer

I do not know if this will make you happy, but yes, a kind of Lindenmayer system can work for your problem, using the fact that the cube is made of 20 copies of a 3-times-smaller itself, who themselves are made of a 3-times-smaller cube, and so on. Here is a way you can do it; I let you make the needed adaptations to get a better picture.

First generate a .tex file level0.tex with no \documentclass, \begin{document} or so, whose content just would be:

\fill[yellow] (1, 1, 1) -- (1, -1, 1) -- (-1, -1, 1) -- (-1, 1, 1) -- cycle;
\fill[cyan] (1, 1, 1) -- (1, 1, -1) -- (1, -1, -1) -- (1, -1, 1) -- cycle;
\fill[magenta] (1, 1, 1) -- (1, 1, -1) -- (-1, 1, -1) -- (-1, 1, 1) -- cycle;

Now you create a file level1.tex (again, no \documentclass or so) in which you use the content of level0 twenty times as if you were putting several small cubes near each other. Beware that you have to draw the cubes in a correct order, so that a cube hiding another one will only be drawn after it. Your file level1.tex would be the following:

\begin{scope}[scale=.3333333333]
\begin{scope}[shift={(-2, -2, -2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(0, -2, -2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(2, -2, -2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(-2, 0, -2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(2, 0, -2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(-2, 2, -2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(0, 2, -2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(2, 2, -2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(-2, -2, 0)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(2, -2, 0)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(-2, 2, 0)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(2, 2, 0)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(-2, -2, 2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(0, -2, 2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(2, -2, 2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(-2, 0, 2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(2, 0, 2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(-2, 2, 2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(0, 2, 2)}]
\input{level0}
\end{scope}
\begin{scope}[shift={(2, 2, 2)}]
\input{level0}
\end{scope}
\end{scope}

Next you create a file level2.tex which is the same as level1.tex, except that \input{level0} is replaced by \input{level1}. And finally your final drawing (inside a real .tex source file with \documentclass and so on) will be

\begin{tikzpicture}
\input{level2}
\end{tikzpicture}

Below is the rendering I get. Of course you can go up to level 3, 4 or so, at least theoretically, but do not forget that the amount of computation and memory used will be multiplied by a factor 20 at each further level!

Menger sponge, level 2

Kind regards, /Nancy-N


EDIT: Paul Gaborit has quite nicely improved my solution by coding a command \mengersponge taking one argument, so that you do not have to create one file per level. Here is his code (where he also chose more relevant colours ;-)):

\documentclass[tikz]{standalone}
\usepackage{luatex85}
\newcommand\mengersponge[1]{
  \pgfmathsetmacro\level{int(#1 - 1)}
  \ifnum \level = 0 \relax
  \filldraw[gray!50!black,line width=0] (1, 1, 1) -- (1, 1, -1) -- (1, -1, -1) -- (1, -1, 1) -- cycle;
  \filldraw[gray,line width=0] (1, 1, 1) -- (1, 1, -1) -- (-1, 1, -1) -- (-1, 1, 1) -- cycle;
  \filldraw[gray!50,line width=0] (1, 1, 1) -- (1, -1, 1) -- (-1, -1, 1) -- (-1, 1, 1) -- cycle;
  \else
  \begin{scope}[scale=.3333333333]
    \begin{scope}[shift={(-2, -2, -2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(0, -2, -2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(2, -2, -2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(-2, 0, -2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(2, 0, -2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(-2, 2, -2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(0, 2, -2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(2, 2, -2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(-2, -2, 0)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(2, -2, 0)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(-2, 2, 0)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(2, 2, 0)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(-2, -2, 2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(0, -2, 2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(2, -2, 2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(-2, 0, 2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(2, 0, 2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(-2, 2, 2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(0, 2, 2)}]
      \mengersponge{\level}
    \end{scope}
    \begin{scope}[shift={(2, 2, 2)}]
      \mengersponge{\level}
    \end{scope}
  \end{scope}
  \fi
}
\begin{document}
\begin{tikzpicture}
  \mengersponge{4}
\end{tikzpicture}
\end{document}

With pdflatex, Paul Gaborit could only perform the computations up to level 3 (corresponding to \mengersponge{4} in his code), which took 40 seconds to compilate; but with lualatex he could compile level 4 (corresponding to \mengersponge{5}). Below are his pictures :-)

For level 3:

Menger sponge, level 3

For level 4:

Menger sponge, level 4

Regards, /Nancy-N