Building Polyhedra First posted: Sept 15, 2005
My typical design over the years has been to isolate the mathematical objects, such as vectors and polyhedra, from their output format. Math objects should be platform agnostic in some sense. However, convenience and theoretical purity represent a tradeoff a lot of the time, and in this use case, I eliminate the middle man by letting my math objects "speak POVRay" i.e. my vector objects know how to write scene description language directly and don't have to go through some POVwriter object per my habitual design. This might be a good way to teach refactoring. Start with POVRay as your only output format, and develop around that for awhile. Then introduce VRML and/or X3D. Now it becomes more obvious why we might want specialized writer objects to translate math objects into specific output formats. But that needn't be where we start. Starting with POVRay alone is a fine beginning. The Vector class provides just enough infrastructure to support scaling and adding. In this example, we don't rotate our vectors. This is consistent with the objective: to get some scene description language for POVRay. POVRay has its own easytouse rotation methods. Let's just rely on those for now  no reason to clutter our story with too many convergent/divergent plot lines. class Vector(object): color = 'Red' def __init__(self, xyz): self.xyz = xyz def write(self): basic = "cylinder {<0,0,0>, <%s,%s,%s>, 0.1" % self.xyz return "%s %s" % (basic, "pigment { color %s } no_shadow }" % self.color) def __repr__(self): return "Vector <%s, %s, %s>" % self.xyz def __mul__(self, scalar): xyz = tuple([scalar * i for i in self.xyz]) return Vector(xyz) def __add__(self, other): xyz = tuple([ i+j for i,j in zip(self.xyz, other.xyz)]) return Vector(xyz) def _get_xyz(self): return '<%s, %s, %s>' % self.xyz def _get_length(self): return pow(sum([i**2 for i in xyz]), 0.5) coords = property(_get_xyz) length = property(_get_length) As I explained to my daughter yesterday, screenwriters often kill off characters just to keep the plot lines manageable. If too many people learn of Clark's secret in Smallville, then the storylines fly apart. So yes, the tabloid journalist learns the truth, but Clark's dad destroys the tape, and Lex has to shoot the journalist (who is selfishly trying to kill the dad). But I digress. Just keep it simple, is the lesson here.[1] The Edge class needs two vectors to define an edge. A vector alone always considers the origin to be its tail, so one xyz tuple is sufficient to place a POVRay cylinder. An edge, on the other hand, starts anywhere and ends anywhere, so two vectors are required, and the edge runs tip to tip. This has long been my practice.[2] class Edge(object): color = 'Red' def __init__(self, v0, v1): self.v0 = v0 self.v1 = v1 def __repr__(self): return "Edge %s, %s" % (self.v0.coords, self.v1.coords) def write(self): basic = "cylinder {%s, %s, 0.1" % (self.v0.coords, self.v1.coords) return "%s %s" % (basic, "pigment { color %s } no_shadow }" % self.color) The Sphere class (I almost called it the Point class  woulda been OK) gets used in Polyhedra to round off the edge cylinders. The Polyhedron class needs two data structures: a dictionary of vectors and a "wiring diagram" showing how the vector tips get connected into faces. From facetuples, edges are distilled. Again, our polyhedra know how to output themselves directly in POVRay's scene description language, without a middle man. My Header and Camera classes are new. My POVwriter used to take care of those details, but since we're talking directly to POVRay here, we might as well split these out and suggest an expandable API. class Header (object): # defaults bgcolor = "color Gray50" lightloc0 = "<300, 300, 1000>" lightloc1 = "<300, 300, 1000>" lightcolor = "White" @staticmethod def write(): return """ #include "colors.inc" background { %(bgcolor)s } light_source{ %(lightloc0)s %(lightcolor)s } light_source{ %(lightloc1)s %(lightcolor)s } """ % Header.__dict__ Given the large number of usermodifiable parameters when it comes to the header and camera, why not just set a bunch of default attributes, then string substitute directly from the class dictionary, in case any were changed? This design gives me an opportunity to show static methods in action. My demo main() calls a function that builds an icosahedron from three mutually perpendicular golden rectangles. Coming up with the "wiring diagram" (face tuples) may require a student to draw and/or label a physical model. I used StrangeAttractors from Design Science Toys (this was not Roger Silber's design, was a far superior toy to Roger's Connection).[3]
The main() function also scales the first icosahedron by 80% to get a smaller one, nested inside in a different color (see output above).
[1] Smallville: a popular TV series dealing with the mythical superman character's boyhood in the fictional Smallville, Kansas (much of the series was actually shot in British Columbia, Canada). [2] If you want to see a linear algebra textbook that takes a very similar approach, check out Elementary Linear Algebra by Stewart Venit and Wayne Bishop, ISBN 0534916899 (see Lines in R^{m}, Chapter 1). I added this to my collection after participating in debates with Wayne on mathteach (Math Forum) for a number of years. [3] There's a long and twisted story regarding the relationship between Roger of Roger's Connection and Stu & Cary's Design Science Toys  but here is not the place for it. For further reading:

