"""
Author:  Kirby Urner, 4D Solutions, Sept. 15 2005
Last modified:  Sept. 17 2005
Version #: 1.3

Developed for a Saturday Academy class (PSU, Portland)[1]
In a curriculum context, some parts are left unfinished, plus
even this version is suggestive of possible enhancements,
e.g. the Camera and Header APIs could be far more developed.

[1] this is during a time after SA had moved to PSU from OGI
"""

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)
    

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)


class Vertex(object):

    color = 'Red'
    radius = 0.1
    
    def __init__(self, v0):
        self.v0 = v0

    def __repr__(self):       
        return "Sphere %s, radius = %s" % (self.v0.coords, self.radius)

    def write(self):
        basic = "sphere {%s, %s" % (self.v0.coords, self.radius)
        return "%s %s" % (basic, "pigment { color %s } no_shadow}" % self.color)
    
class Polyhedron(object):

    color = 'Red'

    def __init__(self, vertsdict, faces):
        self.verts = vertsdict
        self.faces = faces
        self.edges = self.__get_edges()

    def __repr__(self):
        return "Polyhedron: %s vertices, %s edges, %s faces" \
               % (len(self.verts), len(self.edges), len(self.faces))

    def __get_edges(self):
        """
        take a list of face-tuples and distill
        all the unique edges,
        e.g. ((1,2,3)) => ((1,2),(2,3),(3,1))
        e.g. icosahedron has 20 faces and 30 unique edges
        ( = cubocta 24 + tetra's 6 edges to squares per
        jitterbug)
        """
        uniqueset = set()
        for f in self.faces:
            edgetries = zip(f, f[1:]+ (f[0],))
            for e in edgetries:
                e = tuple(sorted(e))
                uniqueset.add(e)                
        return tuple(uniqueset)
        
    def write(self, name):
        """
        generate the edges and vertices in POV-Ray
        scene description language
        """
        lines = ['#declare %s = union {' % name]
        for e in self.edges:
            edge = Edge( self.verts[e[0]], self.verts[e[1]] )
            edge.color = self.color
            lines.append(edge.write())
        for v in self.verts.values():
            sphere = Vertex(v)
            sphere.color = self.color
            lines.append(sphere.write())
        lines.append("}")
        lines.append("object {%s}\n" % name)
        return '\n'.join(lines)

    def __mul__(self, scalar):
        newvectors = {}
        for v in self.verts:
            newvectors[v] = self.verts[v] * scalar
        return Polyhedron(newvectors, self.faces)

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__


class Camera (object):

    # defaults

    look_at  = 0
    location = '<0, .1, -25>'
        
    @staticmethod
    def write():
        return """
camera {
  location %(location)s
  look_at %(look_at)s
}
""" % Camera.__dict__
    
def buildicosa():

    """
    Build an icosahedron from three mutually perpendicular
    golden rectangles
    """
    
    phi = (1 + pow(5,0.5))/2.0
    
    verts = {# 2*phi x 2 rectangle in XZ

              1:Vector((-phi,  0,  -1)),
              2:Vector((-phi,  0,   1)),
              3:Vector(( phi,  0,   1)),
              4:Vector(( phi,  0,  -1)),
             # 2*phi x 2 rectange in XY

              5:Vector(( -1,  phi,  0)),
              6:Vector((  1,  phi,  0)),
              7:Vector((  1, -phi,  0)),
              8:Vector(( -1, -phi,  0)),
             # 2*phi x 2 rectange in YZ

              9:Vector((  0,  1,  phi)),
             10:Vector((  0, -1,  phi)),
             11:Vector((  0, -1, -phi)),
             12:Vector((  0,  1, -phi))}
    
    faces = ((5,6,9),(5,9,2),(5,2,1),(5,1,12),(5,12,6),
             (1,11,12),(11,12,4),(12,4,6),(4,6,3),(6,3,9),
             (3,9,10),(9,10,2),(10,2,8),(2,8,1),(8,1,11),            
             (7,11,4),(7,4,3),(7,3,10),(7,10,8),(7,8,11))
    
    return verts, faces

def main():
    icosa = Polyhedron(*buildicosa())

    # overwriting defaults:

    
    # Halloweeny skin (or Princetonian as the case may be)

    icosa.color = 'Orange'
    Header.bgcolor = 'Black'   
    Header.lightloc1 = '<0, 0, 0>'
    Camera.location = '<2, 4, -3>'  
    f = file('icosa.pov','w')    
    print >> f, Header.write()
    print >> f, Camera.write()
    print >> f, icosa.write("bigicosa") # shapes need a name


    # added a lush green interior icosa, why not?

    # smaller is edgewise 80% of original,

    # 0.512 original volume

    icosasmall = icosa * 0.8  
    icosasmall.color = 'Green'    
    print >> f, icosasmall.write("smallicosa")
    
    f.close()  # just to be neat & tidy


if __name__ == '__main__':
    main()
    
# code highlighted using py2html.py version 0.8