The Two Nucleic Symmetries of Rhombohedral Tessellation

The Rhombic Dodecahedron and the Truncated Octahedron

During a game jam, it was discovered through play that by wrapping the outlying vertices of a group of tessellated Rhombic Dodecahedra with a convex hull, an approximation of a Truncated Octahedron was produced.  Upon further investigation, it was discovered that an approximation of a Rhombic Dodecahedron emerged by wrapping the outlying vertices of a group of tessellated Truncated Octahedra with a convex hull.

Below, we can see three tessellations of the Rhombic Dodecahedron, increasing in size:

Rhombic Dodecahedral Tessellations of increasing size

By coloring the Rhombic Dodecahedrons and taking their convex hull, we can easily see the emergence of the Truncated Octahedron.  The faces in orange belong to the emergent shape; the faces in blue are what I have been calling “aliasing.”  These aliased regions diminish in size the larger the tesselation is; an infinitely large tessellation would have no aliasing.

To render convex hulls, I employed a piece of assistive software: Stella4D.

In the same way that we created a rhombohedral tessellation, we can create a truncated octahedral teasellation:

Truncated Octahedral tessellations of Increasing Size

Once again, by wrapping the outermost vertices of a truncated octahedral honeycomb with a convex hull, we receive an aliased rhombic dodecahedron.  The regions in orange are the emergent shape; the regions in dark blue are aliasing.

To render convex hulls, I employed a piece of assistive software: Stella4D.

Harmonic Shapes

The Rhombic Dodecahedron and Truncated Octahedron exhibit a Harmony of Shapes.  A rhombohedron iterated sufficiently forms the Truncated Octahedron.  The Truncated Octahedron iterated sufficiently forms the Rhombic Dodecahedron.  Another way to imagine this harmony is that the Rhombic Dodecahedron can be arranged into truncated octahedral blocks, and these blocks of rhombohedrons could be iterated into larger super-rhombohedrons.  That is, the Rhombohedron can become itself once more, after passing through the transient phase of being a truncated octahedron. 

This relationship reminds me of the frequency of musical harmony.  When we ascend an octave from one note, we receive the same note once more, but constituted of many wave cycles instead of just one.  Watching the number of sine cycles on an oscilloscope, we can see that by leaping an octave (the simplest & strongest harmony), we double our sine cycles, and of course our frequency – much like by leaping a Rhombohedral Octave, we arrive once more a the rhombohedron, but with more polyhedral cycles comprising it.

Harmonic Sounds are made of stacked waves, as harmonic shapes are made out of stacked sub-shapes.

Nevermind the strangeness of our equal-temperament musical system and the odd edge cases it brings on.  This analogy to musical harmony is enough for me to indulge the slightly whimsical title of “Harmonic Shapes.”

Due Diligence

I am under the assumption that this ‘harmonic’ property is fully and explicitly described by someone far more intelligent than I.  However, after extensive search, despite the abundant writing I am able to find on space-filling polyhedra, I am unable to find an explicit description of this particular phenomenon of reciprocating tessellation.  My search continues as I begin to crawl my way through Buckminster Fuller’s Synergetics, which explores the Rhombic Dodecaheron and its various properties in great depth, as I bludgeon my way through Coxeter’s Regular Polytopes, a far less philosophical but significantly more rigorous and procedural treatment of polyhedrons, and as I am constantly delighted by Keith Critchlow’s Order in Space, which is the most visually pleasing book I have ever read.  So far, despite all three authors’ ruminations on space-fillers and n-dimensional tessellations, I haven’t found anything explicitly describing the strange, harmonic relationship I’ve observed between these two polyhedrons.  Most likely, my observation is a trivial and/or exceedingly obvious result of some other pre-existing, already named property, but until I find it in my reading, I’m going to keep pushing forward and playing with these shapes. 

A Correction on Space-Filling Polyhedra

In my last post, I falsely claimed that there were “only five regular polyhedrons that are space-filling.”

After more research on the topic, I feel it is important to correct myself by saying that there is only one regular space-filling polyhedron: the cube.

There are only five space-filling convex polyhedra with regular faces: the cube, the triangular prism, the hexagonal prism, the gyrobifastigium, and the truncated octahedron.

The Rhombic Dodecahedron, despite being space-filling, is in fact a semi-regular polyhedron.

Finally, it should be noted that there are hundreds of space-filling polyhedra that are neither regular nor semi-regular.


