Slack/emoji/animated GIFs can be tricky to make. They have to be less than 128KB, and fit into a 128 by 128 pixel square. For the purposes of this post, I’m going to liberate a few slackmojifs from their current 128³ prison, and let them roam free over this page at a slighly higher resolution. These free-range versions can enjoy a brief moment in the foreground before scuttling back into the shadows and wasting CPU cycles in the background.
Slack/emoji/animated GIFs (I’ll call them Slackmojifs, which temporarily solves the GIF pronounciation problem) can be tricky to make. They have to be less than 128KB, and fit into a 128 by 128 pixel square. For the purposes of this post, I’m going to liberate a few slackmojifs from their current 128³ prison, and let them roam free over this page at a slighly higher resolution. These free-range versions can enjoy a brief moment in the foreground before scuttling back into the shadows and wasting CPU cycles in the background. Read more
This page contains quite a few animated GIFs. I apologise in advance if your browser or network connection doesn’t like them. It’s much easier to add a GIF than it is to embed a video into a GitHub pages blog, unfortunately.
Pentachoron
I encountered the word “pentachoron” recently for the first time, and I thought it had to be something cool. Turns out it is, so I thought I’d have a go at drawing one…
Unfortunately, it’s a 4-dimensional object, which is going to tax my limited 2D skills. But I like being out of my depth.
First steps
The first job is to define a couple of types to help organize all the various points.
Here’s an immutable structure to hold the coordinates of a point in four dimensions. In four dimensions, the axes are usually called x, y, z, and w.
By adopting the AbstractArray as our stepmother type, we can pick up many useful behaviors. size() and getindex() can be taught how to handle Point4Ds. If you browse the official documentation for a while you’ll find out how to effectively tap in to many desirable pre-defined abilities that our new type can inherit.
While we’re here, let’s do three-dimensional points as well:
Conversion
The primary task we have to address is how to convert a 4D point into a 2D point. Let’s start with the easier task: how to convert a 3D point into a 2D point, ie how can we draw a 3D shape on a flat surface?
Consider a simple cube. The front face and the back face could have the same X and Y coordinates, and vary only by their Z values.
(This is just a diagram, it’s not really 3D…)
So the idea is to project the cube from 3D to 2D by keeping the first two values, and multiplying or modifying them by the third value. We’ll make a convert() function to do this:
K is just a constant which provides a consistent value for depth:
Testing this quickly in Luxor.jl gives promising results:
It’s a simple type of perspective projection.
Using the same principle, let’s make a method for converting a 4D point.
Let’s hope it works.
We can combine these into a single utility function called flatten() that takes a list of 4D points and double-maps them into a list of 2D points suitable for drawing.
Going up a level
To test this, we’ll define the vertices of a unit pentachoron:
(According to Wikipedia, other names for the pentachoron include:
Regular 5-cell
C5
pentatope
pentahedroid
tetrahedral pyramid
4-simplex
I think pentachoron is the coolest, though.)
Here’s a list of “faces”, with each face defined by three of the vertices:
A quick test:
This isn’t very interesting, although I think it’s correct. To see a more appealing display, let’s make it dance…
Swings and roundabouts
You usually rotate 2D points about a 1D point. You usually rotate 3D points about a 2D line (often one of the XYZ axes). So logically you’d rotate 4D points with reference to a 3D plane. We’ll define some matrices that do 4D rotations relative to a plane defined by two of the X, Y, Z, and W axes.
As I’m thinking about it, the XY plane is typically the plane of the drawing surface. If you think of the XY plane as a computer screen straight in front of you, the XZ plane is parallel to your desk or floor, and the YZ plane is like a wall beside your desk on your right or left side.
But what about the XW plane? And the YW and ZW planes, for that matter? This is the mystery of 4D shapes: we can’t see these planes, we can only imagine their existence by watching shapes moving through and around them.
Here’s a function that generates a suitable rotation matrix for rotating a 4D point in the XY plane:
and here’s another one that rotates a 4D point in the XW plane:
Oh, it looks like we’re having a matrix party, so we might as well do the others while we’re here:
We’ll make a handy utility function that uses one of these matrix functions to rotate an array of 4D points A in the plane defined by the matrix function:
So we can write rotate4(A, XW(π/2)) to rotate the array of points in A in the XW plane by π/2. We can also write rotate4(A, XW(π/2) * XY(π/2)) to rotate the points first in the XW plane and then in the XY plane.
Notice that the rotate4() function returns a new array, rather than modifying the original one. This probably has some disadvantages, but for now it means that we don’t have to keep track of mutating objects so carefully.
It moves
We’re now able to make an animation that rotates our pentachoron in one or more planes.
The frame() function generates a single frame, using the framenumber (eased_n provides a normalized version of the framenumber modulated by the easing function) to control the rotation between 0 and 2π. The scalefactor makes the unit shape big enough to fill the frame.
It’s helpful to color the faces very slightly, using poly(..., :fill)-er.
And this function generates a series of frames and saves them to an animation:
This is a rotation in the “parallel to the desk my computer’s resting on” XZ plane.
It’s not a realistic rendering with hidden-surface removal—I wouldn’t know how to do that in 3D, let alone 4D. But it does make it slightly easier to follow some of the faces. Something to do one day might be to sort the “faces” to determine which are drawn first, but given the maths involved this is a challenge I’m dubious about tackling…
We can modify frame() to allow various permutations of XY, XW, XZ, YZ, YW, and ZW rotations. Here’s a version that rotates in XZ, followed by YW:
These are starting to make me think of kneading machines for making pizza dough.
You can draw a few different models at once, placing each one in a separate cell:
This is showing 2D shadows of 3D shadows of 4D objects. Do these objects exist? I don’t know…
Other shapes
While the word “pentachoron” was unfamiliar to me until recently, the word “tesseract” is much more familiar. After all, didn’t the tesseract contain the Space Stone, one of the six Infinity Stones that apparently predate the universe and possess unlimited energy? (According to the scientists at Marvel Comics, at least…) It’s also the name of a 4-dimensional hypercube, one step up from the pentachoron.
Here are the vital numbers for the tesseract:
Replace pentachoron everywhere by tesseract in the above code, choosing the rotations to taste.
(Just for fun I temporarily switched the color scheme over to the Julia logo colors. You can see the 3D rendering problems more clearly, as well!)
Here’s a composite of four pairs of rotations:
Enough is enough
It’s tempting to go further, but these web pages do get very big with all these GIFs. But just one more… I quite like this one, the hexadecachoron:
There’s lots more fun to be had (combining two or more different shapes is fun), but I’m worried about your network and my brain cells, so that’s enough animated GIFs for today.