"""

Version 3.1  April 4, 2005

This version is compatible with Python 2.4 and above ONLY.

Creates a network of VPython animations connected through key
frames, with a random selection at each frame.  Like a network
of subway tunnels, and a choice of where to go next at each
station (tunnel = scenario, nodelist = options of where to go
next i.e. any scenario starting with current key frame.

The theme of this hypertoon is Buckminster Fuller's concentric
hierarchy of polyhedra, complete with jitterbug transformation
and allusions to the CCP.

Depends on / compatible with rbf.py Version 1.3 (March 14, 2005)

===============================================================
History

March 15, 2009:  added scenario 31, squished some bugs tracing to
the fact that scalar multiplication of a polyhedron alters the
original in this implementation of rbf.py i.e. doesn't return
a new copy.  Since the last modifications, we've added a volume
7.5 rhombic triacontahedron or radius phi/sqrt(2) that intersects
the volume 6 rhombic dodecahedron.  We develop it starting with
the rt of volume 5, scaling by 3rd root of 1.5 i.e. volume goes
up as a 3rd power to 7.5.

April 4, 2005:  in an effort to prove to myself that I could still
think straight, after a week-long fever, I added a couple more
scenarios (one to shrink-wrap the rhombic triacontahedron around
a sphere, one to draw a smaller cuboctahedron inside the green
cube (volume 2.5)).

March 31, 2005:
Scott's modifications were to an older version, so integrated the
changes.

March 27, 2005:
by Scott David Daniels to make use decorators syntax (2.4).  The
transition function returns the original (a transition), but also
populates the transitions dictionary, thereby eliminating much code.
He also beefed up the command line situation (and used random.choice
instead of random.randint -- now why didn't I think of that?).

March 15, 2005:
got rid of silly eval and Vset class -- a plain dictionary works
incorporated changes by John Zelle (more startup options, 2.3 compat)
rewrote all the jitterbug and cube transformations to redraw
  existing cylinders and vertices, instead of using delete/draw
  double buffering -- makes for much smoother motions

March 14 2005:
29 scenarios, 8 key frames
Fixed bugs in jitterbug
Put rbf canonical points inside a class, use copies of
Sped it up, other aesthetic improvements
Added more five-fold shapes place balls growing/shrinking
Removed dependencies on colors.py coords.py
Implemented threading (2 hypertoons run together)
Added stereo option

First posted:  March 11 2005 16 scenarios, 6 key frames
Original proof of concept
Background posted to Python/edu-sig
http://mail.python.org/pipermail/edu-sig/2005-March/004499.html

John Zelle gets it in stereo!
http://mail.python.org/pipermail/edu-sig/2005-March/004503.html

Kirby Urner
4D Solutions

"""


from random import choice
import threading
import rbf
import sys

transitions = {}
verbose = True

def transition(initial, final):
    def record_transition(transition):
        try:
            surprise = transitions[initial][final]
        except KeyError:
            pass # this is expected
        else:
            print >>sys.stderr, 'Changing %s -> %s: from %r to %r' % (
                initial, final, surprise.__name__, transition.__name__)
        transitions.setdefault(initial, {})[final] = transition
        return transition
    return record_transition


@transition('rh', 'octa')
def trans0(thing):
    """trans0: rh -> octa"""
    rh = rbf.Rhdodeca()
    # A: rh dodeca all alone
    rh.draw()
    if thing is not None:
        thing.delete()
    oc = rbf.Octa()
    oc.draw(trace=True)
    rh.delete()
    # B: octahedron all alone
    return oc

@transition('rh', 'cube')
def trans1(thing):
    """trans1: rh -> cube"""
    rh = rbf.Rhdodeca()
    # A: rh dodeca all alone
    rh.draw()
    if thing is not None:
        thing.delete()
    cb = rbf.Cube()
    cb.draw(trace=True)
    rh.delete()
    # C: cube all alone
    return cb