Data Representation and Mesh Generation of the Truncated Octahedron

The following section is devoted to the data representation of the Truncated Octahedron and its rendering in Unity Gaming Engine.  Those concerned solely with the abstract geometrical musings of this post can skip this section without missing anything important and jump right into “Nucleic and Non-Nucleic Tessellation.”

Generalization of Polyhedron, Polygon

In my last post, the only shape we were concerned with representing was the Rhombic Dodecahedron.  Therfore, our class representing the shape could stand alone, without extending any other classes or being generalized in any kind of way.

Now that we want to generate a new Polyhedron, it makes sense to implement a Polyhedron class from which both RhombicDodecahedron and TruncatedOctahedron extend.  All Polyhedrons will contain a Vector3 center, a float scale, a set of Vector3 vertices and Polygon faces.

Generalizing our faces as a single type, Polygon, is also new.  Previously there was only one face type: the Rhombus, which still exists as an extension of Polygon.  However, we now have two new face-types: Square and Hexagon, the two faces we need in order to construct the truncated octahedron.

All three of these face classes extend Polygon and contain a set of Vector3 vertices, a Vector3 centroid (needed for our tiling system) and a Polyhedron parent reference.

In the future, we may need far more data on these classes: knowledge of edges, orientation vectors – for now, I have only implemented exactly what I need and no more. 

Here is the class hierarchy:

Introduction to Object-Oriented Programming 100

Implementing this hierarchy is straightforward enough that I feel I do not need to descend into a code breakdown.  Where things really get interesting is when we attempt to render a truncated octahedral mesh in Unity.

Meshes in Unity

A Unity Mesh is attached to a GameObject.  It contains all of the information required to represent an object in three-dimensional space – its vertices, its triangles, and its UV array.

  • Vertices are a series of Vector3 describing the object’s points in space.  They are referenced by triangles and UV.
  • Triangles specify how Unity should render the object as a series of triangles; this integer array is organized into triplets, with each triplet being a reference to three indices of the Vertices array, through which Unity should render a triangle.  Therefore, this array’s size must always be a multiple of three.
  • UV is an array of Vector2. Each Vector2, existing in an array, naturally has an index.  Each Vector2 corresponds with the vertex in vertices that has a matching index.  That is: UV[0] corresponds with Vertices[0], and UV[n] corresponds with Vertices[n].  Each UV entry specifies where the vertex of the corresponding index should lie in a 2-D texture file. 
Vertices of the Truncated Octahedron

The innards of the TruncatedOctahedron class are almost identical to the RhombicDodecahedron clas, whose interior I show in my previous post.  Therefore, I will not share the internals of TruncatedOctahedron – but I will share how its vertices are found.

Every permutation of the integers (0, +-1, +-2) is a vertex coordinate of the Truncated Octahedron whose edge length is sqrt(2) and who is centered at the origin.  In other words, 

        // Generate the 24 vertices.
        // Every permutation of (+-2, +-1, 0) is a vertex. 
        Vector3 v1  = new Vector3( 2,  1,  0);
        Vector3 v2  = new Vector3( 2, -1,  0);
        Vector3 v3  = new Vector3(-2,  1,  0);
        Vector3 v4  = new Vector3(-2, -1,  0);
        // Every permutation of (0, +-2, +-1) is a vertex. 
        Vector3 v5  = new Vector3( 0,  2,  1);
        Vector3 v6  = new Vector3( 0,  2, -1);
        Vector3 v7  = new Vector3( 0, -2,  1);
        Vector3 v8  = new Vector3( 0, -2, -1);
        // Every permutation of (+-1, 0, +-2) is a vertex. 
        Vector3 v9  = new Vector3( 1,  0,  2);
        Vector3 v10 = new Vector3( 1,  0, -2);
        Vector3 v11 = new Vector3(-1,  0,  2);
        Vector3 v12 = new Vector3(-1,  0, -2);
        // Every permutation of (+-1, +-2, 0) is a vertex. 
        Vector3 v13 = new Vector3( 1,  2,  0);
        Vector3 v14 = new Vector3( 1, -2,  0);
        Vector3 v15 = new Vector3(-1,  2,  0);
        Vector3 v16 = new Vector3(-1, -2,  0);
        // Every permutation of (0, +-1, +-2) is a vertex.
        Vector3 v17 = new Vector3( 0,  1,  2);
        Vector3 v18 = new Vector3( 0,  1, -2);
        Vector3 v19 = new Vector3( 0, -1,  2);
        Vector3 v20 = new Vector3( 0, -1, -2);
        // Every permutation of (+-2, 0, +-1) is a vertex.
        Vector3 v21 = new Vector3( 2,  0,  1);
        Vector3 v22 = new Vector3( 2,  0, -1);
        Vector3 v23 = new Vector3(-2,  0,  1);
        Vector3 v24 = new Vector3(-2,  0, -1);

These vertices can all be offset by the TruncatedOctahedron’s center Vector3 when it comes time to orient the object in space.

These vertices might invoke a dim and distant memory for long-term followers of my blog, if such people exist. 

When developing the source code for the dynamic, asymmetrical icosphere, a PISES utility, we generated the 12 vertices of the icosahedron by intersecting three orthogonal golden rectangles.

As it turns out, we generate the 24 vertices of the truncated octahedron by intersecting three orthogonal (non-golden) rectangles twice, rotated by 60 degrees!  Pretty neat.

Triangulating the Truncated Octahedron

The Truncated Octahedron has 8 hexagonal faces and 6 square faces.  We must decompose all of these faces into triangles and store them in the triangles array.

Triangulating a square is simple: for a square ABCD, simply connect AC or BD.

Triangulating a hexagon is also simple, but there are far more options:

Catalan Hexagons

I wound up using the triangulation style highlighted in blue.  I drew out an isometric truncated octahedron on paper, and painstakingly brained out the 32 hex-triangles and 12 square-triangles by hand.

Vertices here are 1-indexed.  Of course in code, they are 0-indexed.
List<int> triangles1 = new List<int> {
    // Hex 1 ////////// 
    12, 4, 0, 
    4, 20, 0, 
    4, 16, 20, 
    16, 8, 20, 
    // Hex 2 ////////// 
    10, 16, 4, 
    14, 10, 4, 
    22, 10, 14, 
    2, 22, 14, 
    // Hex 3 ////////// 
    11, 23, 2, 
    14, 11, 2, 
    17, 11, 14, 
    5, 17, 14, 
    // Hex 4 ////////// 
    6, 18, 10, 
    22, 6, 10, 
    15, 6, 22, 
    3, 15, 22, 
    // Hex 8 ////////// 
    21, 1, 13, 
    7, 21, 13, 
    9, 21, 7, 
    19, 9, 7, 
    // Square 6 //////////
    19, 11, 17,
    19, 17, 9
};
List<int> triangles2 = new List<int> {
    // Square 1 //////////
    18, 8, 16, 
    18, 16, 10,
    // Square 2 //////////
    4, 12, 5,
    4, 5, 14,
    // Square 3 //////////
    2, 23, 3, 
    2, 3, 22, 
    // Square 4 //////////
    13, 6, 15, 
    13, 15, 7, 
    // Square 5 //////////
    0, 20, 1, 
    0, 1, 21, 
};
List<int> triangles3 = new List<int> {
    // Hex 5 ////////// 
    1, 20, 8, 
    18, 1, 8, 
    13, 1, 18, 
    6, 13, 18, 
    // Hex 6 ////////// 
    9, 17, 5,
    12, 9, 5,
    21, 9, 12,
    0, 21, 12,
    // Hex 7 ////////// 
    3, 23, 11, 
    19, 3, 11, 
    15, 3, 19, 
    7, 15, 19, 
};

One will immediately notice that these triangles are broken up into three different groups.  As it turns out, when rendering a Truncated Octahedron with a UV texture, we actually have to create a composite of three meshes.

Texturing the Truncated Octahedron

For a textured Truncated Octahedron, one mesh is insufficient.  This is because each vertex of the truncated octahedron is a member of three different faces.  If a vertex must belong to a hexagonal corner and also belong to a square corner, and our texture file must exist in a meager two dimensions, depending on how we unfold the Truncated Octahedron, certain vertices will require three different mappings.  Let us take a look at an unfolding of the Truncated Octahedron – the following image is the texture image used for our shape.

One possible unfolding of the Truncated Octahedron.

These vertex numbers align with my hand-drawn diagram from earlier. As we can see, Vertex 17’s position is accurate with respect to all three of its adjacent faces. However, Vertex 9’s position is broken up across three different locations in our texture. This is simply a result of how we have chosen to cut and unfold the Truncated Octahedron. If we had chosen a different unfolding, perhaps vertex 9 would be able to reside in one location, and vertex 17 would be the broken-up point.

