Kaleidoscopes and Escher

6 August 2023

I love the elegant patterns created by kaleidoscopes. I'm also a huge fan of Escher's engravings, especially his Circle Limit series. So here's a cool little project that shows the beautiful connection between these two things.

1. The Euclidean kaleidoscope

Let's draw three infinite lines enclosing an equilateral triangle. This triangle is called the "fundamental domain", and we assume that it contains the point [0,0].

Now, let's define the following function:

Function IIS([x, y]):

  1. Set pt = [x, y], count = 0.
  2. Calculate the reflections p1, p2, p3 of pt with respect to each of the three lines.
  3. If one of these three points is closer to the central triangle than p, replace p with that point (choosing the closest one) and increment count.
  4. Repeat steps (2) and (3) about fifteen times.
  5. Return count.

Next, let's color each pixel [x, y] based on the count variable returned by the IIS function. We get the following image:

The IIS function counts how many reflections are needed to move from the point [x, y] to the corresponding point in the fundamental domain (the black triangle). It looks nice, but a bit flat! Let's modify step 5 of the IIS function as follows:

  1. Return (count, pt).

With this modification, the IIS function now also returns the point [x', y'] in the fundamental domain that corresponds to [x, y]. We can use this point to sample a texture! Here's what we get:

Fascinating, isn't it?

And to impress your friends, note that this tiling has the Schläfli symbol {3,6}. This means it is made up of regular polygons with 3 sides (triangles), arranged in groups of 6 around each vertex.

2. The hyperbolic kaleidoscope

In this second part, we try to replicate the geometry of Escher's Circular Limits. Start by defining these two functions:

Then choose two integers p and q such that (p-2)(q-2) > 4. For example, p=6 and q=4. Define also three matrices A, B, and C using the formulas on page 148 of D. Dunham's Hyperbolic Symmetry paper.

We need to modify slightly the IIS([x, y]) function:

  1. Set pt = PoincareToWeierstrass([x, y]), count = 0.
  2. Calculate v1 = A*pt, v2 = B*pt, v3 = C*pt, where * is matrix multiplication and pt is viewed as a column vector.
  3. If any of the points v1, v2, or v3 is closer (in Euclidean distance) to the point [0.03, 0.02, 0] than pt, replace pt with that point (choosing the closest one) and increment count.
  4. Repeat steps (2) and (3) about fifteen times.
  5. Set [x', y'] = WeierstrassToPoincare(pt) and return ([x', y'], count).

Like in part 1, color each pixel [x, y] inside the unit disk based on the parity of the count variable. We get this:

I added white lines to help you see that this figure is made of hexagons (6-sided polygons) arranged in groups of 4 around each vertex. Its Schläfli symbol is therefore {6,4}, these are the two integers p,q we chose earlier!

Now, if you want a more "Escher-esque" figure, you can use the point [x',y'] returned by the IIS function to sample a texture, like in part 1. Here is an image I created (I called it "Daisies"):