@transition('rh', 'tetra')
def trans2(thing):
    """trans2: rh -> tetra"""
    rh = rbf.Rhdodeca()
    # A: rh dodeca all alone
    rh.draw()
    if thing is not None:
        thing.delete()
    cb = rbf.Cube()
    cb.draw(trace=True)
    tet = rbf.Tetra()
    tet.draw(trace=True)
    rh.delete()
    cb.delete()
    # D: tetra all alone
    return tet

@transition('octa', 'cube')
def trans3(thing):
    """trans3: octa -> cube"""
    oc = rbf.Octa()
    # B: octahedron all alone
    oc.draw()
    if thing is not None:
        thing.delete()
    cb = rbf.Cube()
    cb.draw(trace=True)
    oc.delete()
    # C: cube all alone
    return cb

@transition('cube', 'tetra')
def trans4(thing):
    """trans4: cube -> tetra"""
    cb = rbf.Cube()
    # C: cube all alone
    cb.draw()
    if thing is not None:
        thing.delete()
    tet = rbf.Tetra()
    tet.draw(trace=True)
    cb.delete()
    # D: tetra all alone
    return tet

@transition('tetra', 'octa')
def trans5(thing):
    """trans5: tetra -> octa"""
    tet = rbf.Tetra()
    # D: tetra all alone
    tet.draw()
    if thing is not None:
        thing.delete()
    oc = rbf.Octa()
    oc.draw(trace=True)
    tet.delete()
    # B: octahedron all alone
    return oc

@transition('octa', 'rh')
def trans6(thing):
    """trans6: octa -> rh dodeca"""
    oc = rbf.Octa()
    # B: octahedron all alone
    oc.draw()
    if thing is not None:
        thing.delete()
    rh = rbf.Rhdodeca()
    rh.draw(trace=True)
    oc.delete()
    # A: rh dodeca all alone
    return rh

@transition('cube', 'rh')
def trans7(thing):
    """trans7: cube -> rh dodeca"""
    cb = rbf.Cube()
    # C: cube all alone
    cb.draw()
    if thing is not None:
        thing.delete()
    rh = rbf.Rhdodeca()
    rh.draw(trace=True)
    cb.delete()
    # A: rh dodeca all alone
    return rh

@transition('tetra', 'rh')
def trans8(thing):
    """trans8: tetra -> rh dodeca"""
    tet = rbf.Tetra()
    # D: tetra all alone
    tet.draw()
    if thing is not None:
        thing.delete()
    cb = rbf.Cube()
    cb.draw(trace=True)
    rh = rbf.Rhdodeca()
    rh.draw(trace=True)
    tet.delete()
    cb.delete()
    # A: rh dodeca all alone
    return rh

@transition('cube', 'octa')
def trans9(thing):
    """trans9: cube -> octa"""
    cb = rbf.Cube()
    # C: cube all alone
    cb.draw()
    if thing is not None:
        thing.delete()
    oc = rbf.Octa()
    oc.draw(trace=True)
    cb.delete()
    # B: octahedron all alone
    return oc

@transition('tetra', 'cube')
def trans10(thing):
    """trans10: tetra -> cube"""
    tet = rbf.Tetra()
    # D: tetra all alone
    tet.draw()
    if thing is not None:
        thing.delete()
    invtet = rbf.Invtetra()
    invtet.draw(trace=True)
    cb = rbf.Cube()
    cb.draw(trace=True)
    tet.delete()
    invtet.delete()
    # C: cube all alone
    return cb

@transition('octa', 'tetra')
def trans11(thing):
    """trans11: octa -> tetra"""
    oc = rbf.Octa()
    # B: octahedron all alone
    oc.draw()
    if thing is not None:
        thing.delete()
    cb = rbf.Cube()
    cb.draw(trace=True)
    tet = rbf.Tetra()
    tet.draw(trace=True)
    cb.delete()
    oc.delete()
    # D: tetra all alone
    return tet

@transition('rh', 'cubocta')
def trans12(thing):
    """trans12: rh -> cubocta"""
    rh = rbf.Rhdodeca()
    # A: rh dodeca all alone
    rh.draw()
    if thing is not None:
        thing.delete()
    sp = rbf.VEspokes()
    sp.draw(trace=True)
    cu = rbf.Cubocta()
    cu.draw(trace=True)
    sp.delete()
    rh.delete()
    # E: cubocta all alone
    return cu

@transition('cubocta', 'rh')
def trans13(thing):
    """trans13: cubocta -> rh"""
    cu = rbf.Cubocta()
    # E: Cubocta all alone
    cu.draw()
    if thing is not None:
        thing.delete()
    sp = rbf.VEspokes()
    sp.draw(trace=True)
    rh = rbf.Rhdodeca()
    rh.draw(trace=True)
    sp.delete()
    cu.delete()
    # A: rh dodeca all alone
    return rh

@transition('cubocta', 'icosa')
def trans14(thing):
    """trans14: cubocta -> icosa"""
    cu  = rbf.Cubocta()
    # E: Cubocta all alone
    cu.draw()
    if thing is not None:
        thing.delete()
    ic = rbf.Icosa()
    tracks = []
    for let in ['O','P','Q','R','S','T','U','V','W','X','Y','Z']:
        tracks.append([ cu.vertices[let], ic.vertices[let+'1'] - cu.vertices[let] ])
    for i in range(30):
        for v in tracks:            
            v[0] += 1/30.0 * v[1]
            rbf.rate(500)            
        cu.redraw()
        
    ic.draw()
    cu.delete()
    # F: Icosa all alone    
    return ic

@transition('icosa', 'cubocta')
def trans15(thing):
    """trans15: icosa -> cubocta"""
    ic  = rbf.Icosa()
    # F: Icosa all alone        
    ic.draw()
    if thing is not None:
        thing.delete()
    cu = rbf.Cubocta()
    tracks = []
    for let in ['O','P','Q','R','S','T','U','V','W','X','Y','Z']:
        tracks.append( [ ic.vertices[let+'1'], cu.vertices[let] - ic.vertices[let+'1'] ])
    for i in range(30):
        for v in tracks:            
            v[0] += 1/30.0 * v[1]
            rbf.rate(500)
        ic.redraw()
        
    cu.draw()
    ic.delete()
    return cu

@transition('cubocta', 'cubocta')
def trans16(thing):
    """trans16: cubocta -> cubocta"""
    cu = rbf.Cubocta()
    # E: Cubocta all alone
    cu.draw()
    if thing is not None:
        thing.delete()

    vs = cu.vertices.values()
    vs.append(rbf.vector(0,0,0))

    wow(vs, 500)
    return cu

@transition('icosa', 'icosa')
def trans17(thing):
    """trans17: icosa -> icosa"""
    ic = rbf.Icosa()
    # E: Icosa all alone
    ic.draw()
    if thing is not None:
        thing.delete()

    vs = ic.vertices.values()

    wow(vs, 500)
    return ic

@transition('rh', 'rh')
def trans18(thing):
    """trans18: rh -> rh"""
    rh = rbf.Rhdodeca()
    # A: rh dodeca all alone
    rh.draw()
    if thing is not None:
        thing.delete()

    vs = []
    vs.append(rbf.vector(0,0,0))

    wow(vs, 100)
    return rh

@transition('icosa', 'octa')
def trans19(thing):
    """trans19: icosa -> octa"""
    ic  = rbf.Jiticosa()
    # F: Icosa all alone        
    ic.draw()
    if thing is not None:
        thing.delete()
    oc = rbf.Octa()
    tracks = []
    for iclet, oclet in zip(['O','P','Q','R','S','T','U','V','W','X','Y','Z'],
                            ['I','K','L','I','N','K','L','N','J','M','M','J']):
        tracks.append( [ ic.vertices[iclet+'1'],
                         oc.vertices[oclet] - ic.vertices[iclet+'1'] ])
    for i in range(30):      
        for v in tracks:            
            v[0] += 1/30.0 * v[1]
            rbf.rate(500)            
        ic.redraw()
        
    oc.draw()
    ic.delete()    
    # E: Octa all alone    
    return oc

@transition('octa', 'icosa')
def trans20(thing):
    """trans20: octa -> icosa"""
    ic  = rbf.Jiticosa()
    oc  = rbf.Octa()
    for iclet, oclet in zip(['O1','P1','Q1','R1','S1','T1','U1','V1','W1','X1','Y1','Z1'],
                            ['I','K','L','I','N','K','L','N','J','M','M','J']):
        v = oc.vertices[oclet]
        ic.vertices[iclet] = rbf.vector(v.x, v.y, v.z)

    # B: Octa all alone        
    ic.draw()
    if thing is not None:
        thing.delete()

    ic3 = rbf.Icosa()
    tracks = []
    for iclet in ['O1','P1','Q1','R1','S1','T1','U1','V1','W1','X1','Y1','Z1']:
        tracks.append( [ ic.vertices[iclet],
                         ic3.vertices[iclet] - ic.vertices[iclet] ])

    for i in range(30):
        for v in tracks:            
            v[0] += 1/30.0 * v[1]
            rbf.rate(500)
        ic.redraw()

    ic3.draw()   
    ic.delete()
    # rbf.init()    
    # F: Icosa all alone    
    return ic3

@transition('icosa', 'pentdodeca')
def trans21(thing):
    """trans21: icosa -> pentdodeca"""
    ic = rbf.Icosa()
    # F: Icosa all alone
    ic.draw()
    if thing is not None:
        thing.delete()

    pd = rbf.Pentdodeca()
    pd.draw(trace=True)
    ic.delete()
    # G: Pdodeca all alone
    return pd

@transition('pentdodeca', 'icosa')
def trans22(thing):
    """trans22: pentdodeca -> icosa"""
    pd = rbf.Pentdodeca()
        # G: Pdodeca all alone
    pd.draw()
    if thing is not None:
        thing.delete()

    ic = rbf.Icosa()
    ic.draw(trace=True)
    pd.delete()
    # F: Icosa all alone
    return ic

@transition('pentdodeca', 'cube')
def trans23(thing):
    """trans23: pentdodeca -> cube"""
    pd = rbf.Pentdodeca()
    # G: Pdodeca
    pd.draw()
    if thing is not None:
        thing.delete()            

    cu1 = rbf.Pentcube()
    cu1.draw(trace=True)

    cu  = rbf.Cube()
    
    tracks = []
    
    for let1, let2 in zip(['B','G','D','E','C','A','F','H'],
                          ['j','b','p','q','f','l','s','m']):
        tracks.append( [ cu1.vertices[let2],
                         cu.vertices[let1] - cu1.vertices[let2] ])

    for i in range(30):
        for v in tracks:            
            v[0] += 1/30.0 * v[1]
            rbf.rate(500)
        cu1.redraw()
        
    cu.draw()
    cu1.delete(1000)
    pd.delete()
    # C: Cube all alone
    return cu

@transition('cube', 'pentdodeca')
def trans24(thing):
    """trans24: cube -> pentdodeca"""
    cu1 = rbf.Cube()
    cu1.draw()
    # C: cube all alone
    if thing is not None:
        thing.delete()            

    cu  = rbf.Pentcube()
    
    tracks = []
    
    for let1, let2 in zip(['B','G','D','E','C','A','F','H'],
                          ['j','b','p','q','f','l','s','m']):
        tracks.append( [ cu1.vertices[let1],
                         cu.vertices[let2] - cu1.vertices[let1] ])

    for i in range(30):
        for v in tracks:            
            v[0] += 1/30.0 * v[1]
            rbf.rate(500)
        cu1.redraw()

    cu.draw()
    cu1.delete(1000)
    
    pd = rbf.Pentdodeca()
    pd.draw(trace=True)
    cu.delete()
    # G: Pdodeca all alone
    return pd