Inevitably, no matter how we choose to cut and flatten the shape, there need to be three vertex sets, each with a different UV mapping. In these three UV maps, vertices like v17 will always have the same vector2 coordinate in the texture. However, vertices like v9 will have three different vector2 coordinates, depending on which UV map we’re looking at.

Another way of visualizing it is by dividing up the faces of the texture into three different groups. Blue Group and its vertices will constitute mesh 1. Orange Group and its vertices will constitute mesh 2. Yellow Group and its vertices will constitute mesh 3.

Three different mesh groups. Inside each, vertices are totally consistent with their peers. There are multiple ways of doing this.

As an aside, It should be mentioned that the reason we were able to get away with only one mesh and UV map for the Rhombic Dodecahedron was due to the fact that all of its faces are identical in both shape and pattern. Despite the fact that the Rhombus has two different orientations when arranged into a dodecahedron, all obtuse-angled vertices align with other obtuse-angled vertices, and all acute-angled vertices align with other acute-angled vertices. Therefore, our texture file could consist of a single, simple rhombus, and we only needed four UV points. Alas, introducing a polyhedron with numerous face-types complicates things greatly. Had we needed a Rhombic Dodecahedron with different patternings on each face, we would have had to create a quadruple mesh (there are vertices of the rhombohedron adjacent to four different faces).

Finally, we can specify our UV offsets as variables and create three separate UV arrays in which to contain them:

// Vector2s point to a position in an image of 8000 x 12000 pixels, hence the division.  
// Unity interprets the bottom left of the image as 0,0 while GIMP measures pixel position from the top left.  Hence the subraction.  
// If I were to deliver this software, I would calculate these values and encode them as constants directly, sparing some startup operations.
Vector2 tuv1  = new Vector2(6916f / 8000f, (12000 - 1841f) / 12000f); // Uniform across all mesh sets
Vector2 tuv2  = new Vector2(4520f / 8000f, (12000 - 9257f) / 12000f); // Uniform across all mesh sets
Vector2 tuv3  = new Vector2(3322f / 8000f, (12000 - 3912f) / 12000f); // Uniform across all mesh sets
Vector2 tuv4  = new Vector2(1530f / 8000f, (12000 - 2876f) / 12000f); // Unique  across all mesh sets
Vector2 tuv5  = new Vector2(5118f / 8000f, (12000 - 2877f) / 12000f); // Uniform across all mesh sets
Vector2 tuv6  = new Vector2(5117f / 8000f, (12000 - 4948f) / 12000f); // Unique  across all mesh sets
Vector2 tuv7  = new Vector2(1528f / 8000f, (12000 - 805f ) / 12000f); // Unique  across all mesh sets
Vector2 tuv8  = new Vector2(2725f / 8000f, (12000 - 8222f) / 12000f); // Uniform across all mesh sets
Vector2 tuv9  = new Vector2(5115f / 8000f, (12000 - 800f ) / 12000f); // Unique  across all mesh sets
Vector2 tuv10 = new Vector2(4521f / 8000f, (12000 - 7186f) / 12000f); // Uniform across all mesh sets
Vector2 tuv11 = new Vector2(3323f / 8000f, (12000 - 1839f) / 12000f); // Uniform across all mesh sets
Vector2 tuv12 = new Vector2(3323f / 8000f, (12000 - 5983f) / 12000f); // Unique  across all mesh sets
Vector2 tuv13 = new Vector2(6313f / 8000f, (12000 - 2876f) / 12000f); // Unique  across all mesh sets
Vector2 tuv14 = new Vector2(3323f / 8000f, (12000 - 9258f) / 12000f); // Uniform across all mesh sets
Vector2 tuv15 = new Vector2(4521f / 8000f, (12000 - 3914f) / 12000f); // Uniform across all mesh sets
Vector2 tuv16 = new Vector2(931f  / 8000f, (12000 - 1838f) / 12000f); // Unique  across all mesh sets
Vector2 tuv17 = new Vector2(4519f / 8000f, (12000 - 1839f) / 12000f); // Uniform across all mesh sets
Vector2 tuv18 = new Vector2(4519f / 8000f, (12000 - 5984f) / 12000f); // Unique  across all mesh sets
Vector2 tuv19 = new Vector2(2725f / 8000f, (12000 - 805f ) / 12000f); // Unique  across all mesh sets
Vector2 tuv20 = new Vector2(3323f / 8000f, (12000 - 7187f) / 12000f); // Uniform across all mesh sets
Vector2 tuv21 = new Vector2(6315f / 8000f, (12000 - 800f ) / 12000f); // Unique  across all mesh sets
Vector2 tuv22 = new Vector2(5117f / 8000f, (12000 - 8222f) / 12000f); // Uniform across all mesh sets
Vector2 tuv23 = new Vector2(2724f / 8000f, (12000 - 2876f) / 12000f); // Uniform across all mesh sets
Vector2 tuv24 = new Vector2(2724f / 8000f, (12000 - 4946f) / 12000f); // Unique  across all mesh sets
Vector2 tuv1mesh2  = new Vector2(6159f / 8000f, (12000 - 8819f) / 12000f);
Vector2 tuv4mesh2  = new Vector2(1686f / 8000f, (12000 - 3474f) / 12000f);
Vector2 tuv6mesh2  = new Vector2(5561f / 8000f, (12000 - 4518f) / 12000f);
Vector2 tuv7mesh2  = new Vector2(2288f / 8000f, (12000 - 9860f) / 12000f);
Vector2 tuv9mesh2  = new Vector2(4525f / 8000f, (12000 - 641f ) / 12000f);
Vector2 tuv12mesh2 = new Vector2(2725f / 8000f, (12000 - 6150f) / 12000f);
Vector2 tuv13mesh2 = new Vector2(6161f / 8000f, (12000 - 3476f) / 12000f);
Vector2 tuv16mesh2 = new Vector2(1686f / 8000f, (12000 - 8819f) / 12000f);
Vector2 tuv18mesh2 = new Vector2(5116f / 8000f, (12000 - 6150f) / 12000f);
Vector2 tuv19mesh2 = new Vector2(3323f / 8000f, (12000 - 641f ) / 12000f);
Vector2 tuv21mesh2 = new Vector2(5557f / 8000f, (12000 - 9861f) / 12000f);
Vector2 tuv24mesh2 = new Vector2(2286f / 8000f, (12000 - 4513f) / 12000f);
Vector2 tuv1mesh3  = new Vector2(6314f / 8000f, (12000 - 8221f)  / 12000f); 
Vector2 tuv4mesh3  = new Vector2(932f  / 8000f, (12000 - 7184f)  / 12000f); 
Vector2 tuv6mesh3  = new Vector2(6313f / 8000f, (12000 - 6150f)  / 12000f); 
Vector2 tuv7mesh3  = new Vector2(2726f / 8000f, (12000 - 10292f) / 12000f); 
Vector2 tuv9mesh3  = new Vector2(4520f / 8000f, (12000 - 11328f) / 12000f); 
Vector2 tuv12mesh3 = new Vector2(2725f / 8000f, (12000 - 6150f)  / 12000f); 
Vector2 tuv13mesh3 = new Vector2(6911f / 8000f, (12000 - 7186f)  / 12000f); 
Vector2 tuv16mesh3 = new Vector2(1530f / 8000f, (12000 - 8221f)  / 12000f); 
Vector2 tuv18mesh3 = new Vector2(5116f / 8000f, (12000 - 6150f)  / 12000f); 
Vector2 tuv19mesh3 = new Vector2(3324f / 8000f, (12000 - 11328f) / 12000f); 
Vector2 tuv21mesh3 = new Vector2(5117f / 8000f, (12000 - 10293f) / 12000f); 
Vector2 tuv24mesh3 = new Vector2(1528f / 8000f, (12000 - 6150f)  / 12000f); 
// ...
// ...
// ...
UV1.AddRange(new List<Vector2> {
    tuv1, tuv2, tuv3, tuv4, tuv5, tuv6,  
    tuv7, tuv8, tuv9, tuv10, tuv11, tuv12,  
    tuv13, tuv14, tuv15, tuv16, tuv17, tuv18,  
    tuv19, tuv20, tuv21, tuv22, tuv23, tuv24, 
});
UV2.AddRange(new List<Vector2> {
    tuv1mesh2, tuv2, tuv3, tuv4mesh2, tuv5, tuv6mesh2,  
    tuv7mesh2, tuv8, tuv9mesh2, tuv10, tuv11, tuv12mesh2,  
    tuv13mesh2, tuv14, tuv15, tuv16mesh2, tuv17, tuv18mesh2,  
    tuv19mesh2, tuv20, tuv21mesh2, tuv22, tuv23, tuv24mesh2  
});
UV3.AddRange(new List<Vector2> {
    tuv1mesh3, tuv2, tuv3, tuv4mesh3, tuv5, tuv6mesh3,  
    tuv7mesh3, tuv8, tuv9mesh3, tuv10, tuv11, tuv12mesh3,  
    tuv13mesh3, tuv14, tuv15, tuv16mesh3, tuv17, tuv18mesh3,  
    tuv19mesh3, tuv20, tuv21mesh3, tuv22, tuv23, tuv24mesh3  
});

