#Programmer: Chris Tralie
#Purpose: To provide a simple polygon mesh class on top of numpy and PyOpenGL
#capable of reading and writing off files, as well as rendering meshes and
#performing some simple geometric and topological manipulations
from Primitives3D import *
from OpenGL.GL import *
from OpenGL.arrays import vbo
import OpenGL.GL.shaders
import sys
import re
import numpy as np
import numpy.linalg as linalg
import struct

POINT_SIZE = 7

def loadTexture(filename):
    try:
        from PIL.Image import open as imgopen
    except ImportError, err:
        from Image import open as imgopen
    im = imgopen(filename)
    try:
        im = im.convert('RGB')
        ix, iy, image = im.size[0], im.size[1], im.tobytes("raw", "RGBA", 0, -1)
    except SystemError:
        ix, iy, image = im.size[0], im.size[1], im.tobytes("raw", "RGBX", 0, -1)
    assert ix*iy*4 == len(image), """Unpacked image size for texture is incorrect"""
    
    texID = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, texID)
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexImage2D(GL_TEXTURE_2D, 0, 3, ix, iy, 0, GL_RGBA, GL_UNSIGNED_BYTE, image)
    return texID

#Used to help sort edges in CCW order
class EdgesCCWComparator(object):
    def __init__(self, VCenter, N):
        self.VCenter = VCenter #Common point of all edges
        self.N = N
        self.mesh = VCenter.mesh
    
    def compare(self, e1, e2):
        V1 = e1.vertexAcross(self.VCenter)
        V2 = e2.vertexAcross(self.VCenter)
        a = V1.getPos() - VCenter.getPos()
        b = V2.getPos() - VCenter.getPos()
        triNormal = np.cross(a, b)
        dot = triNormal.dot(self.N)
        if dot > 0:
            return 1
        elif dot == 0:
            return 0
        return -1

class MeshVertex(object):
    def __init__(self, mesh, pos, color, texCoords, ID):
        self.mesh = mesh
        self.ID = ID
        #Set color and position in buffers
        if self.mesh.VPos.shape[0] <= ID:
            #If the client side vertex array needs to be expanded
            NPad = ID - self.mesh.VPos.shape[0] + 1
            self.mesh.VPos = np.concatenate((self.mesh.VPos, np.zeros((NPad, 3))), 0)
            self.mesh.VColors = np.concatenate((self.mesh.VColors, np.zeros((NPad, 3))), 0)
            self.mesh.VTexCoords = np.concatenate((self.mesh.VTexCoords, np.zeros((NPad, 2))), 0)
        self.mesh.VPos[ID, :] = pos
        self.mesh.VColors[ID, :] = color
        self.mesh.VTexCoords[ID, :] = texCoords
        self.edges = set() #Store reference to all emanating edges
        #NOTE: Edges are not guaranteed to be in any particular order
        self.component = -1 #Which connected component it's in
    
    def getPos(self):
        return self.mesh.VPos[self.ID, :]
    
    def getVertexNeighbors(self):
        ret = [0]*len(self.edges)
        i = 0
        for edge in self.edges:
            ret[i] = edge.vertexAcross(self)
            i = i+1
        return ret
    
    #Return a set of all faces attached to this vertex
    def getAttachedFaces(self):
        ret = set()
        i = 0
        for edge in self.edges:
            if (edge.f1 != None):
                ret.add(edge.f1)
            if (edge.f2 != None):
                ret.add(edge.f2)
        return ret
    
    #Return the area of the one-ring faces attached to this vertex
    def getOneRingArea(self):
        faces = self.getAttachedFaces()
        ret = 0.0
        for f in faces:
            ret = ret + f.getArea()
        return ret
    
    #Get an estimate of the vertex normal by taking a weighted
    #average of normals of attached faces
    def getNormal(self):
        faces = self.getAttachedFaces()
        totalArea = 0.0;
        normal = np.array([0, 0, 0])
        for f in faces:
            w = f.getArea()
            totalArea = totalArea + w
            normal = normal + w*f.getNormal()
        if totalArea == 0:
            return normal
        return (1.0/totalArea)*normal
    
    #Sort the edges so that they are in CCW order when projected onto
    #the plane defined by the vertex's normal
    def getCCWSortedEdges(self):
        comparator = EdgesCCWComparator(self, self.getNormal())
        return sorted(self.edges, cmp = comparator.compare)

class MeshFace(object):
    def __init__(self, mesh, ID):
        self.mesh = mesh
        self.ID = ID
        self.edges = [] #Store edges in CCW order
        self.startV = 0 #Vertex that starts it off

    def flipNormal(self):
        #Reverse the specification of the edges to make the normal
        #point in the opposite direction
        self.edges.reverse()
        self.normal = np.zeros((0, 0)) #Invalidate current cached normal

    #Return a list of vertices on the face in CCW order
    def getVertices(self):
        ret = [0]*len(self.edges)
        v = self.startV
        for i in range(0, len(self.edges)):
            ret[i] = v
            v = self.edges[i].vertexAcross(v)
        return ret
    
    #Return the vertices' positions in an N x 3 matrix
    def getVerticesPos(self):
        return self.mesh.VPos[[v.ID for v in self.getVertices()], :]
    
    def getNormal(self):
        return getFaceNormal(self.getVerticesPos())
    
    def getArea(self):
        return getPolygonArea(self.getVerticesPos())
    
    def getCentroid(self):
        V = self.getVerticesPos()
        if V.size == 0:
            return np.array([[0, 0, 0]])
        return np.mean(V, 0)
    
    def getPlane(self):
        return Plane3D(self.mesh.VPos[self.startV.ID, :], self.getNormal())

    #Return the closest point inside this face to P
    def getClosestPoint(self, P):
        #TODO: Finish translating to numpy
        print "TODO: getClosestPoint()"
        #return getClosestPoint([v.pos for v in self.getVertices()], P)            

class MeshEdge(object):
    def __init__(self, mesh, v1, v2, ID):
        self.mesh = mesh
        self.ID = ID
        [self.v1, self.v2] = [v1, v2]
        [self.f1, self.f2] = [None, None]
    
    def vertexAcross(self, startV):
        if startV == self.v1:
            return self.v2
        if startV == self.v2:
            return self.v1
        sys.stderr.write("Warning (vertexAcross): Vertex not member of edge\n")
        return None
    
    def addFace(self, face, v1):
        if self.f1 == None:
            self.f1 = face
        elif self.f2 == None:
            self.f2 = face
        else:
            sys.stderr.write("Cannot add face to edge; already 2 there\n")
    
    #Remove pointer to face
    def removeFace(self, face):
        if self.f1 == face:
            self.f1 = None
        elif self.f2 == face:
            self.f2 = None
        else:
            sys.stderr.write("Cannot remove edge pointer to face that was never part of edge\n")
    
    def faceAcross(self, startF):
        if startF == self.f1:
            return self.f2
        if startF == self.f2:
            return self.f1
        sys.stderr.write("Warning (faceAcross): Face not member of edge\n")
        return None
    
    def getCenter(self):
        V = np.zeros((2, 3))
        V[0, :] = self.mesh.VPos[self.v1.ID, :]
        V[1, :] = self.mesh.VPos[self.v2.ID, :]
        return np.mean(V, 0)
    
    def numAttachedFaces(self):
        ret = 0
        if self.f1:
            ret = ret + 1
        if self.f2:
            ret = ret + 1
        return ret

def getFaceInCommon(e1, e2):
    e2faces = []
    if e2.f1 != None:
        e2faces.append(e2.f1)
    if e2.f2 != None:
        e2faces.append(e2.f2)
    if e1.f1 in e2faces:
        return e1.f1
    if e1.f2 in e2faces:
        return e1.f2
    return None

def getEdgeInCommon(v1, v2):
    for e in v1.edges:
        if e.vertexAcross(v1) is v2:
            return e
    return None

def getVertexInCommon(e1, e2):
    v = [e1.v1, e1.v2, e2.v1, e2.v2]
    for i in range(4):
        for j in range(i+1, 4):
            if v[i] == v[j]:
                return v[i]
    return None

#############################################################
####                    POLYGON MESH                    #####
#############################################################

class PolyMesh(object):
    def __init__(self):
        self.idxpointSize = 20
        self.needsDisplayUpdate = True #Vertex, color, texture, and index buffers need updating
        self.vertices = []
        self.edges = []
        self.faces = []
        #Client side numpy arrays for geometric information
        self.VPos = np.zeros((0, 3)) #Client side vertex buffer
        self.VNormals = np.zeros((0, 3)) #Client side vertex normals
        self.VColors = np.zeros((0, 3)) #Client side color buffer
        self.VTexCoords = np.zeros((0, 2)) #Client side texture coordinate buffer
        self.ITris = np.zeros((0, 3)) #Client side triangle index buffer
        self.EdgeLines = np.zeros((0, 3)) #Client side edge lines
        #Buffer pointers
        self.texID = -1
        self.VPosVBO = None
        self.VNormalsVBO = None
        self.VColorsVBO = None
        self.VTexCoordsVBO = None
        self.IndexVBO = None
        self.EdgeLinesVBO = None
        self.VNormalLinesVBO = None
        self.FNormalLinesVBO = None
        #Display lists
        self.IndexDisplayList = -1
        #Pointers to a representative vertex in different 
        #connected components
        self.components = []
    
    def Clone(self):
        print "TODO"
    
    #Return the edge between v1 and v2 if it exists, or
    #return None if an edge has not yet been created 
    #between them
    def getEdge(self, v1, v2):
        edge = v1.edges & v2.edges
        if len(edge) > 1:
            sys.stderr.write("Warning: More than one edge found on vertex list intersection\n")
        for e in edge:
            return e
        return None
    
    def getVerticesCols(self):
        return self.VPos.T
    
    #############################################################
    ####                ADD/REMOVE METHODS                  #####
    #############################################################

    def addVertex(self, pos, color = np.array([0.5, 0.5, 0.5]), texCoords = np.array([0, 0])):
        vertex = MeshVertex(self, pos, color, texCoords, len(self.vertices))
        self.vertices.append(vertex)
        return vertex
    
    #Create an edge between v1 and v2 and return it
    #This function assumes v1 and v2 are valid vertices in the mesh
    def addEdge(self, v1, v2):
        edge = MeshEdge(self, v1, v2, len(self.edges))
        self.edges.append(edge)
        v1.edges.add(edge)
        v2.edges.add(edge)
        return edge
    
    #Given a list of pointers to mesh vertices in CCW order
    #create a face object from them
    def addFace(self, meshVerts):
        verts = np.array([self.VPos[v.ID, :] for v in meshVerts])
        if not arePlanar(verts):
            sys.stderr.write("Error: Trying to add mesh face that is not planar\n")
            for v in verts:
                print v
            return None
        if not are2DConvex(verts):
            sys.stderr.write("Error: Trying to add mesh face that is not convex\n")
            return None
        face = MeshFace(self, len(self.faces))
        face.startV = meshVerts[0]
        for i in range(0, len(meshVerts)):
            v1 = meshVerts[i]
            v2 = meshVerts[(i+1)%len(meshVerts)]
            edge = self.getEdge(v1, v2)
            if edge == None:
                edge = self.addEdge(v1, v2)
            face.edges.append(edge)
            edge.addFace(face, v1) #Add pointer to face from edge
        self.faces.append(face)
        return face
    
    #Remove the face from the list of faces and remove the pointers
    #from all edges to this face
    def removeFace(self, face):
        #Swap the face to remove with the last face (O(1) removal)
        self.faces[face.ID] = self.faces[-1]
        self.faces[face.ID].ID = face.ID #Update ID of swapped face
        face.ID = -1
        self.faces.pop()
        #Remove pointers from all of the face's edges
        for edge in face.edges:
            edge.removeFace(face)
    
    #Remove this edge from the list of edges and remove 
    #references to the edge from both of its vertices
    #(NOTE: This function is not responsible for cleaning up
    #faces that may have used this edge; that is up to the client)
    def removeEdge(self, edge):
        #Swap the edge to remove with the last edge
        self.edges[edge.ID] = self.edges[-1]
        self.edges[edge.ID].ID = edge.ID #Update ID of swapped face
        edge.ID = -1
        self.edges.pop()
        #Remove pointers from the two vertices that make up this edge
        edge.v1.edges.remove(edge)
        edge.v2.edges.remove(edge)
    
    #Remove this vertex from the list of vertices
    #NOTE: This function is not responsible for cleaning up any of
    #the edges or faces that may have used this vertex
    def removeVertex(self, vertex):
        #Also swap color, vertex, and texture information in buffers
        self.vertices[vertex.ID] = self.vertices[-1]
        self.VPos[vertex.ID, :] = self.VPos[-1, :]
        self.VColors[vertex.ID, :] = self.VColors[-1, :]
        self.VTexCoords[vertex.ID, :] = self.VTexCoords[-1, :]
        self.vertices[vertex.ID].ID = vertex.ID
        vertex.ID = -1
        self.vertices.pop()
    
    #############################################################
    ####    TOPOLOGY SUBDIVISION AND REMESHING METHODS      #####
    #############################################################
    
    #Split every face into K+1 faces by creating a new vertex
    #at the midpoint of every edge, removing the original face, 
    #creating a new face connecting all the new vertices, and
    #creating triangular faces to fill in the gaps
    def splitFaces(self):
        #First subdivide each edge
        for e in self.edges:
            P = e.getCenter()
            e.centerVertex = self.addVertex(P)
        faces = list(self.faces)
        #Now create the new faces
        for f in faces:
            self.removeFace(f)
            #Add the inner face
            fInnerVerts = [e.centerVertex for e in f.edges]
            self.addFace(fInnerVerts)
            #Add the triangles around the border
            innerVerts = [0]*len(f.edges)
            outerVerts = [0]*len(f.edges)
            outerV = f.startV
            for i in range(0, len(f.edges)):
                outerV = f.edges[i].vertexAcross(outerV)
                outerVerts[i] = outerV
                innerVerts[i] = f.edges[i].centerVertex
            for i in range(0, len(f.edges)):
                triVerts = [innerVerts[i], outerVerts[i], innerVerts[(i+1)%len(innerVerts)]]
                self.addFace(triVerts)
        #Remove all edges that were on the original faces
        for f in faces:
            for e in f.edges:
                if e.ID != -1: #If the edge has not already been removed
                    self.removeEdge(e)
        self.needsDisplayUpdate = True
    
    #Split every face into N triangles by creating a vertex at
    #the centroid of each face
    def starRemeshFaces(self, faces):
        for f in faces:
            #TODO: Implement normal meshes this way (move centroid along normal)??
            centroidP = f.getCentroid()
            centroid = self.addVertex(centroidP)
            #TODO: Update texture coordinates properly
            verts = f.getVertices()
            #Remove face and replace with N triangles
            self.removeFace(f)
            for i in range(0, len(verts)):
                v1 = verts[i]
                v2 = verts[(i+1)%len(verts)]
                newVerts = [v1, v2, centroid]
                newFace = self.addFace(newVerts)
        self.needsDisplayUpdate = True
        self.needsIndexDisplayUpdate = True
    
    def starRemesh(self):
        #Copy over the face list since it's about to be modified
        faces = list(self.faces)
        self.starRemeshFaces(faces)
    
    #Triangulate all faces that are not triangular by using
    #the star scheme
    def starTriangulate(self):
        faces = []
        for f in self.faces:
            if len(f.edges) > 3:
                faces.append(f)
        self.starRemeshFaces(faces)
    
    #This function works best starting with triangular meshes
    def evenTriangleRemesh(self):
        for e in self.edges:
            pos = e.getCenter()
            color = 0.5*self.VColors[e.v1.ID, :] + 0.5*self.VColors[e.v2.ID, :]
            texCoords = 0.5*self.VTexCoords[e.v1.ID, :] + 0.5*self.VTexCoords[e.v2.ID, :]
            e.centerVertex = self.addVertex(pos, color, texCoords)
        facesToAdd = []
        for f in self.faces:
            #Add the faces along the outside
            for i in range(0, len(f.edges)):
                e1 = f.edges[i]
                e2 = f.edges[(i+1)%len(f.edges)]
                v = getVertexInCommon(e1, e2)
                facesToAdd.append([e1.centerVertex, v, e2.centerVertex])
            #Add the face in the center
            facesToAdd.append([e.centerVertex for e in f.edges])
        
        #Remove every face and every original edge
        #and add the new faces (which will implicitly 
        #add the new split edges)
        self.faces = []
        self.edges = []
        for v in self.vertices:
            v.edges.clear()
        for f in facesToAdd:
            self.addFace(f)

    #Divide every polygonal face with N sides into (N-2) triangles
    #This assumes the polygonal faces are convex
    def minTrianglesRemesh(self):
        faces = list(self.faces)
        for f in faces:
            verts = f.getVertices()
            if len(verts) <= 3:
                continue
            #Remove face and replace with (N-2) triangles
            self.removeFace(f)
            v0 = verts[0]
            for i in range(1, len(verts)-1):
                v1 = verts[i]
                v2 = verts[i+1]
                newFace = self.addFace([v0, v1, v2])
        self.needsDisplayUpdate = True
    
    def flipNormals(self):
        for f in self.faces:
            f.flipNormal()
    
    def getConnectedComponents(self):
        self.components = []
        for v in self.vertices:
            if v.component == -1:
                self.components.append(v)
                stack = [v]
                while len(stack) > 0:
                    vcurr = stack[-1]
                    if vcurr.component == -1:
                        vcurr.component = len(self.components)-1
                        stack.pop()
                        for vNeighbor in vcurr.getVertexNeighbors():
                            stack.append(vNeighbor)
                    else:
                        stack.pop()
    
    def getConnectedComponentCounts(self):
        counts = [0]*len(self.components)
        for v in self.vertices:
            if v.component > -1:
                counts[v.component] = counts[v.component] + 1
        return counts
    
    def deleteAllButLargestConnectedComponent(self):
        if len(self.components) == 0:
            self.getConnectedComponents()
        counts = self.getConnectedComponentCounts()
        largestComponent = 0
        largestCount = 0
        for i in range(0, len(counts)):
            if counts[i] > largestCount:
                largestCount = counts[i]
                largestComponent = i
        facesToDel = []
        edgesToDel = []
        #Delete faces first, then edges, then vertices
        for f in self.faces:
            if f.startV.component != largestComponent:
                edgesToDel = edgesToDel + f.edges
                facesToDel.append(f)
        for f in facesToDel:
            self.removeFace(f)
        for e in edgesToDel:
            if e.ID != -1:
                self.removeEdge(e)
        verticesToDel = []
        for v in self.vertices:
            if v.component != largestComponent:
                verticesToDel.append(v)
        for v in verticesToDel:
            self.removeVertex(v)
        #Now update the connected components list
        if len(self.vertices) > 0:
            self.components = [self.vertices[0]]
            for v in self.vertices:
                v.component = 0
        self.needsDisplayUpdate = True
    
    #Fill hole with the "advancing front" method
    #but keep it simple for now; not tests for self intersections
    def fillHole(self, hole):
        #TODO: Maintain a min priority queue that indexes based on angle
        #for i in range(0, len(hole)):
        #    vb = hole[(i+len(hole)-1)%len(hole)]
        #    v = hole[i]
        #    va = hole[(i+1)%len(hole)]
        #    v.before = vb
        #    v.after = va
        c = [0, 0, 0]
        for mV in hole:
            c = c + self.VPos[mV.ID, :]
        c = (1.0/len(hole))*c
        vCenter = self.addVertex(c)
        for i in range(0, len(hole)):
            v1 = hole[i]
            v2 = hole[(i+1)%len(hole)]
            self.addFace([vCenter, v1, v2])
        
    
    def fillHoles(self, slicedHolesOnly = False):
        holes = []
        origEdges = self.edges[:]
        for e in origEdges:
            if e.numAttachedFaces() == 1 and ((not slicedHolesOnly) or e.v1.borderVertex):
                loop = [e.v1, e.v2]
                finished = False
                while not finished:
                    foundNext = False
                    for v in loop[-1].getVertexNeighbors():
                        if v is loop[-2]:
                            #Make sure it doesn't accidentally back up
                            continue
                        elif v is loop[0]:
                            #It's looped back on itself so we're done
                            finished = True
                        else:
                            e = getEdgeInCommon(loop[-1], v)
                            if not e:
                                print "Warning: Edge not found in common while trying to trace hole boundary"
                                finished = True
                                break
                            elif e.numAttachedFaces() == 1:
                                foundNext = True
                                loop.append(v)
                                break
                    if not foundNext and not finished:
                        print "Warning: Unable to close hole"
                        break
                print "Found hole of size %i"%len(loop)
                self.fillHole(loop)
        self.needsDisplayUpdate = True
        self.needsIndexDisplayUpdate = True

    #############################################################
    ####                 GEOMETRY METHODS                   #####
    #############################################################

    #Transformations are simple because geometry information is only
    #stored in the vertices
    def Transform(self, matrix):
        self.VPos = matrix.dot(self.VPos.T).T
    
    def Translate(self, dV):
        self.VPos = self.VPos + np.reshape(dV, [1, 3])
    
    def Scale(self, dx, dy, dz):
        S = np.zeros((1, 3))
        S[0, :] = [dx, dy, dz]
        self.VPos = S*self.VPos

    def getCentroid(self):
        return np.mean(self.VPos, 0)
    
    def getBBox(self):
        if self.VPos.shape[0] == 0:
            print "Warning: PolyMesh.getBBox(): Adding bbox but no vertices"
            return BBox3D()
        bbox = BBox3D()
        bbox.fromPoints(self.VPos)
        return bbox
    
    #Use PCA to find the principal axes of the vertices
    def getPrincipalAxes(self):
        X = self.VPos - self.getCentroid()
        XTX = (X.T).dot(X)
        (lambdas, axes) = linalg.eig(XTX)
        #Put the eigenvalues in decreasing order
        idx = lambdas.argsort()[::-1]
        lambdas = lambdas[idx]
        axes = axes[:, idx]
        T = X.dot(axes)
        maxProj = T.max(0)
        minProj = T.min(0)
        axes = axes.T #Put each axis on each row to be consistent with everything else
        return (axes, maxProj, minProj)        
    
    #Delete the parts of the mesh below "plane".  If fillHoles
    #is true, plug up the holes that result from the cut
    def sliceBelowPlane(self, plane, fillHoles = True):
        facesToDel = []
        edgesToDel = []
        verticesToDel = []
        facesToAdd = []
        newVertices = []
        borderVertex = None
        for e in self.edges:
            #Keep track of edges which intersect the plane
            #and the vertex that represents that intersection
            e.centerVertex = None
        for v in self.vertices:
            #Keep track of which vertices are introduced at the plane slice
            v.borderVertex = False
        for f in self.faces:
            v1 = f.startV
            deleteFace = False
            splitFaceStartE = None
            splitFaceStartEIndex = -1
            splitFaceStartV = None
            splitFaceEndE = None
            for i in range(0, len(f.edges)):
                e = f.edges[i]
                v2 = e.vertexAcross(v1)
                distV1 = plane.distFromPlane(self.VPos[v1.ID, :])
                distV2 = plane.distFromPlane(self.VPos[v2.ID, :])
                #Mark all vertices below the plane for deletion
                #(this will count vertices more than once but 
                #because mesh is manifold the amortized size will
                #still be linear in the number of vertices)
                if distV1 < 0:
                    verticesToDel.append(v1)
                #Mark all edges that are fully or partially below
                #the plane for deletion.  This list will hold
                #every such edge at most twice since 2 faces meet
                #at an edge in a manifold mesh
                if distV1 < 0 or distV2 < 0:
                    edgesToDel.append(e)
                    deleteFace = True
                #Edge goes from negative side to plus side of plane
                if distV1 < 0 and distV2 >= 0:
                    if not e.centerVertex:
                        line = Line3D(self.VPos[v1.ID, :], self.VPos[v2.ID, :] - self.VPos[v1.ID, :])
                        [t, P] = line.intersectPlane(plane)
                        if P:
                            newColor = (1-t)*self.VColors[v1.ID, :] + t*self.VColors[v2.ID, :]
                            newTexCoords = (1-t)*self.VTexCoords[v1.ID, :] + t*self.VTexCoords[v2.ID, :]
                            e.centerVertex = self.addVertex(P, newColor, newTexCoord)
                            e.centerVertex.borderVertex = True
                            borderVertex = e.centerVertex
                    if e.centerVertex:
                        splitFaceStartEIndex = i
                        splitFaceStartV = v1
                        splitFaceStartE = e
                #Edge goes from plus side to negative side of plane
                if distV1 >= 0 and distV2 <= 0:
                    if not e.centerVertex:
                        line = Line3D(self.VPos[v1.ID, :], self.VPos[v2.ID, :] - self.VPos[v1.ID, :])
                        [t, P] = line.intersectPlane(plane)
                        if P:
                            newColor = (1-t)*self.VColors[v1.ID, :] + t*self.VColors[v2.ID, :]
                            newTexCoords = (1-t)*self.VTexCoords[v1.ID, :] + t*self.VTexCoords[v2.ID, :]
                            e.centerVertex = self.addVertex(P, newColor, newTexCoords)
                            e.centerVertex.texCoords = newTexCoords
                            e.centerVertex.borderVertex = True
                            borderVertex = e.centerVertex
                    if e.centerVertex:
                        splitFaceEndE = e                    
                v1 = v2
            if deleteFace:
                facesToDel.append(f)
            #Walk along the split part of the face on the positive
            #side of the plane
            if splitFaceStartE and splitFaceEndE:
                newFace = [splitFaceStartE.centerVertex]
                i = splitFaceStartEIndex
                e = splitFaceStartE
                v1 = splitFaceStartV
                while e != splitFaceEndE:
                    v1 = e.vertexAcross(v1)
                    newFace.append(v1)
                    i = (i+1)%len(f.edges)
                    e = f.edges[i]
                newFace.append(splitFaceEndE.centerVertex)
                facesToAdd.append(newFace)
        #First remove all faces that are no longer relevant
        for f in facesToDel:
            self.removeFace(f)    
        #Now remove edges that are no longer relevant
        for e in edgesToDel:
            if e.ID != -1:
                self.removeEdge(e)
        #Now remove vertices that are no longer relevant
        for v in verticesToDel:
            if v.ID != -1:
                self.removeVertex(v)
        #Add new faces
        for f in facesToAdd:
            self.addFace(f)
        if fillHoles:
            self.fillHoles(slicedHolesOnly = True)
        self.needsDisplayUpdate = True
        self.needsIndexDisplayUpdate = True
    
    def sliceAbovePlane(self, plane, fillHoles = True):
        planeNeg = Plane3D(plane.P0, plane.N)
        planeNeg.initFromEquation(-plane.A, -plane.B, -plane.C, -plane.D)
        self.sliceBelowPlane(planeNeg, fillHoles)
    
    def flipAcrossPlane(self, plane):
        P0 = plane.P0
        N = plane.N
        for V in self.vertices:
            P = self.VPos[V.ID, :]
            dP = P - P0
            dPPar = projVec(dP, N)
            dPPerp = dP - dPPar
            self.VPos[V.ID, :] = P0 - dPPar + dPPerp
        self.needsDisplayUpdate = True
    
    #Uniformly sample points on this mesh at random, taking into consideration
    #the area of triangle faces
    #Return the points and normal estimates
    #colPoints: Whether to return the points/normals in columns or rows
    def randomlySamplePoints(self, NPoints, colPoints = True):
        if self.needsDisplayUpdate:
            #Make sure the triangle buffer is in place even if the rest of
            #the buffers haven't been setup yet
            self.updateTris()
        ###Step 1: Compute cross product of all face triangles and use to compute
        #areas and normals (very similar to code used to compute vertex normals)
        
        #Vectors spanning two triangle edges
        P0 = self.VPos[self.ITris[:, 0], :]
        P1 = self.VPos[self.ITris[:, 1], :]
        P2 = self.VPos[self.ITris[:, 2], :]
        V1 = P1 - P0
        V2 = P2 - P0
        FNormals = np.cross(V1, V2)
        FAreas = np.sqrt(np.sum(FNormals**2, 1)).flatten()
        
        #Get rid of zero area faces and update points
        self.ITris = self.ITris[FAreas > 0, :]
        FNormals = FNormals[FAreas > 0, :]
        FAreas = FAreas[FAreas > 0]
        P0 = self.VPos[self.ITris[:, 0], :]
        P1 = self.VPos[self.ITris[:, 1], :]
        P2 = self.VPos[self.ITris[:, 2], :]
        
        #Compute normals
        NTris = self.ITris.shape[0]
        FNormals = FNormals/FAreas[:, None]
        FAreas = 0.5*FAreas
        self.FNormals = FNormals
        self.FCentroid = 0*FNormals
        self.VNormals = 0*self.VPos
        VAreas = np.zeros(self.VPos.shape[0])
        for k in range(3):
            self.VNormals[self.ITris[:, k], :] += FAreas[:, None]*FNormals
            VAreas[self.ITris[:, k]] += FAreas
        #Normalize normals
        VAreas[VAreas == 0] = 1
        self.VNormals = self.VNormals / VAreas[:, None]
        
        ###Step 2: Randomly sample points based on areas
        FAreas = FAreas/np.sum(FAreas)
        AreasC = np.cumsum(FAreas)
        samples = np.sort(np.random.rand(NPoints))
        #Figure out how many samples there are for each face
        FSamples = np.zeros(NTris)
        fidx = 0
        for s in samples:
            while s > AreasC[fidx]:
                fidx += 1
            FSamples[fidx] += 1
        #Now initialize an array that stores the triangle sample indices
        tidx = np.zeros(NPoints, dtype=np.int64)
        idx = 0
        for i in range(len(FSamples)):
            tidx[idx:idx+FSamples[i]] = i
            idx += FSamples[i]
        N = np.zeros((NPoints, 3)) #Allocate space for normals
        idx = 0
        
        #Vector used to determine if points need to be flipped across parallelogram
        V3 = P2 - P1
        V3 = V3/np.sqrt(np.sum(V3**2, 1))[:, None] #Normalize
        
        #Randomly sample points on each face        
        #Generate random points uniformly in parallelogram
        u = np.random.rand(NPoints, 1)
        v = np.random.rand(NPoints, 1)
        Ps = u*V1[tidx, :] + P0[tidx, :]
        Ps += v*V2[tidx, :]
        #Flip over points which are on the other side of the triangle
        dP = Ps - P1[tidx, :]
        proj = np.sum(dP*V3[tidx, :], 1)
        dPPar = V3[tidx, :]*proj[:, None] #Parallel project onto edge
        dPPerp = dP - dPPar
        Qs = Ps - dPPerp
        dP0QSqr = np.sum((Qs - P0[tidx, :])**2, 1)
        dP0PSqr = np.sum((Ps - P0[tidx, :])**2, 1)
        idxreg = np.arange(NPoints, dtype=np.int64)
        idxflip = idxreg[dP0QSqr < dP0PSqr]
        u[idxflip, :] = 1 - u[idxflip, :]
        v[idxflip, :] = 1 - v[idxflip, :]
        Ps[idxflip, :] = P0[tidx[idxflip], :] + u[idxflip, :]*V1[tidx[idxflip], :] + v[idxflip, :]*V2[tidx[idxflip], :]
        
        #Step 3: Compute normals of sampled points by barycentric interpolation
        Ns = u*self.VNormals[self.ITris[tidx, 1], :]
        Ns += v*self.VNormals[self.ITris[tidx, 2], :] 
        Ns += (1-u-v)*self.VNormals[self.ITris[tidx, 0], :]
        
        if colPoints:
            return (Ps.T, Ns.T)
        return (Ps, Ns)
    
    #############################################################
    ####                INPUT/OUTPUT METHODS                #####
    #############################################################
    def loadFile(self, filename):
        suffix = re.split("\.", filename)[-1]
        if suffix == "off":
            self.loadOffFile(filename)
        elif suffix == "toff":
            self.loadTOffFile(filename)
        elif suffix == "obj":
            self.loadObjFile(filename)
        else:
            print "Unsupported file suffix (%s) for loading mesh"%(suffix, filename)
        self.needsDisplayUpdate = True
        self.needsIndexDisplayUpdate = True
    
    def saveFile(self, filename, verbose = False):
        suffix = re.split("\.", filename)[-1]
        if suffix == "off":
            self.saveOffFile(filename, verbose)
        elif suffix == "obj":
            self.saveObjFile(filename, verbose)
        elif suffix == "ply":
            self.savePlyFile(filename, verbose)
        else:
            print "Unsupported file suffix (%s) for saving mesh %s"%(suffix, filename)        
    
    def loadOffFile(self, filename):
        fin = open(filename, 'r')
        nVertices = 0
        nFaces = 0
        nEdges = 0
        lineCount = 0
        face = 0
        vertex = 0
        divideColor = False
        for line in fin:
            lineCount = lineCount+1
            fields = line.split() #Splits whitespace by default
            if len(fields) == 0: #Blank line
                continue
            if fields[0][0] in ['#', '\0', ' '] or len(fields[0]) == 0:
                continue
            #Check section
            if nVertices == 0:
                if fields[0] == "OFF" or fields[0] == "COFF":
                    if len(fields) > 2:
                        fields[1:4] = [int(field) for field in fields]
                        [nVertices, nFaces, nEdges] = fields[1:4]  
                        #Pre-allocate vertex arrays    
                        self.VPos = np.zeros((nVertices, 3)) 
                        self.VColors = np.zeros((nVertices, 3))
                        self.VTexCoords = np.zeros((nVertices, 2))
                    if fields[0] == "COFF":
                        divideColor = True            
                else:
                    fields[0:3] = [int(field) for field in fields]
                    [nVertices, nFaces, nEdges] = fields[0:3]
            elif vertex < nVertices:
                fields = [float(i) for i in fields]
                P = [fields[0],fields[1], fields[2]]
                color = np.array([0.5, 0.5, 0.5]) #Gray by default
                if len(fields) >= 6:
                    #There is color information
                    if divideColor:
                        color = [float(c)/255.0 for c in fields[3:6]]
                    else:
                        color = [float(c) for c in fields[3:6]]
                self.addVertex(P, color)
                vertex = vertex+1
            elif face < nFaces:
                #Assume the vertices are specified in CCW order
                fields = [int(i) for i in fields]
                meshVerts = fields[1:fields[0]+1]
                verts = [self.vertices[i] for i in meshVerts]
                self.addFace(verts)
                face = face+1
        fin.close()
        if np.max(self.VColors) > 1:
            #Rescale colors
            self.VColors = self.VColors / 255.0
    
    #My own "TOFF" format, which is like OFF with texture
    def loadTOffFile(self, filename):
        fin = open(filename, 'r')
        nVertices = 0
        nFaces = 0
        nEdges = 0
        lineCount = 0
        face = 0
        vertex = 0
        textureName = ""
        for line in fin:
            lineCount = lineCount+1
            fields = line.split() #Splits whitespace by default
            if len(fields) == 0: #Blank line
                continue
            if fields[0][0] in ['#', '\0', ' '] or len(fields[0]) == 0:
                continue
            #Check section
            if nVertices == 0:
                if fields[0] == "TOFF":
                    textureName = fields[1]
                    self.texID = loadTexture(textureName)    
                else:
                    fields[0:3] = [int(field) for field in fields]
                    [nVertices, nFaces, nEdges] = fields[0:3]
                    #Pre-allocate vertex arrays    
                    self.VPos = np.zeros((nVertices, 3)) 
                    self.VColors = np.zeros((nVertices, 3))
                    self.VTexCoords = np.zeros((nVertices, 2))
            elif vertex < nVertices:
                fields = [float(i) for i in fields]
                P = [fields[0],fields[1], fields[2]]
                v = self.addVertex(P)
                v.texCoords = [fields[3], fields[4]]
                vertex = vertex+1
            elif face < nFaces:
                #Assume the vertices are specified in CCW order
                fields = [int(i) for i in fields]
                meshVerts = fields[1:fields[0]+1]
                verts = [self.vertices[i] for i in meshVerts]
                self.addFace(verts)
                face = face+1
        fin.close()
            
    def saveOffFile(self, filename, verbose = False, outputColors = True, output255 = False):
        nV = len(self.vertices)
        nE = len(self.edges)
        nF = len(self.faces)
        fout = open(filename, "w")
        #fout.write("#Generated with Chris Tralie's G-RFLCT Library\n")
        #fout.write("#http://www.github.com/ctralie/G-RFLCT\n")
        fout.write("COFF\n%i %i %i\n"%(nV, nF, 0))
        for v in self.vertices:
            fout.write("%g %g %g"%tuple(self.VPos[v.ID, :]))
            if outputColors:
                c = self.VColors[v.ID, :]
                if output255:
                    fout.write(" %i %i %i"%tuple(np.round(c)))
                else:
                    fout.write(" %g %g %g"%tuple(c))
            fout.write("\n")
        for f in self.faces:
            verts = f.getVertices()
            fout.write("%i "%(len(verts)))
            for v in verts:
                fout.write("%i "%(v.ID))
            fout.write("\n")
        fout.close()
        if verbose:
            print "Saved file to %s"%filename

    def savePlyFile(self, filename, verbose = False, outputColors = True, output255 = True):
        nV = len(self.vertices)
        nE = len(self.edges)
        nF = len(self.faces)
        fout = open(filename, "w")
        fout.write("ply\nformat ascii 1.0\nelement vertex %i\n"%nV)
        fout.write("property float x\nproperty float y\nproperty float z\n")
        if outputColors:
            fout.write("property uchar red\nproperty uchar green\nproperty uchar blue\n")
        fout.write("element face %i\n"%nF)
        fout.write("property list uchar int vertex_indices\nend_header\n")
        for v in self.vertices:
            fout.write("%g %g %g"%tuple(self.VPos[v.ID, :]))
            if outputColors:
                c = self.VColors[v.ID, :]
                if output255:
                    fout.write(" %i %i %i"%tuple(np.round(c)))
                else:
                    fout.write(" %g %g %g"%tuple(c))
            fout.write("\n")
        for f in self.faces:
            verts = f.getVertices()
            fout.write("%i "%(len(verts)))
            for v in verts:
                fout.write("%i "%(v.ID))
            fout.write("\n")
        fout.close()
        if verbose:
            print "Saved file to %s"%filename
        
    def loadObjFile(self, filename):
        #TODO: Right now vertex normals, face normals, and texture coordinates are ignored
        #Later incorporate them??
        fin = open(filename, 'r')
        for line in fin:
            fields = line.split()
            if len(fields) == 0: #Blank line
                continue
            if fields[0][0] in ['#', '\0', ' '] or len(fields[0]) == 0:
                continue
            if fields[0] == "v":
                coords = [float(i) for i in fields[1:4]]
                self.addVertex([coords[0], coords[1], coords[2]])
            if fields[0] == "f":
                #Indices are numbered starting at 1 (so need to subtract that off)
                indices = [int(re.split("/",s)[0])-1 for s in fields[1:]]
                verts = [self.vertices[i] for i in indices]
                self.addFace(verts)
        fin.close()
    
    def saveObjFile(self, filename, verbose = False):
        fout = open(filename, "w")
        fout.write("#Generated with Chris Tralie's G-RFLCT Library\n")
        fout.write("#http://www.github.com/ctralie/G-RFLCT\n")
        fout.flush()
        for v in self.vertices:
            fout.write("%g %g %g"%tuple(self.VPos[v.ID, :]))
        for f in self.faces:
            verts = f.getVertices()
            fout.write("f ")
            i = 0
            for i in range(0, len(verts)):
                v = verts[i]
                fout.write("%i"%(v.ID+1))#Indices are numbered starting at 1
                if i < len(verts) - 1:
                    fout.write(" ")
            fout.write("\n")
        fout.close()
        if verbose:
            print "Saved file to %s"%filename

    #############################################################
    ####                     RENDERING                      #####
    #############################################################
    
    #Figure out triangles that go into the index buffer, splitting faces
    #with more than three vertices into triangles (assuming convexity)
    def updateTris(self):
        NTris = np.sum(np.array([len(f.edges)-2 for f in self.faces]))
        self.ITris = np.zeros((NTris, 3), dtype = np.int32)
        idx = 0
        for f in self.faces:
            IDs = [v.ID for v in f.getVertices()]
            for k in range(len(IDs)-2):
                self.ITris[idx, :] = [IDs[0]] + IDs[k+1:k+3]
                idx += 1
    
    def updateNormalBuffer(self):
        #First compute cross products of all face triangles
        V1 = self.VPos[self.ITris[:, 1], :] - self.VPos[self.ITris[:, 0], :]
        V2 = self.VPos[self.ITris[:, 2], :] - self.VPos[self.ITris[:, 0], :]
        FNormals = np.cross(V1, V2)
        FAreas = np.reshape(np.sqrt(np.sum(FNormals**2, 1)), (FNormals.shape[0], 1))
        FAreas[FAreas == 0] = 1
        FNormals = FNormals/FAreas
        self.FNormals = FNormals
        self.FCentroid = 0*FNormals
        self.VNormals = 0*self.VPos
        VAreas = np.zeros((self.VPos.shape[0], 1))
        for k in range(3):
            self.VNormals[self.ITris[:, k], :] += FAreas*FNormals
            VAreas[self.ITris[:, k]] += FAreas
            self.FCentroid += self.VPos[self.ITris[:, k], :]
        self.FCentroid /= 3
        #Normalize normals
        VAreas[VAreas == 0] = 1
        self.VNormals = self.VNormals / VAreas
    
    #buffersOnly: True if there is no mesh structure other than VPos
    #VColors and ITris
    def performDisplayUpdate(self, buffersOnly = False):
        #Clear the buffers for the invalidated mesh
        if self.VPosVBO:
            self.VPosVBO.delete()
        if self.VNormalsVBO:
            self.VNormalsVBO.delete()
        if self.VColorsVBO:
            self.VColorsVBO.delete()
        if self.VTexCoordsVBO:
            self.VTexCoordsVBO.delete()
        if self.IndexVBO:
            self.IndexVBO.delete()
        if self.EdgeLinesVBO:
            self.EdgeLinesVBO.delete()
        if self.VNormalLinesVBO:
            self.VNormalLinesVBO.delete()
        if self.FNormalLinesVBO:
            self.FNormalLinesVBO.delete()
        self.VPosVBO = vbo.VBO(np.array(self.VPos, dtype=np.float32))
        self.VColorsVBO = vbo.VBO(np.array(self.VColors, dtype=np.float32))
        self.VTexCoordsVBO = vbo.VBO(np.array(self.VTexCoords, dtype=np.float32))
        if not buffersOnly:
            self.updateTris()
        self.IndexVBO = vbo.VBO(self.ITris, target=GL_ELEMENT_ARRAY_BUFFER)
        
        #Update edges buffer
        if buffersOnly:
            #Use triangle faces to add edges (will be redundancy but is faster
            #tradeoff for rendering only)
            NTris = self.ITris.shape[0]
            self.EdgeLines = np.zeros((NTris*3*2, 3))
            for k in range(3):
                istart = k*NTris*2
                self.EdgeLines[istart:istart+NTris*2:2, :] = self.VPos[self.ITris[:, k], :]
                self.EdgeLines[istart+1:istart+NTris*2:2, :] = self.VPos[self.ITris[:, (k+1)%3], :]
        else:
            #Use the mesh structure to find the edges
            self.EdgeLines = np.zeros((len(self.edges)*2, 3))
            for i in range(len(self.edges)):
                self.EdgeLines[i*2, :] = self.VPos[self.edges[i].v1.ID, :]
                self.EdgeLines[i*2+1, :] = self.VPos[self.edges[i].v2.ID, :]
        self.EdgeLinesVBO = vbo.VBO(np.array(self.EdgeLines, dtype=np.float32))
        
        #Update face and vertex normals
        scale = 0.01*self.getBBox().getDiagLength()
        self.updateNormalBuffer()
        self.VNormalsVBO = vbo.VBO(np.array(self.VNormals, dtype=np.float32))
        VNList = np.zeros((self.VPos.shape[0]*2, 3))
        VNList[np.arange(0, VNList.shape[0], 2), :] = self.VPos
        VNList[np.arange(1, VNList.shape[0], 2), :] = self.VPos + scale*self.VNormals
        self.VNormalLinesVBO = vbo.VBO(np.array(VNList, dtype=np.float32))
        VFList = np.zeros((self.ITris.shape[0]*2, 3))
        VFList[np.arange(0, VFList.shape[0], 2), :] = self.FCentroid
        VFList[np.arange(1, VFList.shape[0], 2), :] = self.FCentroid + scale*self.FNormals
        self.FNormalLinesVBO = vbo.VBO(np.array(VFList, dtype=np.float32))
        
        self.needsDisplayUpdate = False
    
    #vertexColors is an Nx3 numpy array, where N is the number of vertices
    def renderGL(self, drawEdges = False, drawVerts = False, drawFaces = True, drawVertexNormals = True, drawFaceNormals = True, useLighting = True, useTexture = False):
        if self.needsDisplayUpdate:
            self.performDisplayUpdate()
            self.needsDisplayUpdate = False
        #Draw the requested objects
        if useLighting:
            glEnable(GL_LIGHTING)
        else:
            glDisable(GL_LIGHTING)
        if drawFaces:
            glEnableClientState(GL_VERTEX_ARRAY)
            glEnableClientState(GL_COLOR_ARRAY)
            glEnableClientState(GL_NORMAL_ARRAY)
            self.VPosVBO.bind()
            glVertexPointerf(self.VPosVBO)
            self.VNormalsVBO.bind()
            glNormalPointerf(self.VNormalsVBO)
            self.VColorsVBO.bind()
            glColorPointerf(self.VColorsVBO)
            if useTexture and self.VTexCoordsVBO:
                glEnable(GL_TEXTURE_2D)
                glEnableClientState(GL_TEXTURE_COORD_ARRAY)
                self.VTexCoordsVBO.bind()
                glTexCoordPointerf(self.VTexCoordsVBO)
                glBindTexture(GL_TEXTURE_2D, self.texID)
            else:
                glEnable(GL_COLOR_MATERIAL)
                glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
            self.IndexVBO.bind()
            glDrawElements(GL_TRIANGLES, 3*self.ITris.shape[0], GL_UNSIGNED_INT, None)
            self.IndexVBO.unbind()
            if useTexture and self.VTexCoordsVBO:
                self.VTexCoordsVBO.unbind()
                glDisableClientState(GL_TEXTURE_COORD_ARRAY)
            self.VPosVBO.unbind()
            self.VNormalsVBO.unbind()
            self.VColorsVBO.unbind()
            glDisableClientState(GL_NORMAL_ARRAY)
            glDisableClientState(GL_COLOR_ARRAY)
            glDisableClientState(GL_VERTEX_ARRAY)
        
        if drawVerts:
            glEnableClientState(GL_VERTEX_ARRAY)
            self.VPosVBO.bind()
            glVertexPointerf(self.VPosVBO)
            glDisable(GL_LIGHTING)
            glPointSize(POINT_SIZE)
            glColor3f(1.0, 0, 0)
            glDrawArrays(GL_POINTS, 0, self.VPos.shape[0])
            self.VPosVBO.unbind()
            glDisableClientState(GL_VERTEX_ARRAY)
        
        if drawEdges:
            glEnableClientState(GL_VERTEX_ARRAY)
            self.EdgeLinesVBO.bind()
            glVertexPointerf(self.EdgeLinesVBO)
            glDisable(GL_LIGHTING)
            glLineWidth(2)
            glColor3f(1.0, 1.0, 0.0)
            glDrawArrays(GL_LINES, 0, self.EdgeLines.shape[0])
            self.EdgeLinesVBO.unbind()
            glDisableClientState(GL_VERTEX_ARRAY)
        
        if drawVertexNormals:
            glEnableClientState(GL_VERTEX_ARRAY)
            self.VNormalLinesVBO.bind()
            glVertexPointerf(self.VNormalLinesVBO)
            glDisable(GL_LIGHTING)
            glColor3f(1.0, 0, 1.0)
            glDrawArrays(GL_LINES, 0, self.VPos.shape[0]*2)
            self.VNormalLinesVBO.unbind()
            glDisableClientState(GL_VERTEX_ARRAY)
        
        if drawFaceNormals:
            glEnableClientState(GL_VERTEX_ARRAY)
            self.FNormalLinesVBO.bind()
            glVertexPointerf(self.VNormalLinesVBO)
            glDisable(GL_LIGHTING)
            glColor3f(1.0, 0, 0)
            glDrawArrays(GL_LINES, 0, self.ITris.shape[0]*2)
            self.FNormalLinesVBO.unbind()
            glDisableClientState(GL_VERTEX_ARRAY)
        
        if useLighting:
            glEnable(GL_LIGHTING)

    #Render the vertices of the mesh as points with colors equal to their indices
    #in the vertex list.  Used to help with vertex selection (will work as long as
    #there are fewer than 2^32 vertices)
    #TODO: Make this faster with vertex buffer instead of display list
    #(but that would require some annoying numpy bitwhacking)
    def renderGLIndices(self):
        if self.needsDisplayUpdate:
            self.performDisplayUpdate()
            self.needsDisplayUpdate = False
        glCallList(self.IndexDisplayList)
        
    def updateIndexDisplayList(self):
        if self.IndexDisplayList != -1: #Deallocate previous display list
            glDeleteLists(self.IndexDisplayList, 1)
        self.IndexDisplayList = glGenLists(1)
        print "Updating index display list"
        glNewList(self.IndexDisplayList, GL_COMPILE)
        glDisable(GL_LIGHTING)
        N = len(self.vertices)
        #First draw all of the faces with index N+1 so that occlusion is
        #taken into proper consideration
        [R, G, B, A] = splitIntoRGBA(N+2)
        glColor4ub(R, G, B, A)
        glEnableClientState(GL_VERTEX_ARRAY)
        self.VPosVBO.bind()
        glVertexPointerf(self.VPosVBO)
        self.IndexVBO.bind()
        glDrawElements(GL_TRIANGLES, 3*self.ITris.shape[0], GL_UNSIGNED_INT, None)
        self.IndexVBO.unbind()
        self.VPosVBO.unbind()
        glPointSize(POINT_SIZE)
        glBegin(GL_POINTS)
        for i in range(0, N):
            P = self.VPos[i, :]
            [R, G, B, A] = splitIntoRGBA(i+1)
            glColor4ub(R, G, B, A)
            glVertex3f(P[0], P[1], P[2])
        glEnd()
        glEnable(GL_LIGHTING)
        glEndList()

    #Slow version with no spatial subdivision
    def getRayIntersection(self, ray):
        t = float("inf")
        Point = None
        Face = None
        for f in self.faces:
            intersection = ray.intersectMeshFace(f)
            if intersection != None:
                if intersection[0] < t:
                    t = intersection[0]
                    Point = intersection[1]
                    Face = f
        return [t, Point, Face]
        return None
    
    def __str__(self):
        nV = len(self.vertices)
        nE = len(self.edges)
        nF = len(self.faces)
        euler = nV-nE+nF
        return "PolyMesh Object: NVertices = %i, NEdges = %i, NFaces = %i, euler=%i"%(nV, nE, nF, euler)    


#############################################################
####               UTILITY FUNCTIONS                    #####
#############################################################
def saveOffFileExternal(filename, VPos, VColors, ITris):
    #Save off file given buffers, not necessarily in the PolyMesh object
    nV = VPos.shape[0]
    nF = ITris.shape[0]
    fout = open(filename, "w")
    if VColors.size == 0:
        fout.write("OFF\n%i %i %i\n"%(nV, nF, 0))
    else:
        fout.write("COFF\n%i %i %i\n"%(nV, nF, 0))
    for i in range(nV):
        fout.write("%g %g %g"%tuple(VPos[i, :]))
        if VColors.size > 0:
            fout.write(" %g %g %g"%tuple(VColors[i, :]))
        fout.write("\n")
    for i in range(nF):
        fout.write("3 %i %i %i\n"%tuple(ITris[i, :]))
    fout.close()

#Return VPos, VColors, and ITris without creating any structure
#(Assumes triangle mesh)
def loadOffFileExternal(filename):
    fin = open(filename, 'r')
    nVertices = 0
    nFaces = 0
    lineCount = 0
    face = 0
    vertex = 0
    divideColor = False
    VPos = np.zeros((0, 3))
    VColors = np.zeros((0, 3))
    ITris = np.zeros((0, 3))
    for line in fin:
        lineCount = lineCount+1
        fields = line.split() #Splits whitespace by default
        if len(fields) == 0: #Blank line
            continue
        if fields[0][0] in ['#', '\0', ' '] or len(fields[0]) == 0:
            continue
        #Check section
        if nVertices == 0:
            if fields[0] == "OFF" or fields[0] == "COFF":
                if len(fields) > 2:
                    fields[1:4] = [int(field) for field in fields]
                    [nVertices, nFaces, nEdges] = fields[1:4]  
                    print "nVertices = %i, nFaces = %i"%(nVertices, nFaces)
                    #Pre-allocate vertex arrays    
                    VPos = np.zeros((nVertices, 3)) 
                    VColors = np.zeros((nVertices, 3))
                    ITris = np.zeros((nFaces, 3))
                if fields[0] == "COFF":
                    divideColor = True            
            else:
                fields[0:3] = [int(field) for field in fields]
                [nVertices, nFaces, nEdges] = fields[0:3]
                VPos = np.zeros((nVertices, 3)) 
                VColors = np.zeros((nVertices, 3))
                ITris = np.zeros((nFaces, 3))
        elif vertex < nVertices:
            fields = [float(i) for i in fields]
            P = [fields[0],fields[1], fields[2]]
            color = np.array([0.5, 0.5, 0.5]) #Gray by default
            if len(fields) >= 6:
                #There is color information
                if divideColor:
                    color = [float(c)/255.0 for c in fields[3:6]]
                else:
                    color = [float(c) for c in fields[3:6]]
            VPos[vertex, :] = P
            VColors[vertex, :] = color
            vertex = vertex+1
        elif face < nFaces:
            #Assume the vertices are specified in CCW order
            fields = [int(i) for i in fields]
            ITris[face, :] = fields[1:fields[0]+1]
            face = face+1
    fin.close()
    VPos = np.array(VPos, np.float64)
    VColors = np.array(VColors, np.float64)
    ITris = np.array(ITris, np.int32)
    return (VPos, VColors, ITris) 

#Make a surface of revolution around the y-axis (x = 0, z = 0)
#X: An ordrered N x 2 list of points in the XY plane that make up a curve
#NSteps: Number of points to sample around the circle of revolution
def makeSurfaceOfRevolution(X, NSteps):
    N = X.shape[0]
    thetas = np.linspace(0, 2*np.pi, NSteps+1)[0:NSteps]
    M = N*NSteps #Total number of vertices
    VPos = np.zeros((M, 3))
    #First make all of the points
    for i in range(NSteps):
        VPos[N*i:N*(i+1), 1] = X[:, 1] #The Y positions stay the same; it's only XZ that change
        VPos[N*i:N*(i+1), 0] = X[:, 0]*np.cos(thetas[i])
        VPos[N*i:N*(i+1), 2] = X[:, 0]*np.sin(thetas[i])
    #Now make all of the triangle faces connecting them
    L = (N-1)*2 #Number of triangles added for each curve position
    ITris = np.zeros((L*NSteps, 3))
    for i in range(NSteps):
        j = (i+1)%NSteps
        #Upper triangles, CCW order
        idx = np.arange(L*i, L*(i+1), 2)
        ITris[idx, 0] = N*i + np.arange(N-1)
        ITris[idx, 1] = N*j + np.arange(N-1)
        ITris[idx, 2] = N*j + 1 + np.arange(N-1)
        #Lower triangles, CCW order
        idx = np.arange(L*i+1, L*(i+1), 2)
        ITris[idx, 0] = N*i + np.arange(N-1)
        ITris[idx, 1] = N*j + 1 + np.arange(N-1)
        ITris[idx, 2] = N*i + 1 + np.arange(N-1)
    return (VPos, ITris)

#############################################################
####               STANDARD MESHES                      #####
#############################################################

#Helper function for getBoxMesh and addFaceTiles
def makeBoxEdge(mesh, v1, v2, stepSize):
    if stepSize < 0:
        return [v1, v2]
    verts = [v1]
    direc = mesh.VPos[v2.ID, :] - mesh.VPos[v1.ID, :]
    frac = stepSize/np.sqrt(direc.dot(direc))
    #Round to the nearest integer number of tiles
    N = int(math.floor(1.0/frac+0.5))
    if N == 0:
        N = 1
    frac = 1.0/float(N)
    for i in range(1, N):
        newVert = mesh.addVertex(mesh.VPos[v1.ID, :]+direc*frac*i)
        verts.append(newVert)
    verts.append(v2)
    return verts

#Helper function for getBoxMesh
def addFaceTiles(mesh, stepSize, ebott, eright, etop, eleft):
    topRow = etop
    index = 1
    for index in range(1, len(eleft)):
        bottomRow = None
        if index == len(eleft)-1:
            bottomRow = ebott
        else:
            bottomRow = makeBoxEdge(mesh, eleft[index], eright[index], stepSize)
        #Now add the square faces on this part
        for i in range(0, len(topRow)-1):
            mesh.addFace([bottomRow[i], bottomRow[i+1], topRow[i+1], topRow[i]])
        topRow = bottomRow