@transition('pentdodeca', 'rh_triaconta')
def trans25(thing):
    """trans25: pentdodeca -> rh triaconta"""
    pd = rbf.Pentdodeca()
    # G: Pdodeca all alone
    pd.draw()
    if thing is not None:
        thing.delete()
    ic = rbf.Icosa()
    ic.draw(trace=True)
    rt = rbf.Rhtriaconta()
    rt.draw(trace=True)
    pd.delete()
    ic.delete()
    # H: Rhtriac all alone
    return rt

@transition('rh_triaconta', 'pentdodeca')
def trans26(thing):
    """trans26: rh triaconta -> Pentdodeca"""
    rt = rbf.Rhtriaconta()
    # H: Rtriac all alone
    rt.draw()
    if thing is not None:
        thing.delete()
    pd = rbf.Pentdodeca()
    pd.draw(trace=True)
    rt.delete()
    # G: Pentdodeca all alone
    return pd

@transition('rh_triaconta', 'icosa')
def trans27(thing):
    """trans27: rh triaconta -> Icosa"""
    rt = rbf.Rhtriaconta()
    # H: Rtriac all alone
    rt.draw()
    if thing is not None:
        thing.delete()
    ic = rbf.Icosa()
    ic.draw(trace=True)
    rt.delete()
    # F: Icosa all alone
    return ic

@transition('icosa', 'rh_triaconta')
def trans28(thing):
    """trans28: Icosa -> rh triaconta"""
    ic = rbf.Icosa()
    # F: Icosa all alone
    ic.draw()
    if thing is not None:
        thing.delete()
    rt = rbf.Rhtriaconta()
    rt.draw(trace=True)
    ic.delete()
    # H: Rhtriac all alone
    return rt

@transition('cube', 'cubocta')
def trans29(thing):
    """trans29: Cube -> Cubocta"""
    cu = rbf.Cube()
    cu.draw()
    if thing is not None:
        thing.delete()
    ve = rbf.Cubocta()
    
    smve = rbf.Cubocta() * 0.5
    smve.draw(True)
    
    tracks = []
    
    for let1, let2 in zip(smve.vertices.keys(), ve.vertices.keys()):
        tracks.append( [ smve.vertices[let1],
                         ve.vertices[let2] - smve.vertices[let1] ])

    for i in range(30):
        for v in tracks:            
            v[0] += 1/30.0 * v[1]
            rbf.rate(500)
        smve.redraw()

    ve.draw()
    smve.delete(1000)
    cu.delete(1000)
    # E: Cubocta all alone
    return ve

@transition('rh_triaconta', 'rh')
def trans30(thing):
    """trans30: rh triaconta -> rh"""
    rt = rbf.Rhtriaconta()
    # H: Rtriac all alone
    rt.draw()
    if thing is not None:
        thing.delete()
    
    tracks = []
    smrt = rbf.Rhtriaconta() * rbf.Vset['ef']
    
    for let1, let2 in zip(rt.vertices.keys(), smrt.vertices.keys()):
        tracks.append( [ rt.vertices[let1],
                         smrt.vertices[let2] - rt.vertices[let1] ])

    for i in range(30):
        for v in tracks:            
            v[0] += 1/30.0 * v[1]
            rbf.rate(500)
        rt.redraw()

    smrt.draw()
    rt.delete(1000)

    rh = rbf.Rhdodeca()
    
    vs = []
    vs.append(rbf.vector(0,0,0))
    # wow(vs, 100)
    
    rh.draw(trace=True)
    smrt.delete(1000)
    # wow(vs, 100)
    return rh