At this point, we have three UV arrays, three triangle arrays, and we have three (duplicate) vertex arrays. With these, we can create three separate GameObjects with their own meshes, which will interlink to create a Truncated Octahedron. We assign these three disparate objects to a single parent container object. This is essential for positioning and orienting the Truncated Octahedron as a single cohesive object, instead of juggling three separate ones.

// Create a parent object and three child objects.
GameObject compositeTOctObject = new GameObject("Truncated Octahedron " + tOctCount);
GameObject tOctObject1 = new GameObject();
GameObject tOctObject2 = new GameObject();
GameObject tOctObject3 = new GameObject();
tOctObject1.transform.SetParent(compositeTOctObject.transform);
tOctObject2.transform.SetParent(compositeTOctObject.transform);
tOctObject3.transform.SetParent(compositeTOctObject.transform);
// Equip Mesh Renderers.
tOctObject1.AddComponent<MeshRenderer>();
tOctObject2.AddComponent<MeshRenderer>();
tOctObject3.AddComponent<MeshRenderer>();
// Obtain Meshes as variables
tOctObject1.AddComponent<MeshFilter>();
Mesh mesh1 = tOctObject1.GetComponent<MeshFilter>().mesh;
tOctObject2.AddComponent<MeshFilter>();
Mesh mesh2 = tOctObject2.GetComponent<MeshFilter>().mesh;
tOctObject3.AddComponent<MeshFilter>();
Mesh mesh3 = tOctObject3.GetComponent<MeshFilter>().mesh;
// assign vertices, triangles and UV arrays.
mesh1.vertices = vertices1.ToArray();
mesh1.triangles = triangles1.ToArray();
mesh1.uv = UV1.ToArray();
tOctObject1.AddComponent<MeshCollider>();
mesh1.RecalculateNormals();
mesh2.vertices = vertices2.ToArray();
mesh2.triangles = triangles2.ToArray();
mesh2.uv = UV2.ToArray();
tOctObject2.AddComponent<MeshCollider>();
mesh2.RecalculateNormals();
mesh3.vertices = vertices3.ToArray();
mesh3.triangles = triangles3.ToArray();
mesh3.uv = UV3.ToArray();
tOctObject3.AddComponent<MeshCollider>();
mesh3.RecalculateNormals();
// Set materials.  tOctAtlas is the unfolded 2D texture of the truncated octahedron.
tOctObject1.GetComponent<Renderer>().material = tOctAtlas;
tOctObject1.isStatic = true;
tOctObject1.transform.position = new Vector3(0,0,0); // Position relative to parent object.
tOctObject2.GetComponent<Renderer>().material = tOctAtlas;
tOctObject2.isStatic = true;
tOctObject2.transform.position = new Vector3(0,0,0);
tOctObject3.GetComponent<Renderer>().material = tOctAtlas;
tOctObject3.isStatic = true;
tOctObject3.transform.position = new Vector3(0,0,0);
// Position of Greater Truncated Octahedron.
compositeTOctObject.transform.position = new Vector3(tBlock.center.X, tBlock.center.Y, tBlock.center.Z);

At this point, using the same positioning and snapping system we used for rhombohedrons in my previous post, we’re ready to tessellate Truncated Octahedrons!


Nucleic and Non-Nucleic Tessellation

Nucleic and Non-Nucleic Rhombohedral Tessellation

After creating the Rhombic Dodecahedron in Unity, it was by accident that I discovered its ability to tessellate into a Truncated Octahedron. After finally setting up my ability to tessellate Truncated Octahedrons, I sat down to make a deeper investigation of this phenomenon, diving into it with a little bit more systematic focus. I quickly realized that the Truncated Octahedron is not the only special shape that the Rhombohedral Tessellation can produce.

