Building the Unit Icosahedron

This post builds on the concepts contained in the previous post: “The Icospherical World Model: Problem Specification and Preliminary Data Structure.”

In this post, we would like to use our atomic icospherical building block, the Triangle, to build up the basic Icosahedron, which is an icosphere of recursive depth zero.

Our first step is to simply define the 12 vertices of the unit Icosahedron. From a simple Google search, Wikipedia reveals the (really interesting) fact that an Icosahedron’s vertices can be represented by 12 vertices of three orthogonal Golden Rectangles.

An Icosahedron, Represented by Three Orthogonal Golden Rectangles.

A Golden Rectangle is any Rectangle in keeping with the Golden Ratio. That is, the ratio of the rectangle’s sides, a and b, is equivalent to the ratio of a+b to the longer of the two sides.

With this knowledge, generating the vertices of our icosahedron is simple:

//////////////////////////////////////////////////////////
// GENERATE UNIT VERTICES using 3 Orthogonal Rectangles //
//                                                      //
//                    /|  - R1                          //
//                   / |                                //
//                  /  |                                //
//                 /   |                                //
//                |    |                                //
//               _|    |_______                         //
//      ________/_|   _|______/__                       //
//     |          |  |           |                      //
//     |      ____|  |______     |                      //
//     |     /    | /      /     |                      //
//     |____/     |/      /______|  - R2                //
//         /             /                              //
//        /_____________/                               //
//                |    |                                //
//         ^      |    /                                //
//        R3      |   /                                 //
//                |  /                                  //
//                | /                                   //
//                |/                                    //
//                                                      //
//////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////

// The golden ratio
float t = (1.0f + (float) Math.Sqrt(5.0)) / 2.0f;

// A collection of the 12 base vertices.
Dictionary<int, Vector3> vertices = new Dictionary<int, Vector3>();

// Trace the four vertices of a Golden rectangle [R1]
vertices.Add(0, addVertexToUnitSphere(new Vector3(-1,  t,  0)));
vertices.Add(1, addVertexToUnitSphere(new Vector3( 1,  t,  0)));
vertices.Add(2, addVertexToUnitSphere(new Vector3(-1, -t,  0)));
vertices.Add(3, addVertexToUnitSphere(new Vector3( 1, -t,  0)));

// Trace the four verices of a Golden rectangle orthagonal to the last [R2]
vertices.Add(4, addVertexToUnitSphere(new Vector3( 0, -1,  t)));
vertices.Add(5, addVertexToUnitSphere(new Vector3( 0,  1,  t)));
vertices.Add(6, addVertexToUnitSphere(new Vector3( 0, -1, -t)));
vertices.Add(7, addVertexToUnitSphere(new Vector3( 0,  1, -t)));

// Trace the four verices of a Golden rectangle orthagonal to the last two [R3]
vertices.Add(8, addVertexToUnitSphere(new Vector3( t,  0, -1)));
vertices.Add(9, addVertexToUnitSphere(new Vector3( t,  0,  1)));
vertices.Add(10, addVertexToUnitSphere(new Vector3(-t, 0, -1)));
vertices.Add(11, addVertexToUnitSphere(new Vector3(-t, 0,  1)));

Notice that we are adding these vertices to the Unit Sphere right off the bat, using the convenient private method:

// Add a vector to the unit sphere.
private Vector3 addVertexToUnitSphere(Vector3 v)
{
    float length = (float) Math.Sqrt(v.X*v.X + v.Y*v.Y + v.Z*v.Z);
    return new Vector3(v.X/length, v.Y/length, v.Z/length);
}

Now that we have the vertices of the unit icosahedron, we need to create Triangles with them.

Knowing that vertices 0-3 belong to the first rectangle, vertices 4-7 belong to the second triangle, and vertices 8-11 belong to the third, we can easily construct our 20 Triangle objects using our vertices.

In our last post, we did not discuss the Triangle constructor. In the course of our development, we will create numerous different Triangle constructors for different purposes. But the constructor used below accepts the following arguments:

  • Unique Index (int)
  • Recursive Depth (int)
  • v1 (Vector3)
  • v2 (Vector3)
  • v3 (Vector3)
Dictionary<int, Triangle> faces = new Dictionary<int, Triangle>();

// 5 faces around point 0
faces.Add(0, new Triangle(0, 0, vertices[0], vertices[11], vertices[5]));
faces.Add(1, new Triangle(1, 0, vertices[0], vertices[5], vertices[1]));
faces.Add(2, new Triangle(2, 0, vertices[0], vertices[1], vertices[7]));
faces.Add(3, new Triangle(3, 0, vertices[0], vertices[7], vertices[10]));
faces.Add(4, new Triangle(4, 0, vertices[0], vertices[10], vertices[11]));

// 5 adjacent faces
faces.Add(5, new Triangle(5, 0, vertices[1], vertices[5], vertices[9]));
faces.Add(6, new Triangle(6, 0, vertices[5], vertices[11], vertices[4]));
faces.Add(7, new Triangle(7, 0, vertices[11], vertices[10], vertices[2]));
faces.Add(8, new Triangle(8, 0, vertices[10], vertices[7], vertices[6]));
faces.Add(9, new Triangle(9, 0, vertices[7], vertices[1], vertices[8]));

// 5 faces around point 3
faces.Add(10, new Triangle(10, 0, vertices[3], vertices[9], vertices[4]));
faces.Add(11, new Triangle(11, 0, vertices[3], vertices[4], vertices[2]));
faces.Add(12, new Triangle(12, 0, vertices[3], vertices[2], vertices[6]));
faces.Add(13, new Triangle(13, 0, vertices[3], vertices[6], vertices[8]));
faces.Add(14, new Triangle(14, 0, vertices[3], vertices[8], vertices[9]));