@transition('rh', 'rh_triaconta')
def trans31(thing):
    "trans31: rh -> rh triaconta (7.5) -> rh triaconta (dual)"
    rh = rbf.Rhdodeca()
    # A: rh dodeca all alone
    rh.draw()
    if thing is not None:
        thing.delete()

    rt = rbf.Rhtriaconta()
    bgrt = rbf.Rhtriaconta() * rbf.Vset['ef'] * rbf.Vset['tf']
    bgrt = bgrt * pow(1.5, 1./3)

    bgrt.draw(trace=True)
    
    tracks = []
    
    for let1, let2 in zip(bgrt.vertices.keys(), rt.vertices.keys()):
        tracks.append( [ bgrt.vertices[let1],
                         rt.vertices[let2] - bgrt.vertices[let1] ])

    for i in range(30):
        for v in tracks:            
            v[0] += 1/30.0 * v[1]
            rbf.rate(500)
        bgrt.redraw()

    rt.draw(trace=True)
    bgrt.delete(1000)
    rh.delete(1000)
    return rt

def wow(vs, r=60):
    sphs = []
    maxlen = rbf.mag(rbf.Vset['A'] - rbf.Vset['B'])/2.0

    for v in vs:
        sphs.append(rbf.sphere(pos=v, radius=0, color = (1,0.7,0.2)))

    for i in range(1,101):
        for s in sphs:
            rbf.rate(r)
            s.radius =  maxlen * i/100

    for i in range(100,-1,-1):
        for s in sphs:
            rbf.rate(r)
            s.radius =  maxlen * i/100

def hypertoon(repetitions, node=None):
    """Start an N-transition hypertoon.  node may be a name like 'cube'"""
    liveobject = None
    if node is None:
        node = choice(transitions.keys())
    for x in range(repetitions):
        possibilities = transitions[node]
        coming, function = choice(possibilities.items())
        if verbose:
            print node, coming, function.__name__, function.__doc__
        liveobject = function(liveobject)
        node = coming

def setup(stereo=False, fullscreen=True):
    scene2 = rbf.display(title='Hypertoon!', center=(0,0,0),
                         background=(.5, .5, .5))
    scene2.ambient = .5
    scene2.fullscreen = fullscreen

    if stereo:
        if stereo is True:
            stereo = 'redblue'
        scene2.stereo = stereo
        scene2.stereodepth = 1.5

    xyz = rbf.XYZaxes()
    xyz = 1.5 * xyz
    xyz.draw()
    scene2.autoscale = 0
    xyz.delete()
    return scene2

class Mythread(threading.Thread):

    def __init__(self, hypertoon, number, shape=None):
        super(Mythread, self).__init__(None)
        self.hypertoon = hypertoon
        self.n = number
        self.shape = shape

    def run(self):
        self.hypertoon(self.n, self.shape)

def main(number, stereo=False, threads=True, fullscreen=True, shape=None):
    if verbose:
        for base, choices in sorted(transitions.iteritems()):
            print '%s: %s' % (base, ', '.join(sorted(choices)))
    if shape is None:
        shape = choice(transitions.keys())            
    scene = setup(stereo, fullscreen)
    if threads:
        t1 = Mythread(hypertoon, number, shape)
        t2 = Mythread(hypertoon, number, shape)
        t1.start()
        t2.start()
        t1.join()
        t2.join()
        print "Threads complete"
    else:
        hypertoon(number, shape)

if __name__ == '__main__':
    _stereo = _threads = verbose = _windowed = False
    _shape = None
    _repetitions = 20
    for name in sys.argv[1:]:
        if '-H' == name:
            print '-R# -S: stereo, -T: threads -W: windowed, -V: verbose',
            print '-Spassive or -Sredblue'
            print 'or a starting shape:', ' '.join(sorted(transitions))
            raise SystemExit
        if '-S' == name:
            _stereo = True
        elif '-T' == name:
            _threads = True
        elif '-V' == name:
            verbose = True    # A global
        elif '-W' == name:
            _windowed = True
        elif name.startswith('-R'):
            _repetitions = int(name[2:])
        elif name.startswith('-S'):
            _stereo = name[2:]
        elif name in transitions:
            _shape = name
        else:
            print 'Ignoring parameter %r' % name
    main(_repetitions, _stereo, _threads, not _windowed, _shape)

# code highlighted using py2html.py version 0.8