Oregon Curriculum Network
Discovering Math with Python

Chapter 7: POLYHEDRONS

The ray-tracing program POV-Ray, free and open source, understands XYZ coordinates, so at the end of the day we'll need those for rendering our polyhedrons as ray tracings.

However, building our small vocabulary of 26 vertexes will make use of a more congenial set of 4-tuples known as Quadray coordinates.

We will compute and then store our polyhedron vertexes using Quadray Coordinates (see Chapter 6) and then convert to XYZ on the fly, when it's time to actually write scene description language for POV-Ray to process.

Do not confuse Quadrays with Quaternions, the subject of Chapter 11.

Polyhedrons as a Data Structure

Consider the Tetrahedron, the simplest of all polyhedrons, also known as a Simplex for that reason. Its vertexes, labeled 'A', 'B', 'C', 'D' give us a way to represent the faces as a tuple of tuples: (('A', 'B', 'C'), ('A', 'B', 'D'), ('A', 'C', 'D'), ('B', 'C', 'D')).

'A' through 'D' may be considered the names of point vectors.

The dual Tetrahedron (-A, -B, -C, -D) intersects our first at its mid-edges and together these define the eight vertexes of a cube. The cube's dual, an octahedron, has vertexes we might define as vector sums of those already given.

The combination of the cube and octahedron define the twelve diamond faced rhombic dodecahedron, a favorite of Kepler's, and a space-filler. It's dual, the cuboctahedron, will be the final shape we include. All 26 vertexes labeled 'A' through 'Z' may be developed as vector sums of the initial A, B, C, D.

However, before we get to our actual dict of vectors, lets take a look at the Polyhedron that's shaping up, based on the transformations we'll be needing.

The more complete version will be in the accompanying source code file, polyhedrons.py.


In [1]:
from qrays import Vector # see Chapter 6

class Polyhedron:
    
    def __init__(self, name, volume, faces : set, 
                 vertexes : dict, center = Vector((0,0,0))):        
        self.name = name
        self.vertexes = vertexes
        self.volume = volume
        self.faces = faces
        self.edges = self._distill() # derive unique edges from faces
        self.center = center
        
    def _distill(self):
        """
        get all edge pairs from faces
        """
        edges = set()
        for face in self.faces:
            new_pairs = {tuple(sorted([a, b])) # sorting pairs keeps them unique
                         for a,b in zip(face, face[1:] + (face[0],))}
             # ('B','A') won't sneak in as another ('A','B')
            edges = edges.union(new_pairs) 
        return edges
    
    def translate(self, slider : Vector):
        """
        slide to a new position, keep track of the center
        """
        pass
    
    def rotate(self, degrees, axis):
        """
        We will use a matrix to rotate around x, y or z axis
        """            
        pass
    
    def scale(self, scale_factor):
        """
        resize by scale_factor and emit a new polyhedron
        Volume changes as a 3rd power of scale_factor.
        """
        pass
    
    def render(self, color, filename = "output.pov"):
        """
        build a povray file in Scene Description Language
        """
        pass
    
    def __repr__(self):
        return "Polyhedron({})".format(self.name)

The polyhedrons we've talked about will be instances of our Polyhedron class. Once instantiated, they each keep their vertex labels and pass them on to their progeny. To transform a polyhedron, as we will do in the next chapter, is to create a new one, not to change an existing one in place. Once defined, a polyhedron should be considered immutable.

Quadrays are just like XYZ vectors except they're described by 4-tuples indicating how much of three of the four directions are needed to reach a point. The basis vectors point to the corners of a regular tetrahedron from its center, dividing space intor four quadrants. The basis vector pointing away from the quadrant where our point is, will have a coefficient of 0.

Lets look at some examples:


In [2]:
from qrays import Qvector, Vector
tet1 = {}
tet1['A'] = Qvector((1,0,0,0))
tet1['B'] = Qvector((0,1,0,0))
tet1['C'] = Qvector((0,0,1,0))
tet1['D'] = Qvector((0,0,0,1))
for v in tet1:
    print(tet1[v].xyz())


xyz_vector(x=Decimal('0.3535533905932737308575042334'), y=Decimal('0.3535533905932737308575042334'), z=Decimal('0.3535533905932737308575042334'))
xyz_vector(x=Decimal('-0.3535533905932737308575042334'), y=Decimal('-0.3535533905932737308575042334'), z=Decimal('0.3535533905932737308575042334'))
xyz_vector(x=Decimal('-0.3535533905932737308575042334'), y=Decimal('0.3535533905932737308575042334'), z=Decimal('-0.3535533905932737308575042334'))
xyz_vector(x=Decimal('0.3535533905932737308575042334'), y=Decimal('-0.3535533905932737308575042334'), z=Decimal('-0.3535533905932737308575042334'))

Qvector A is in the all-positive XYZ octant (+,+,+), whereas Qvector B is still above the XY plane but kitty-corner in octant (-,-,+). The other two points form a segment below the XY plane and perpendicular to the first, in (-,+,-) and (+,-,-) respectively.

Placing your forearms perpendicular to one another, but some distance apart, gives the same idea. Your wrists and elbows will be the four vertexes in question. Opposite edges of the regular tetrahedron are at 90 degrees to one another.

We're now ready to define our polyhedron faces accordingly and feed them into the class, along with the dict of needed vectors (or Qvectors in this case).


In [3]:
Tet1 = Polyhedron("Tetrahedron", volume = 1, 
                  faces = {('A', 'B', 'C'), ('A', 'B', 'D'), 
                           ('A', 'C', 'D'), ('B', 'C', 'D')},
                  vertexes = tet1)

In [4]:
Tet1.edges  # lets make sure _distill() did its job


Out[4]:
{('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')}

In [5]:
tet2 = {}  # now for the dual tetrahedron
tet2['E'] = -tet1['A']
tet2['F'] = -tet1['B']
tet2['G'] = -Qvector((0,0,1,0)) # same as -tet1['C']
tet2['H'] = -Qvector((0,0,0,1)) # same as -tet1['D']

In [6]:
Tet2 = Polyhedron("Dual Tetrahedron", volume = 1, 
                  faces = {('E', 'F', 'G'), ('E', 'F', 'H'), 
                           ('E', 'F', 'G'), ('F', 'G', 'H')},
                  vertexes = tet2)

In [7]:
Tet2.edges


Out[7]:
{('E', 'F'), ('E', 'G'), ('E', 'H'), ('F', 'G'), ('F', 'H'), ('G', 'H')}

Back to Chapter 6: Vectors in Space
Continue to Chapter 8: Transformations
Introduction / Table of Contents