Depending on the Nucleus of the Tessellation, the Rhombic Dodecahedron will either form a Cuboctahedron or a Truncated Octahedron.

A Nucleic Tessellation involves starting with a single rhombohedron and adding new rhombohedrons to each of its 12 faces. The tessellation’s centerpoint will be the exact center of the starting rhombohedron. This single rhombohedron is the tessellation’s nucleus, and the tessellation will form a Cuboctohedron.

A Non-Nucleic Tessellation involves starting with 6 close-packed rhombohedrons: two stacked groups of three, rotationally offset by 180 degrees. The tessellation’s center lies at the point where all six of these rhombohedron’s touch. Its center is outside of any rhombohedron; it is at the point where the six centermost rhombohedrons touch (their osculation). There is no nucleus, and the tessellation will form a Truncated Octahedron.

Minimal (smallest possible) non-nucleic Rhombohedral Tesselation from its two axes of symmetry, isometric view
Angled Isometric View

If we draw out these tessellations for a few layers, the difference between the nucleic tessellation and the non-nucleic tessellation becomes very obvious.

A Non-Nucelic and Nucleic Rhombohedral Tessellation, Hulls Applied

The emergence of a Cuboctahedron is significant: the Cuboctahedron is the Polyhedral Dual of the Rhombic Dodecahedron. More on this in the next section.

Nucleic and Non-Nucleic Truncated Octahedral Tessellation

Next, I tried creating Nucleic and Non-Nucleic tessellations using the Truncated Octahedron. The results were not quite as clean.

First, here is the starting-structure for the non-nucleic tessellation. Just like with the Rhombic Dodecahedron, the structure’s center of mass will be at the osculation of the six truncated octahedra, outside of any constituent shape.

If we draw out these tessellations for a few layers, we receive a very strange emergent shape.

Unlike the Rhombic Dodecahedron, a non-nucleic tessellation does not produce the t. Octahedron’s dual (Tetrakis Hexahedron). In fact, I am not quite sure what this shape is. I had hoped for another strange, synergistic shape to emerge – but this one is quite unexpected.

The shape clearly resembles the rhombic dodecahedron, but with four of its fourteen vertices truncated. It is almost a truncated rhombic dodecahedron (that would have been fucking fascinating), but it isn’t: it’s sortof a “semi-truncated” rhombic dodecahedron. After some pretty thorough search online, I can’t find any specific names for the shape. It is comprised of eight pentagons and four rhombuses (still a dodecahedron). From some angles, it’s quite symmetrical – dare I say elegant?

Yet from other angles, it is quite lopsided – dare I say derpy?

Whatever it is, the shape is comprised of rhombuses and pentagons: for now, I am calling it the Rhombipentic Dodecahedron, until I find a pre-existing formalized name for it.

This shape gives me great pause: I wonder if something about my non-nucleic tessellation has gone wrong. Perhaps an error on my behalf while tessellating? Perhaps my starting minimum non-nucleic shape is not actually correct? Where the Rhombic Dodecahedron’s tessellation produces an outpouring of seemingly mystical connections, one after the next (more on this in the next section), the Rhombipentic Dodecahedron feels quite out of place. There is a lot more to explore here.

Dual Polyhedra and Supertruncation

The Cuboctahedron is a fascinating shape to emerge from the rhombohedral tessellation. First, The Cuboctahedron is the Polyhedral Dual of the Rhombic Dodecahedron.

Every shape has a Polyhedral Dual: another shape whose vertices correspond with each of its faces, and vice versa. If we place a new vertex at the centroid of every face belonging to some polyhedron A, and connect those new vertices to form a convex, inner shape named polyhedron B, polyhedron B would be the dual of A.

Platonic Duals. The Tetrahedron is a self-dual!
The Dual relationship of the Rhombic Dodecahedron and Cuboctahedron made clear

When I first started playing with the rhombohedron and eventually noticed the eerie emergent Truncated Octahedron, my first guess at its relationship with the Rhombic Dodecahedron was that the two shapes were duals. I quickly learned that this was not the case, but the Cuboctahedron is actually somewhat related to the Truncated Octahedron – it’s a supertruncated octahedron, if you will.

Progressively more extreme truncations of the Octahedron

Here’s where things get really wild. If you continue truncating the cuboctahedron, you eventually receive a cube.