#L is length along z
#W is width along x
#H is height along y
#stepSize is the length of each square tile.  By default there are no tiles
#(stepSize = -1).  If one of the sides is not an integer multiple of the step size,
#then round to the nearest step size that would make it an integer multiple along
#that dimension
def getBoxMesh(L = 1.0, W = 1.0, H = 1.0, C = np.array([0, 0, 0]), stepSize = -1):
    mesh = PolyMesh()
    endpoints = []
    for dZ in [L/2.0, -L/2.0]:
        for dH in [-H/2.0, H/2.0]:
            for dW in [-W/2.0, W/2.0]:
                endpoints.append(mesh.addVertex(C+np.array([dW, dH, dZ])))
    edgeIndices = [[0, 1], [1, 3], [3, 2], [2, 0], [1, 5], [5, 7], [7, 3], [7, 6], [6, 2], [0, 4], [4, 6], [4, 5]]
    edges = []
    edgesRev = []
    for edgePointers in edgeIndices:
        [v1, v2] = [endpoints[edgePointers[0]], endpoints[edgePointers[1]]]
        edges.append(makeBoxEdge(mesh, v1, v2, stepSize))
    for edge in edges:
        edgeRev = edge[:]
        edgeRev.reverse()
        edgesRev.append(edgeRev)
    #def addFaceTiles(mesh, stepSize, ebott, eright, etop, eleft):
    #Front Face
    addFaceTiles(mesh, stepSize, edges[0], edgesRev[1], edgesRev[2], edges[3])
    #Back Face
    addFaceTiles(mesh, stepSize, edgesRev[11], edgesRev[10], edges[7], edgesRev[5])
    #Left Face
    addFaceTiles(mesh, stepSize, edgesRev[9], edges[3], edges[8], edgesRev[10])
    #Right Face
    addFaceTiles(mesh, stepSize, edges[4], edgesRev[5], edgesRev[6], edgesRev[1])
    #Top Face
    addFaceTiles(mesh, stepSize, edgesRev[2], edges[6], edgesRev[7], edges[8])
    #Bottom Face
    addFaceTiles(mesh, stepSize, edges[11], edges[4], edges[0], edges[9])
    return mesh