// 5 adjacent faces
faces.Add(15, new Triangle(15, 0, vertices[4], vertices[9], vertices[5]));
faces.Add(16, new Triangle(16, 0, vertices[2], vertices[4], vertices[11]));
faces.Add(17, new Triangle(17, 0, vertices[6], vertices[2], vertices[10]));
faces.Add(18, new Triangle(18, 0, vertices[8], vertices[6], vertices[7]));
faces.Add(19, new Triangle(19, 0, vertices[9], vertices[8], vertices[1]));

Now that all of our Triangles exist, we can ascribe them adjacency data.

I wish that I had an impressive, two-line, mathematically elegant method that I wrote in order to assign this adjacency data here, but I do not. I have very tediously hand-written each of the 20 faces’ adjacency data, but the good news is: once the R-0 adjacency data is written, we never need to handwrite any adjacency data again.

This is because, as we recurse each face, we can programatically generate adjacency data as we go. As long as the R0 structure has accurate adjacency data, all descendants will be able to iterate their own accurate adjacency data.

The following code is almost not worth including, but I will include it here in the off chance that some wayfaring developer on the internet is trying to replicate my code, so that they can simply have these adjacency values instead of scratching their head for an hour, turning over a d20 in their hands like I did. You may consider wrapping these assignments in a handy “setAdjacencies” method, as I have written to do in my own personal backlog.

Remember: The order of adjacencies is of crucial importance.

// Face 0 adjacencies;
faces[0].adjacency1=faces[4];
faces[0].adjacency2=faces[6];
faces[0].adjacency3=faces[1];

// Face 1 adjacencies; 
faces[1].adjacency1=faces[0];
faces[1].adjacency2=faces[5];
faces[1].adjacency3=faces[2];

// Face 2 adjacencies; 
faces[2].adjacency1=faces[1];
faces[2].adjacency2=faces[9];
faces[2].adjacency3=faces[3];

// Face 3 adjacencies;  
faces[3].adjacency1=faces[2];
faces[3].adjacency2=faces[8];
faces[3].adjacency3=faces[4];

// Face 4 adjacencies; 
faces[4].adjacency1=faces[3];
faces[4].adjacency2=faces[7];
faces[4].adjacency3=faces[0];

// Face 5 adjacencies; 
faces[5].adjacency1=faces[1];
faces[5].adjacency2=faces[15];
faces[5].adjacency3=faces[19];

// Face 6 adjacencies; 
faces[6].adjacency1=faces[0];
faces[6].adjacency2=faces[16];
faces[6].adjacency3=faces[15];

// Face 7 adjacencies; 
faces[7].adjacency1=faces[4];
faces[7].adjacency2=faces[17];
faces[7].adjacency3=faces[16];

// Face 8 adjacencies; 
faces[8].adjacency1=faces[3];
faces[8].adjacency2=faces[18];
faces[8].adjacency3=faces[17];

// Face 9 adjacencies; 
faces[9].adjacency1=faces[2];
faces[9].adjacency2=faces[19];
faces[9].adjacency3=faces[18];

// Face 10 adjacencies; 
faces[10].adjacency1=faces[14];
faces[10].adjacency2=faces[15];
faces[10].adjacency3=faces[11];

// Face 11 adjacencies; 
faces[11].adjacency1=faces[10];
faces[11].adjacency2=faces[16];
faces[11].adjacency3=faces[12];

// Face 12 adjacencies; 
faces[12].adjacency1=faces[11];
faces[12].adjacency2=faces[17];
faces[12].adjacency3=faces[13];

// Face 13 adjacencies; 
faces[13].adjacency1=faces[12];
faces[13].adjacency2=faces[18];
faces[13].adjacency3=faces[14];

// Face 14 adjacencies; 
faces[14].adjacency1=faces[13];
faces[14].adjacency2=faces[19];
faces[14].adjacency3=faces[10];

// Face 15 adjacencies; 
faces[15].adjacency1=faces[10];
faces[15].adjacency2=faces[5];
faces[15].adjacency3=faces[6];

// Face 16 adjacencies; 
faces[16].adjacency1=faces[11];
faces[16].adjacency2=faces[6];
faces[16].adjacency3=faces[7];

// Face 17 adjacencies; 
faces[17].adjacency1=faces[12];
faces[17].adjacency2=faces[7];
faces[17].adjacency3=faces[8];

// Face 18 adjacencies; 
faces[18].adjacency1=faces[13];
faces[18].adjacency2=faces[8];
faces[18].adjacency3=faces[9];

// Face 19 adjacencies; 
faces[19].adjacency1=faces[14];
faces[19].adjacency2=faces[9];
faces[19].adjacency3=faces[5];

With this, we have represented the baseline, R0 Icosahedron. We are now ready to concern ourselves with recursing the faces of the Icosahedron – at first uniformly, and then, asymmetrically.

Special Note

It is very important to PISES that the “data systems” remain totally airgapped from Unity, the system which I am using to render the software. If one day, for whatever reason, I decide to break up with Unity, it is very important that PISES be able to separate cleanly from the gaming engine.

For this reason, we are creating this Icosphere fully in the abstract, with no Unity libraries, texture data, UV data or mesh information.

When we are finished with the development of AbstractIcosphere, as I have named the class, we will concern ourselves with translating this abstract data structure into a visual mesh (or system of meshes) in Unity. For now, we will keep things purely in the abstract.

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