If you remember from my last post, the cube and octahedron are very important shapes with respect to the rhombic dodecahedron: The rhombic dodecahedron’s vertices are produced by overlaying a cube and an octahedron!

Cube in blue; Octahedron in green; Convex Hull in light green

Pulling it all together

What is going on here? We have observed a number of very interesting, slightly eerie, emergent phenomena by tessellating the Rhombic Dodecahedron and Truncated Octahedron. We’ve also seen some goofy and (seemingly) inconsistent behavior from the Truncated Octahedron’s non-nucleic tessellation. We’ve made a lot of connections, but we still don’t have a grip on a bigger picture.

Here is what we know.

  1. Both the Rhombic Dodecahedron and the Truncated Octahedron can be tessellated in 3D space in one of two ways:
    • Nucleic Tessellation is centered on a single polyhedron.
    • Non-Nucleic Tessellation is centered on the osculation of six polyhedra; there is no individual shape at its center.
  2. Nucleic and Non-Nucleic Rhombohedral Tessellations produce different shapes:
    • The Rhombic Dodecahedron’s Nucleic Tessellation produces the Cuboctahedron.
      • The Cuboctahedron is the Polyhedral Dual of the Rhombic Dodecahedron. It is not space filling.
    • The Rhombic Dodecahedron’s Non-Nucleic Tessellation produces the Truncated Octahedron. It is space filling.
      • Both the Truncated Octahedron and the Cuboctahedron are in fact truncations of the octahedron; the Truncated Octahedron is simply less “truncated” than the Cuboctahedron.
  3. Nucleic and Non-Nucleic Truncated Octahedral Tessellations produce different shapes:
    • The Truncated Octahedron’s Nucleic Tessellation produces the Rhombic Dodecahedron. It is space filling.
      • Herein lies the initial “Harmonic relationship” first observed, which prompted this descent into shape hell.
    • The Truncated Octahedron’s Non-Nucleic Tessellation produces the “Rhombipentic Dodecahedron.” It is not space-filling, regular, semi-regular or archimedean.
  4. The Rhombic Dodecahedron’s two emergent shapes (the Truncated Octahedron and the Cuboctahedron) can both be produced by the truncation of an octahedron. Truncating an octahedron far enough eventually produces a cube.
    • The Rhombic Dodecahedron’s vertices are the union of an octahedron and cube.
    • The octahedron and cube are Polyhedral Duals.
    • The rhombic dodecahedron and cuboctahedron are Polyhedral Duals.

Where do we go from here?

We have collected a lot of information, but I still do not feel as if we have found a satisfying explanation for the harmonic relationship exhibited between the Rhombic Dodecahedron and the Truncated Octahedron.

There is a ton of data here, and we have only investigated two space-filling shapes. It may be that by extending these experiments to other space-filling shapes, we might be able to reproduce what we have already found. There are very few regular and semi-regular space-filling polyhedra, so these are a good place to start.

We might be able to produce similar patterns in two dimensions by using plane-filling polygons, like triangles, squares, and hexagons.

Last, some readers may have noticed the many parallels between nucleic / non-nucleic rhombohedral tessellation and sphere packing. It may be that because the Rhombic Dodecahedron exhibits the same packing pattern as spheres, what we are actually looking at has less to do with space-filling tessellations and more to do with the sphere-packing problem. The fact that the Rhombic Dodecahedron packs in the same way that spheres do might be nothing but an interesting coincidence – an odd example of a much deeper and much more well-explored area of geometry (sphere packing). This would help to explain the fact that the non-nucleic truncated octahedral tessellation did not create as synergistic a shape as I had hoped it would – what we are seeing might not be a property of specific shapes, but a property of spheres.

As I explore these avenues, I am also working my way through three texts, which investigate these topics in great depth:

  • Order in Space, Keith Critchlow
  • Regular Polytopes, HSM Coxeter
  • Synergetics, Buckminster Fuller

Perhaps one of these books explains everything we’re seeing. Perhaps not! Stay posted for the next chapter of this geometric odyssey.

Bonus Content: Grossular Garnet Samples

Attending a recent music festival, I wandered my way into a strip of vendors selling gemstone samples. After a couple hours of digging, I managed to find these incredible Grossular Garnet samples. Garnet has the natural habit of the Rhombic Dodecahedron. I think it’s incredible that nature can form these incredible patterns so clearly.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s