def getRectMesh(P0, P1, P2, P3, stepSize = -1):
    mesh = PolyMesh()
    endpoints = [P0, P1, P2, P3]
    for i in range(0, len(endpoints)):
        endpoints[i] = mesh.addVertex(endpoints[i])
    edgeIndices = [[0, 1], [2, 1], [3, 2], [3, 0]]
    edges = []
    for edgePointers in edgeIndices:
        [v1, v2] = [endpoints[edgePointers[0]], endpoints[edgePointers[1]]]
        edges.append(makeBoxEdge(mesh, v1, v2, stepSize))
    addFaceTiles(mesh, stepSize, edges[0], edges[1], edges[2], edges[3])
    return mesh

def getTetrahedronMesh():
    mesh = PolyMesh()
    v1 = mesh.addVertex(np.array([-1, 1, 1]))
    v2 = mesh.addVertex(np.array([1, -1, 1]))
    v3 = mesh.addVertex(np.array([1, 1, -1]))
    v4 = mesh.addVertex(np.array([-1, -1, -1]))
    mesh.addFace([v1, v2, v3])
    mesh.addFace([v2, v4, v3])
    mesh.addFace([v3, v4, v1])
    mesh.addFace([v4, v2, v1])
    return mesh

def getOctahedronMesh():
    mesh = PolyMesh()
    v1 = mesh.addVertex(np.array([0, 0, 1]))
    v2 = mesh.addVertex(np.array([1, 0, 0]))
    v3 = mesh.addVertex(np.array([0, 1, 0]))
    v4 = mesh.addVertex(np.array([0, -1, 0]))
    v5 = mesh.addVertex(np.array([0, 0, -1]))
    v6 = mesh.addVertex(np.array([-1, 0, 0]))
    #Top Part
    mesh.addFace([v3, v1, v2])
    mesh.addFace([v3, v2, v5])
    mesh.addFace([v3, v5, v6])
    mesh.addFace([v3, v6, v1])
    #Bottom Part
    mesh.addFace([v1, v4, v2])
    mesh.addFace([v2, v4, v5])
    mesh.addFace([v5, v4, v6])
    mesh.addFace([v6, v4, v1])
    return mesh

def getIcosahedronMesh():
    mesh = PolyMesh()
    phi = (1+math.sqrt(5))/2
    #Use the unit cube to help construct the icosahedron
    #Front cube face vertices
    FL = mesh.addVertex(np.array([-0.5, 0, phi/2]))
    FR = mesh.addVertex(np.array([0.5, 0, phi/2]))
    #Back cube face vertices
    BL = mesh.addVertex(np.array([-0.5, 0, -phi/2]))
    BR = mesh.addVertex(np.array([0.5, 0, -phi/2]))
    #Top cube face vertices
    TF = mesh.addVertex(np.array([0, phi/2, 0.5]))
    TB = mesh.addVertex(np.array([0, phi/2, -0.5]))
    #Bottom cube face vertices
    BF = mesh.addVertex(np.array([0, -phi/2, 0.5]))
    BB = mesh.addVertex(np.array([0, -phi/2, -0.5]))
    #Left cube face vertices
    LT = mesh.addVertex(np.array([-phi/2, 0.5, 0]))
    LB = mesh.addVertex(np.array([-phi/2, -0.5, 0]))
    #Right cube face vertices
    RT = mesh.addVertex(np.array([phi/2, 0.5, 0]))
    RB = mesh.addVertex(np.array([phi/2, -0.5, 0]))
    
    #Add the icosahedron faces associated with each cube face
    #Front cube face faces
    mesh.addFace([TF, FL, FR])
    mesh.addFace([BF, FR, FL])
    #Back cube face faces
    mesh.addFace([TB, BR, BL])
    mesh.addFace([BB, BL, BR])
    #Top cube face faces
    mesh.addFace([TB, TF, RT])
    mesh.addFace([TF, TB, LT])
    #Bottom cube face faces
    mesh.addFace([BF, BB, RB])
    mesh.addFace([BB, BF, LB])
    #Left cube face faces
    mesh.addFace([LB, LT, BL])
    mesh.addFace([LT, LB, FL])
    #Right cube face faces
    mesh.addFace([RT, RB, BR])
    mesh.addFace([RB, RT, FR])
    
    #Add the icosahedron faces associated with each cube vertex
    #Front of cube
    mesh.addFace([FL, TF, LT]) #Top left corner
    mesh.addFace([BF, LB, FL]) #Bottom left corner
    mesh.addFace([FR, RT, TF]) #Top right corner
    mesh.addFace([BF, RB, FR]) #Bottom right corner
    #Back of cube
    mesh.addFace([LT, TB, BL]) #Top left corner
    mesh.addFace([BL, LB, BB]) #Bottom left corner
    mesh.addFace([RT, BR, TB]) #Top right corner
    mesh.addFace([BB, RB, BR]) #Bottom right corner
    
    return mesh

def getDodecahedronMesh():
    #Use icosahedron dual to help construct this
    icosa = getIcosahedronMesh()
    mesh = PolyMesh()
    #Add the vertex associated with each icosahedron face
    for f in icosa.faces:
        f.V = mesh.addVertex(f.getCentroid())
    #Add the face associated with each icosahedron vertex
    for v in icosa.vertices:
        verts = [f.V for f in v.getAttachedFaces()]
        vertsP = [mesh.VPos[V.ID, :] for V in verts]
        comparator = PointsCCWComparator(np.array([0, 0, 0]), vertsP[0])
        vertsP = [(i, vertsP[i]) for i in range(len(vertsP))]
        #Sort vertices in CCW order
        verts = [verts[x[0]] for x in sorted(vertsP, cmp=comparator.compare)]
        mesh.addFace(verts)
    return mesh

def getHemiOctahedronMesh():
    mesh = PolyMesh()
    v1 = mesh.addVertex(np.array([0, 0, 1]))
    v2 = mesh.addVertex(np.array([1, 0, 0]))
    v3 = mesh.addVertex(np.array([0, 1, 0]))
    v4 = mesh.addVertex(np.array([0, -1, 0]))
    v6 = mesh.addVertex(np.array([-1, 0, 0]))
    #Top Part
    mesh.addFace([v3, v1, v2])
    mesh.addFace([v3, v6, v1])
    #Bottom Part
    mesh.addFace([v1, v4, v2])
    mesh.addFace([v6, v4, v1])
    return mesh

def getSphereMesh(R, nIters):
    mesh = getOctahedronMesh()
    for i in range(nIters):
        mesh.evenTriangleRemesh()
        #Move points so that they're R away from the origin
        mesh.VPos = R*mesh.VPos/np.reshape(np.sqrt(np.sum(mesh.VPos**2, 1)), [mesh.VPos.shape[0], 1])
    return mesh

def getHemiSphereMesh(R, nIters):
    mesh = getHemiOctahedronMesh()
    for i in range(nIters):
        mesh.evenTriangleRemesh()
        #Move points so that they're R away from the origin
        mesh.VPos = mesh.VPos/np.reshape(np.sqrt(np.sum(mesh.VPos**2, 1)), [mesh.VPos.shape[0], 1])
    return mesh    

if __name__ == '__main__':
    icosahedronMesh = getIcosahedronMesh()
    icosahedronMesh.saveOffFile('icosahedron.off')
    dodecahedronMesh = getDodecahedronMesh()
    dodecahedronMesh.saveOffFile('dodecahedron.off')
    sphereMesh = getSphereMesh(1, 2)
    sphereMesh.saveOffFile("sphere.off")
    boxMesh = getBoxMesh(1, 1, 1, np.array([0, 0, 0]), 1)
    boxMesh.saveOffFile("box.off")