class MeshViewerCanvas(glcanvas.GLCanvas): def __init__(self, parent): attribs = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24) glcanvas.GLCanvas.__init__(self, parent, -1, attribList=attribs) self.context = glcanvas.GLContext(self) self.setText = False self.parent = parent #Camera state variables self.size = self.GetClientSize() #self.camera = MouseSphericalCamera(self.size.x, self.size.y) self.camera = MousePolarCamera(self.size.width, self.size.height) #Main state variables self.MousePos = [0, 0] self.initiallyResized = False random.seed() #GUI State Variables self.GUIState = STATE_INTRO self.GUISubstate = -1 #Head Mesh self.headMesh = PolyMesh() self.headMesh.loadFile('NotreDameMedium.off') self.headMeshLowres = PolyMesh() self.headMeshLowres.loadFile('NotreDameLowres.off') self.styrofoamHead = PolyMesh() self.styrofoamHead.loadFile('StyrofoamHead.off') self.rotAngle = 0 self.zoom = 0 self.rotCount = 0 self.timelineCount = 0 #User's face self.userMesh = None self.colorUserMesh = None #ICP state variables self.ICPTransformation = np.zeros(0) self.ICPMutex = Lock() self.ICPThread = None self.bbox = self.headMesh.getBBox() self.camera.centerOnBBox(self.bbox, theta=-math.pi / 2, phi=math.pi / 2) self.zCenter = (self.bbox.zmax + self.bbox.zmin) / 2.0 self.GLinitialized = False #GL-related events wx.EVT_ERASE_BACKGROUND(self, self.processEraseBackgroundEvent) wx.EVT_SIZE(self, self.processSizeEvent) wx.EVT_PAINT(self, self.processPaintEvent) #Mouse Events wx.EVT_LEFT_DOWN(self, self.MouseDown) wx.EVT_LEFT_UP(self, self.MouseUp) wx.EVT_RIGHT_DOWN(self, self.MouseDown) wx.EVT_RIGHT_UP(self, self.MouseUp) wx.EVT_MIDDLE_DOWN(self, self.MouseDown) wx.EVT_MIDDLE_UP(self, self.MouseUp) wx.EVT_MOTION(self, self.MouseMotion) self.initGL() def drawText(self, x, y, text, size=1, font=GLUT_BITMAP_TIMES_ROMAN_24): glDisable(GL_LIGHTING) glDisable(GL_DEPTH_TEST) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluOrtho2D(0, self.size[0], 0, self.size[1]) glMatrixMode(GL_MODELVIEW) glLoadIdentity() glColor3f(1, 0, 0) glRasterPos2f(x, y) glPushMatrix() #for i in range(len(text)): # glutBitmapCharacter(font, ord(text[i])) glPopMatrix() glEnable(GL_LIGHTING) glEnable(GL_DEPTH_TEST) def startButtonHandler(self, evt): print "Starting Face capture..." if not self.setText: self.titleText.SetLabelText("Capturing Face...") self.setText = True #os.popen3("SingleFace.exe") #Captures the face process = subprocess.Popen("SingleFace", shell=True, stdout=subprocess.PIPE) process.wait() print "FINISHED CAPTURE" extractMeshFiles() #Convert the captured data to a triangulated mesh self.userMesh = LaplacianMesh() self.userMesh.loadFile("out.off") self.bbox = self.userMesh.getBBox() self.camera.centerOnBBox(self.bbox, theta=-math.pi / 2, phi=math.pi / 2) self.GUIState = STATE_SHOWPOINTS self.GUISubstate = SHOWPOINTS_ZOOMIN self.zoom = 0 self.setText = False self.repaint() def processEraseBackgroundEvent(self, event): pass #avoid flashing on MSW. def processSizeEvent(self, event): self.size = self.GetClientSize() self.SetCurrent(self.context) glViewport(0, 0, self.size.width, self.size.height) if not self.initiallyResized: #The canvas gets resized once on initialization so the camera needs #to be updated accordingly at that point self.camera = MousePolarCamera( self.size.width, self.size.height, ) self.camera.centerOnBBox(self.bbox, theta=-math.pi / 2, phi=math.pi / 2) self.initiallyResized = True def processPaintEvent(self, event): dc = wx.PaintDC(self) self.SetCurrent(self.context) if not self.GLinitialized: self.initGL() self.GLinitialized = True self.repaint() def handleIntroState(self): #Draw head glTranslatef(0, 0, self.zCenter) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) self.headMesh.renderGL() self.rotAngle = self.rotAngle + 1 self.rotAngle = self.rotAngle % 360 #self.drawText(self.size[0]/2-50, self.size[1]-40, "A Head of Our Times") if not self.setText: self.titleText.SetLabelText("A Head of Our Times") self.startButton.SetLabelText("Click To Start") self.setText = True time.sleep(0.01) self.Refresh() def handleShowPointsState(self): if self.GUISubstate == SHOWPOINTS_ZOOMIN: if not self.setText: self.titleText.SetLabelText("Showing 3D Face Capture Result") glTranslatef(0, 0, self.zoom) self.userMesh.renderGL(drawEdges=1, drawVerts=1, drawNormals=0, drawFaces=0) glTranslatef(0, 0, -self.zoom) self.zoom = self.zoom + 0.005 time.sleep(0.01) if self.zoom >= abs(self.zCenter / 6): self.GUISubstate = SHOWPOINTS_ROTATELEFT self.rotAngle = 0 self.rotCount = 0 elif self.GUISubstate == SHOWPOINTS_ROTATELEFT: self.setText = True glTranslatef(0, 0, self.zCenter + self.zoom) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) if self.rotCount == 0: self.userMesh.renderGL(drawEdges=1, drawVerts=1, drawNormals=0, drawFaces=0) else: self.userMesh.renderGL(drawEdges=0, drawVerts=0, drawNormals=0, drawFaces=1, lightingOn=False) self.rotAngle = self.rotAngle - 1 if self.rotAngle < -60: self.GUISubstate = SHOWPOINTS_ROTATERIGHT time.sleep(0.01) elif self.GUISubstate == SHOWPOINTS_ROTATERIGHT: glTranslatef(0, 0, self.zCenter + self.zoom) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) if self.rotCount == 0: self.userMesh.renderGL(drawEdges=1, drawVerts=1, drawNormals=0, drawFaces=0) else: self.userMesh.renderGL(drawEdges=0, drawVerts=0, drawNormals=0, drawFaces=1, lightingOn=False) self.rotAngle = self.rotAngle + 1 if self.rotAngle > 60: self.rotCount = self.rotCount + 1 if self.rotCount >= 2: self.GUISubstate = SHOWPOINTS_CENTER else: self.GUISubstate = SHOWPOINTS_ROTATELEFT time.sleep(0.01) elif self.GUISubstate == SHOWPOINTS_CENTER: glTranslatef(0, 0, self.zCenter + self.zoom) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) self.userMesh.renderGL(drawEdges=0, drawVerts=0, drawNormals=0, drawFaces=1, lightingOn=False) self.rotAngle = self.rotAngle - 1 if self.rotAngle <= 0: self.GUISubstate = SHOWPOINTS_ZOOMOUT time.sleep(0.01) elif self.GUISubstate == SHOWPOINTS_ZOOMOUT: glTranslatef(0, 0, self.zoom) self.userMesh.renderGL(drawEdges=0, drawVerts=0, drawNormals=0, drawFaces=1, lightingOn=False) glTranslatef(0, 0, -self.zoom) self.zoom = self.zoom - 0.005 time.sleep(0.01) if self.zoom <= 0: self.GUIState = STATE_SHOWICP self.setText = False self.bbox = self.styrofoamHead.getBBox() self.camera.centerOnBBox(self.bbox, theta=-math.pi / 2, phi=math.pi / 2) #Get the mesh's renderbuffer ready before the thread starts so that no ICP frames are missed self.styrofoamHead.renderGL(drawEdges=0, drawVerts=0, drawNormals=0, drawFaces=1) self.ICPThread = ICPThread(self.userMesh, self.styrofoamHead, self, self.ICPMutex) self.ICPThread.start() self.drawText(self.size[0] / 2 - 50, self.size[1] - 40, "Showing Captured Face") self.Refresh() def transplantColors(self): perms = np.random.permutation(len(self.userMesh.vertices)) N = min(1000, len(perms)) VX = np.zeros((N, 3)) CX = np.zeros((N, 3)) #Randomly subsample points for speed for i in range(N): idx = perms[i] P = self.userMesh.vertices[idx].pos C = self.userMesh.vertices[idx].color VX[i, :] = np.array([P.x, P.y, P.z]) CX[i, :] = C VX = transformPoints(self.ICPTransformation, VX) self.titleText.SetLabelText("Placing Colors on Head") print "Getting initial guess of point positions..." ts, us = getInitialGuessClosestPoints(VX, self.styrofoamHead) print "Finished initial guess of point positions" self.colorUserMesh = transplantColorsLaplacianUsingBarycentric( self.styrofoamHead, CX, ts, us) def handleICPState(self): glPushMatrix() if self.ICPTransformation.shape[0] > 0: glMultMatrixd(self.ICPTransformation.transpose().flatten()) self.userMesh.renderGL(drawEdges=0, drawVerts=0, drawNormals=0, drawFaces=1, lightingOn=False) glPopMatrix() glColor3f(0.7, 0.7, 0.7) self.styrofoamHead.renderGL(drawEdges=0, drawVerts=0, drawNormals=0, drawFaces=1, lightingOn=True) #self.drawText(self.size[0]/2-50, self.size[1]-40, "Aligning Face with Statue...") if not self.setText: self.titleText.SetLabelText("Performing Rigid Alignment to Statue") self.setText = True if not self.ICPThread.isAlive(): print "Alignment finished" print "Transplanting colors..." self.transplantColors() print "Finished Transplanting colors" self.setText = False self.GUIState = STATE_DECAY self.timelineState = 0 self.bbox = self.colorUserMesh.getBBox() self.camera.centerOnBBox(self.bbox, theta=-math.pi / 2, phi=math.pi / 2) def handleDecayState(self): if self.timelineState > 40: #Render the head in its current form self.colorUserMesh.renderGL(drawEdges=0, drawVerts=0, drawNormals=0, drawFaces=1, lightingOn=True) if self.timelineState == 0: titleFont = wx.Font(24, wx.FONTFAMILY_DECORATIVE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) self.titleText.SetFont(titleFont) if self.timelineState <= 20: #TODO: Include Images here self.titleText.SetLabelText( "1180 - 1223: Paris becomes capital of French Kingdom") time.sleep(0.05) self.Refresh() elif self.timelineState <= 40: self.titleText.SetLabelText( "1163: Construction of Notre Dame Cathedral Begins") time.sleep(0.05) self.Refresh() elif self.timelineState <= 60: self.titleText.SetLabelText( "1245 - 1258: Remodling of North transept and addition of the Virtue" ) time.sleep(0.1) elif self.timelineState < 120: year = int( np.round((self.timelineState - 60) * (1793 - 1258) / 60.0 + 1258)) self.titleText.SetLabelText( "%i: Virtue in Situ on Cathedral Facade" % year) #Dampen the colors for v in self.colorUserMesh.vertices: C = v.color meanC = sum(C) / float(len(C)) v.color = [c - (c - meanC) * 0.025 for c in C] self.colorUserMesh.needsDisplayUpdate = True else: if self.timelineState == 121: self.titleText.SetLabelText( "1793: French Revolution and the Iconoclasm/Vandalism") #TODO: Rotate head slightly to left, fix chunks if self.timelineState == 120: USINGLAPLACIAN = False self.titleText.SetLabelText( "1793: French Revolution Iconoclasm: Decapitation Imminent..." ) #Make some dents perms = np.random.permutation(len(self.colorUserMesh.vertices)) print "CHUNKING" constraints = [] chunks = 0 for i in range(0, 20): idx = perms[i] V = self.colorUserMesh.vertices[idx] if USINGLAPLACIAN: N = V.getNormal() #if np.abs(N.z) < 0.8: # continue chunks = chunks + 1 chunkSize = 0.05 * np.random.rand(1)[0] P = V.pos - chunkSize * Vector3D(0, 0, -1) constraints.append((idx, P)) otherVerts = V.getVertexNeighbors() for V2 in otherVerts: P = V2.pos - chunkSize * 0.5 * Vector3D(0, 0, -1) constraints.append((V2.ID, P)) else: P = V.pos randDiff = 0.02 * np.random.rand(1)[0] P = P - randDiff * V.getNormal() V.pos = P for V2 in V.getVertexNeighbors(): P = V2.pos - 0.5 * randDiff * V2.getNormal() V2.pos = P if USINGLAPLACIAN: self.colorUserMesh.solveVertexPositionsWithConstraints( constraints) self.colorUserMesh.needsDisplayUpdate = True print "There were %i chunks" % chunks self.timelineState = self.timelineState + 1 self.Refresh() def handleShowStretchState(self): #TODO: Finish this self.GUIState = STATE_SHOWSTRETCH self.Refresh() def repaint(self): #Set up projection matrix glMatrixMode(GL_PROJECTION) glLoadIdentity() farDist = (self.camera.eye - self.bbox.getCenter()).Length() * 2 #This is to make sure we can see on the inside farDist = max(farDist, self.bbox.getDiagLength() * 2) nearDist = farDist / 50.0 gluPerspective(180.0 * self.camera.yfov / M_PI, float(self.size.x) / self.size.y, nearDist, farDist) #Set up modelview matrix self.camera.gotoCameraFrame() glClearColor(0.0, 0.0, 0.0, 0.0) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLightfv(GL_LIGHT0, GL_POSITION, [3.0, 4.0, 5.0, 0.0]) glLightfv(GL_LIGHT1, GL_POSITION, [-3.0, -2.0, -3.0, 0.0]) glEnable(GL_LIGHTING) glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, [0.8, 0.8, 0.8, 1.0]) glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [0.2, 0.2, 0.2, 1.0]) glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, 64) self.zCenter = (self.bbox.zmax + self.bbox.zmin) / 2.0 if self.GUIState == STATE_INTRO: self.handleIntroState() elif self.GUIState == STATE_SHOWPOINTS: self.handleShowPointsState() elif self.GUIState == STATE_SHOWICP: self.handleICPState() elif self.GUIState == STATE_DECAY: self.handleDecayState() self.SwapBuffers() def initGL(self): glLightModelfv(GL_LIGHT_MODEL_AMBIENT, [0.2, 0.2, 0.2, 1.0]) glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE) glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1.0]) glEnable(GL_LIGHT0) glLightfv(GL_LIGHT1, GL_DIFFUSE, [0.5, 0.5, 0.5, 1.0]) glEnable(GL_LIGHT1) glEnable(GL_NORMALIZE) glEnable(GL_LIGHTING) glEnable(GL_DEPTH_TEST) def handleMouseStuff(self, x, y): #Invert y from what the window manager says y = self.size.height - y self.MousePos = [x, y] def MouseDown(self, evt): x, y = evt.GetPosition() self.CaptureMouse() self.handleMouseStuff(x, y) self.Refresh() def MouseUp(self, evt): x, y = evt.GetPosition() self.handleMouseStuff(x, y) self.ReleaseMouse() self.Refresh() def MouseMotion(self, evt): x, y = evt.GetPosition() [lastX, lastY] = self.MousePos self.handleMouseStuff(x, y) dX = self.MousePos[0] - lastX dY = self.MousePos[1] - lastY if evt.Dragging(): if evt.MiddleIsDown(): self.camera.translate(dX, dY) elif evt.RightIsDown(): self.camera.zoom(-dY) #Want to zoom in as the mouse goes up elif evt.LeftIsDown(): self.camera.orbitLeftRight(dX) self.camera.orbitUpDown(dY) self.Refresh()
class MeshViewerCanvas(glcanvas.GLCanvas): def __init__(self, parent): attribs = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24) glcanvas.GLCanvas.__init__(self, parent, -1, attribList=attribs) self.context = glcanvas.GLContext(self) self.parent = parent #Camera state variables self.size = self.GetClientSize() self.camera = MousePolarCamera(self.size.x, self.size.y) #self.camera = MousePolarCamera(self.size.width, self.size.height) #Main state variables self.MousePos = [0, 0] self.initiallyResized = False self.bbox = BBox3D() self.unionbbox = BBox3D() random.seed() #State variables for saving screenshots self.filepath = None self.rotfilePrefix = "Rotation" self.rotstartAngle = -50 self.rotendAngle = 50 self.rotangleInc = 5 self.rotAngle = 0 self.zCenter = 0 #Face mesh variables and manipulation variables self.mesh = None self.meshCentroid = None self.meshPrincipalAxes = None self.displayMeshFaces = True self.displayMeshEdges = False self.displayMeshVertices = False self.displayMeshNormals = False self.displayPrincipalAxes = False self.useLighting = False self.useTexture = True self.vertexColors = np.zeros(0) self.GLinitialized = False #GL-related events wx.EVT_ERASE_BACKGROUND(self, self.processEraseBackgroundEvent) wx.EVT_SIZE(self, self.processSizeEvent) wx.EVT_PAINT(self, self.processPaintEvent) #Mouse Events wx.EVT_LEFT_DOWN(self, self.MouseDown) wx.EVT_LEFT_UP(self, self.MouseUp) wx.EVT_RIGHT_DOWN(self, self.MouseDown) wx.EVT_RIGHT_UP(self, self.MouseUp) wx.EVT_MIDDLE_DOWN(self, self.MouseDown) wx.EVT_MIDDLE_UP(self, self.MouseUp) wx.EVT_MOTION(self, self.MouseMotion) #self.initGL() def LoadMesh(self): self.mesh = LaplacianMesh() self.mesh.loadFile(self.filepath) print "Finished loading mesh\n %s" % self.mesh print self.mesh self.bbox = self.mesh.getBBox() self.camera.centerOnBBox(self.bbox, theta=-math.pi / 2, phi=math.pi / 2) self.zCenter = (self.bbox.zmax + self.bbox.zmin) / 2.0 self.Refresh() def processEraseBackgroundEvent(self, event): pass #avoid flashing on MSW. def processSizeEvent(self, event): self.size = self.GetClientSize() self.SetCurrent(self.context) glViewport(0, 0, self.size.width, self.size.height) if not self.initiallyResized: #The canvas gets resized once on initialization so the camera needs #to be updated accordingly at that point self.camera = MousePolarCamera(self.size.width, self.size.height) self.camera.centerOnBBox(self.bbox, -math.pi / 2, math.pi / 2) self.initiallyResized = True def processPaintEvent(self, event): dc = wx.PaintDC(self) self.SetCurrent(self.context) if not self.GLinitialized: self.initGL() self.GLinitialized = True self.repaint() def repaint(self): if not self.mesh: self.LoadMesh() return #Set up projection matrix glMatrixMode(GL_PROJECTION) glLoadIdentity() farDist = (self.camera.eye - self.bbox.getCenter()).Length() * 2 #This is to make sure we can see on the inside farDist = max(farDist, self.unionbbox.getDiagLength() * 2) nearDist = farDist / 50.0 gluPerspective(180.0 * self.camera.yfov / M_PI, float(self.size.x) / self.size.y, nearDist, farDist) #Set up modelview matrix self.camera.gotoCameraFrame() glClearColor(0.0, 0.0, 0.0, 0.0) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLightfv(GL_LIGHT0, GL_POSITION, [3.0, 4.0, 5.0, 0.0]) glLightfv(GL_LIGHT1, GL_POSITION, [-3.0, -2.0, -3.0, 0.0]) glEnable(GL_LIGHTING) glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, [0.8, 0.8, 0.8, 1.0]) glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [0.2, 0.2, 0.2, 1.0]) glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, 64) glTranslatef(0, 0, self.zCenter) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) self.mesh.renderGL(self.displayMeshEdges, self.displayMeshVertices, self.displayMeshNormals, self.displayMeshFaces, self.useLighting, self.useTexture) saveImageGL(self, "%s%i.png" % (self.rotfilePrefix, self.rotAngle)) self.rotAngle = self.rotAngle + self.rotangleInc if self.rotAngle > self.rotendAngle: exit(0) self.SwapBuffers() self.Refresh() self.SwapBuffers() def initGL(self): glLightModelfv(GL_LIGHT_MODEL_AMBIENT, [0.2, 0.2, 0.2, 1.0]) glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE) glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1.0]) glEnable(GL_LIGHT0) glLightfv(GL_LIGHT1, GL_DIFFUSE, [0.5, 0.5, 0.5, 1.0]) glEnable(GL_LIGHT1) glEnable(GL_NORMALIZE) glEnable(GL_LIGHTING) glEnable(GL_DEPTH_TEST) def handleMouseStuff(self, x, y): #Invert y from what the window manager says y = self.size.height - y self.MousePos = [x, y] def MouseDown(self, evt): state = wx.GetMouseState() x, y = evt.GetPosition() self.CaptureMouse() self.handleMouseStuff(x, y) self.Refresh() def MouseUp(self, evt): x, y = evt.GetPosition() self.handleMouseStuff(x, y) self.ReleaseMouse() self.Refresh() def MouseMotion(self, evt): state = wx.GetMouseState() x, y = evt.GetPosition() [lastX, lastY] = self.MousePos self.handleMouseStuff(x, y) dX = self.MousePos[0] - lastX dY = self.MousePos[1] - lastY self.Refresh()
class MeshViewerCanvas(glcanvas.GLCanvas): def __init__(self, parent): attribs = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24) glcanvas.GLCanvas.__init__(self, parent, -1, attribList = attribs) self.context = glcanvas.GLContext(self) self.parent = parent #Camera state variables self.size = self.GetClientSize() self.camera = MousePolarCamera(self.size.x, self.size.y) #self.camera = MousePolarCamera(self.size.width, self.size.height) #Main state variables self.MousePos = [0, 0] self.initiallyResized = False self.bbox = BBox3D() self.unionbbox = BBox3D() random.seed() #State variables for saving screenshots self.filepath = None self.rotfilePrefix = "Rotation" self.rotstartAngle = -50 self.rotendAngle = 50 self.rotangleInc = 5 self.rotAngle = 0 self.zCenter = 0 #Face mesh variables and manipulation variables self.mesh = None self.meshCentroid = None self.meshPrincipalAxes = None self.displayMeshFaces = True self.displayMeshEdges = False self.displayMeshVertices = False self.displayMeshNormals = False self.displayPrincipalAxes = False self.useLighting = False self.useTexture = True self.vertexColors = np.zeros(0) self.GLinitialized = False #GL-related events wx.EVT_ERASE_BACKGROUND(self, self.processEraseBackgroundEvent) wx.EVT_SIZE(self, self.processSizeEvent) wx.EVT_PAINT(self, self.processPaintEvent) #Mouse Events wx.EVT_LEFT_DOWN(self, self.MouseDown) wx.EVT_LEFT_UP(self, self.MouseUp) wx.EVT_RIGHT_DOWN(self, self.MouseDown) wx.EVT_RIGHT_UP(self, self.MouseUp) wx.EVT_MIDDLE_DOWN(self, self.MouseDown) wx.EVT_MIDDLE_UP(self, self.MouseUp) wx.EVT_MOTION(self, self.MouseMotion) #self.initGL() def LoadMesh(self): self.mesh = LaplacianMesh() self.mesh.loadFile(self.filepath) print "Finished loading mesh\n %s"%self.mesh print self.mesh self.bbox = self.mesh.getBBox() self.camera.centerOnBBox(self.bbox, theta = -math.pi/2, phi = math.pi/2) self.zCenter = (self.bbox.zmax + self.bbox.zmin) / 2.0 self.Refresh() def processEraseBackgroundEvent(self, event): pass #avoid flashing on MSW. def processSizeEvent(self, event): self.size = self.GetClientSize() self.SetCurrent(self.context) glViewport(0, 0, self.size.width, self.size.height) if not self.initiallyResized: #The canvas gets resized once on initialization so the camera needs #to be updated accordingly at that point self.camera = MousePolarCamera(self.size.width, self.size.height) self.camera.centerOnBBox(self.bbox, -math.pi/2, math.pi/2) self.initiallyResized = True def processPaintEvent(self, event): dc = wx.PaintDC(self) self.SetCurrent(self.context) if not self.GLinitialized: self.initGL() self.GLinitialized = True self.repaint() def repaint(self): if not self.mesh: self.LoadMesh() return #Set up projection matrix glMatrixMode(GL_PROJECTION) glLoadIdentity() farDist = (self.camera.eye - self.bbox.getCenter()).Length()*2 #This is to make sure we can see on the inside farDist = max(farDist, self.unionbbox.getDiagLength()*2) nearDist = farDist/50.0 gluPerspective(180.0*self.camera.yfov/M_PI, float(self.size.x)/self.size.y, nearDist, farDist) #Set up modelview matrix self.camera.gotoCameraFrame() glClearColor(0.0, 0.0, 0.0, 0.0) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLightfv(GL_LIGHT0, GL_POSITION, [3.0, 4.0, 5.0, 0.0]); glLightfv(GL_LIGHT1, GL_POSITION, [-3.0, -2.0, -3.0, 0.0]); glEnable(GL_LIGHTING) glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, [0.8, 0.8, 0.8, 1.0]); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [0.2, 0.2, 0.2, 1.0]) glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, 64) glTranslatef(0, 0, self.zCenter) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) self.mesh.renderGL(self.displayMeshEdges, self.displayMeshVertices, self.displayMeshNormals, self.displayMeshFaces, self.useLighting, self.useTexture) saveImageGL(self, "%s%i.png"%(self.rotfilePrefix, self.rotAngle)) self.rotAngle = self.rotAngle + self.rotangleInc if self.rotAngle > self.rotendAngle: exit(0) self.SwapBuffers() self.Refresh() self.SwapBuffers() def initGL(self): glLightModelfv(GL_LIGHT_MODEL_AMBIENT, [0.2, 0.2, 0.2, 1.0]) glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE) glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1.0]) glEnable(GL_LIGHT0) glLightfv(GL_LIGHT1, GL_DIFFUSE, [0.5, 0.5, 0.5, 1.0]) glEnable(GL_LIGHT1) glEnable(GL_NORMALIZE) glEnable(GL_LIGHTING) glEnable(GL_DEPTH_TEST) def handleMouseStuff(self, x, y): #Invert y from what the window manager says y = self.size.height - y self.MousePos = [x, y] def MouseDown(self, evt): state = wx.GetMouseState() x, y = evt.GetPosition() self.CaptureMouse() self.handleMouseStuff(x, y) self.Refresh() def MouseUp(self, evt): x, y = evt.GetPosition() self.handleMouseStuff(x, y) self.ReleaseMouse() self.Refresh() def MouseMotion(self, evt): state = wx.GetMouseState() x, y = evt.GetPosition() [lastX, lastY] = self.MousePos self.handleMouseStuff(x, y) dX = self.MousePos[0] - lastX dY = self.MousePos[1] - lastY self.Refresh()
class MeshViewerCanvas(glcanvas.GLCanvas): def __init__(self, parent): attribs = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24) glcanvas.GLCanvas.__init__(self, parent, -1, attribList = attribs) self.context = glcanvas.GLContext(self) self.setText = False self.parent = parent #Camera state variables self.size = self.GetClientSize() #self.camera = MouseSphericalCamera(self.size.x, self.size.y) self.camera = MousePolarCamera(self.size.width, self.size.height) #Main state variables self.MousePos = [0, 0] self.initiallyResized = False random.seed() #GUI State Variables self.GUIState = STATE_INTRO self.GUISubstate = -1 #Head Mesh self.headMesh = PolyMesh() self.headMesh.loadFile('NotreDameMedium.off') self.headMeshLowres = PolyMesh() self.headMeshLowres.loadFile('NotreDameLowres.off') self.styrofoamHead = PolyMesh() self.styrofoamHead.loadFile('StyrofoamHead.off') self.rotAngle = 0 self.zoom = 0 self.rotCount = 0 self.timelineCount = 0 #User's face self.userMesh = None self.colorUserMesh = None #ICP state variables self.ICPTransformation = np.zeros(0) self.ICPMutex = Lock() self.ICPThread = None self.bbox = self.headMesh.getBBox() self.camera.centerOnBBox(self.bbox, theta = -math.pi/2, phi = math.pi/2) self.zCenter = (self.bbox.zmax + self.bbox.zmin) / 2.0 self.GLinitialized = False #GL-related events wx.EVT_ERASE_BACKGROUND(self, self.processEraseBackgroundEvent) wx.EVT_SIZE(self, self.processSizeEvent) wx.EVT_PAINT(self, self.processPaintEvent) #Mouse Events wx.EVT_LEFT_DOWN(self, self.MouseDown) wx.EVT_LEFT_UP(self, self.MouseUp) wx.EVT_RIGHT_DOWN(self, self.MouseDown) wx.EVT_RIGHT_UP(self, self.MouseUp) wx.EVT_MIDDLE_DOWN(self, self.MouseDown) wx.EVT_MIDDLE_UP(self, self.MouseUp) wx.EVT_MOTION(self, self.MouseMotion) self.initGL() def drawText(self, x, y, text, size = 1, font = GLUT_BITMAP_TIMES_ROMAN_24): glDisable(GL_LIGHTING) glDisable(GL_DEPTH_TEST) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluOrtho2D(0, self.size[0], 0, self.size[1]) glMatrixMode(GL_MODELVIEW) glLoadIdentity() glColor3f(1, 0, 0) glRasterPos2f(x, y) glPushMatrix() #for i in range(len(text)): # glutBitmapCharacter(font, ord(text[i])) glPopMatrix() glEnable(GL_LIGHTING) glEnable(GL_DEPTH_TEST) def startButtonHandler(self, evt): print "Starting Face capture..." if not self.setText: self.titleText.SetLabelText("Capturing Face...") self.setText = True #os.popen3("SingleFace.exe") #Captures the face process = subprocess.Popen("SingleFace", shell=True, stdout=subprocess.PIPE) process.wait() print "FINISHED CAPTURE" extractMeshFiles() #Convert the captured data to a triangulated mesh self.userMesh = LaplacianMesh() self.userMesh.loadFile("out.off") self.bbox = self.userMesh.getBBox() self.camera.centerOnBBox(self.bbox, theta = -math.pi/2, phi = math.pi/2) self.GUIState = STATE_SHOWPOINTS self.GUISubstate = SHOWPOINTS_ZOOMIN self.zoom = 0 self.setText = False self.repaint() def processEraseBackgroundEvent(self, event): pass #avoid flashing on MSW. def processSizeEvent(self, event): self.size = self.GetClientSize() self.SetCurrent(self.context) glViewport(0, 0, self.size.width, self.size.height) if not self.initiallyResized: #The canvas gets resized once on initialization so the camera needs #to be updated accordingly at that point self.camera = MousePolarCamera(self.size.width, self.size.height, ) self.camera.centerOnBBox(self.bbox, theta = -math.pi/2, phi = math.pi/2) self.initiallyResized = True def processPaintEvent(self, event): dc = wx.PaintDC(self) self.SetCurrent(self.context) if not self.GLinitialized: self.initGL() self.GLinitialized = True self.repaint() def handleIntroState(self): #Draw head glTranslatef(0, 0, self.zCenter) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) self.headMesh.renderGL() self.rotAngle = self.rotAngle + 1 self.rotAngle = self.rotAngle % 360 #self.drawText(self.size[0]/2-50, self.size[1]-40, "A Head of Our Times") if not self.setText: self.titleText.SetLabelText("A Head of Our Times") self.startButton.SetLabelText("Click To Start") self.setText = True time.sleep(0.01) self.Refresh() def handleShowPointsState(self): if self.GUISubstate == SHOWPOINTS_ZOOMIN: if not self.setText: self.titleText.SetLabelText("Showing 3D Face Capture Result") glTranslatef(0, 0, self.zoom) self.userMesh.renderGL(drawEdges = 1, drawVerts = 1, drawNormals = 0, drawFaces = 0) glTranslatef(0, 0, -self.zoom) self.zoom = self.zoom + 0.005 time.sleep(0.01) if self.zoom >= abs(self.zCenter/6): self.GUISubstate = SHOWPOINTS_ROTATELEFT self.rotAngle = 0 self.rotCount = 0 elif self.GUISubstate == SHOWPOINTS_ROTATELEFT: self.setText = True glTranslatef(0, 0, self.zCenter + self.zoom) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) if self.rotCount == 0: self.userMesh.renderGL(drawEdges = 1, drawVerts = 1, drawNormals = 0, drawFaces = 0) else: self.userMesh.renderGL(drawEdges = 0, drawVerts = 0, drawNormals = 0, drawFaces = 1, lightingOn = False) self.rotAngle = self.rotAngle - 1 if self.rotAngle < -60: self.GUISubstate = SHOWPOINTS_ROTATERIGHT time.sleep(0.01) elif self.GUISubstate == SHOWPOINTS_ROTATERIGHT: glTranslatef(0, 0, self.zCenter + self.zoom) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) if self.rotCount == 0: self.userMesh.renderGL(drawEdges = 1, drawVerts = 1, drawNormals = 0, drawFaces = 0) else: self.userMesh.renderGL(drawEdges = 0, drawVerts = 0, drawNormals = 0, drawFaces = 1, lightingOn = False) self.rotAngle = self.rotAngle + 1 if self.rotAngle > 60: self.rotCount = self.rotCount + 1 if self.rotCount >= 2: self.GUISubstate = SHOWPOINTS_CENTER else: self.GUISubstate = SHOWPOINTS_ROTATELEFT time.sleep(0.01) elif self.GUISubstate == SHOWPOINTS_CENTER: glTranslatef(0, 0, self.zCenter + self.zoom) glRotatef(self.rotAngle, 0, 1, 0) glTranslatef(0, 0, -self.zCenter) self.userMesh.renderGL(drawEdges = 0, drawVerts = 0, drawNormals = 0, drawFaces = 1, lightingOn = False) self.rotAngle = self.rotAngle - 1 if self.rotAngle <= 0: self.GUISubstate = SHOWPOINTS_ZOOMOUT time.sleep(0.01) elif self.GUISubstate == SHOWPOINTS_ZOOMOUT: glTranslatef(0, 0, self.zoom) self.userMesh.renderGL(drawEdges = 0, drawVerts = 0, drawNormals = 0, drawFaces = 1, lightingOn = False) glTranslatef(0, 0, -self.zoom) self.zoom = self.zoom - 0.005 time.sleep(0.01) if self.zoom <= 0: self.GUIState = STATE_SHOWICP self.setText = False self.bbox = self.styrofoamHead.getBBox() self.camera.centerOnBBox(self.bbox, theta = -math.pi/2, phi = math.pi/2) #Get the mesh's renderbuffer ready before the thread starts so that no ICP frames are missed self.styrofoamHead.renderGL(drawEdges = 0, drawVerts = 0, drawNormals = 0, drawFaces = 1) self.ICPThread = ICPThread(self.userMesh, self.styrofoamHead, self, self.ICPMutex) self.ICPThread.start() self.drawText(self.size[0]/2-50, self.size[1]-40, "Showing Captured Face") self.Refresh() def transplantColors(self): perms = np.random.permutation(len(self.userMesh.vertices)) N = min(1000, len(perms)) VX = np.zeros((N, 3)) CX = np.zeros((N, 3)) #Randomly subsample points for speed for i in range(N): idx = perms[i] P = self.userMesh.vertices[idx].pos C = self.userMesh.vertices[idx].color VX[i, :] = np.array([P.x, P.y, P.z]) CX[i, :] = C VX = transformPoints(self.ICPTransformation, VX) self.titleText.SetLabelText("Placing Colors on Head") print "Getting initial guess of point positions..." ts, us = getInitialGuessClosestPoints(VX, self.styrofoamHead) print "Finished initial guess of point positions" self.colorUserMesh = transplantColorsLaplacianUsingBarycentric(self.styrofoamHead, CX, ts, us) def handleICPState(self): glPushMatrix() if self.ICPTransformation.shape[0] > 0: glMultMatrixd(self.ICPTransformation.transpose().flatten()) self.userMesh.renderGL(drawEdges = 0, drawVerts = 0, drawNormals = 0, drawFaces = 1, lightingOn = False) glPopMatrix() glColor3f(0.7, 0.7, 0.7) self.styrofoamHead.renderGL(drawEdges = 0, drawVerts = 0, drawNormals = 0, drawFaces = 1, lightingOn = True) #self.drawText(self.size[0]/2-50, self.size[1]-40, "Aligning Face with Statue...") if not self.setText: self.titleText.SetLabelText("Performing Rigid Alignment to Statue") self.setText = True if not self.ICPThread.isAlive(): print "Alignment finished" print "Transplanting colors..." self.transplantColors() print "Finished Transplanting colors" self.setText = False self.GUIState = STATE_DECAY self.timelineState = 0 self.bbox = self.colorUserMesh.getBBox() self.camera.centerOnBBox(self.bbox, theta = -math.pi/2, phi = math.pi/2) def handleDecayState(self): if self.timelineState > 40: #Render the head in its current form self.colorUserMesh.renderGL(drawEdges = 0, drawVerts = 0, drawNormals = 0, drawFaces = 1, lightingOn = True) if self.timelineState == 0: titleFont = wx.Font(24, wx.FONTFAMILY_DECORATIVE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) self.titleText.SetFont(titleFont) if self.timelineState <= 20: #TODO: Include Images here self.titleText.SetLabelText("1180 - 1223: Paris becomes capital of French Kingdom") time.sleep(0.05) self.Refresh() elif self.timelineState <= 40: self.titleText.SetLabelText("1163: Construction of Notre Dame Cathedral Begins") time.sleep(0.05) self.Refresh() elif self.timelineState <= 60: self.titleText.SetLabelText("1245 - 1258: Remodling of North transept and addition of the Virtue") time.sleep(0.1) elif self.timelineState < 120: year = int(np.round((self.timelineState - 60)*(1793 - 1258)/60.0 + 1258)) self.titleText.SetLabelText("%i: Virtue in Situ on Cathedral Facade"%year) #Dampen the colors for v in self.colorUserMesh.vertices: C = v.color meanC = sum(C)/float(len(C)) v.color = [c - (c - meanC)*0.025 for c in C] self.colorUserMesh.needsDisplayUpdate = True else: if self.timelineState == 121: self.titleText.SetLabelText("1793: French Revolution and the Iconoclasm/Vandalism") #TODO: Rotate head slightly to left, fix chunks if self.timelineState == 120: USINGLAPLACIAN = False self.titleText.SetLabelText("1793: French Revolution Iconoclasm: Decapitation Imminent...") #Make some dents perms = np.random.permutation(len(self.colorUserMesh.vertices)) print "CHUNKING" constraints = [] chunks = 0 for i in range(0, 20): idx = perms[i] V = self.colorUserMesh.vertices[idx] if USINGLAPLACIAN: N = V.getNormal() #if np.abs(N.z) < 0.8: # continue chunks = chunks + 1 chunkSize = 0.05*np.random.rand(1)[0] P = V.pos - chunkSize*Vector3D(0, 0, -1) constraints.append((idx, P)) otherVerts = V.getVertexNeighbors() for V2 in otherVerts: P = V2.pos - chunkSize*0.5*Vector3D(0, 0, -1) constraints.append((V2.ID, P)) else: P = V.pos randDiff = 0.02*np.random.rand(1)[0] P = P - randDiff*V.getNormal() V.pos = P for V2 in V.getVertexNeighbors(): P = V2.pos - 0.5*randDiff*V2.getNormal() V2.pos = P if USINGLAPLACIAN: self.colorUserMesh.solveVertexPositionsWithConstraints(constraints) self.colorUserMesh.needsDisplayUpdate = True print "There were %i chunks"%chunks self.timelineState = self.timelineState + 1 self.Refresh() def handleShowStretchState(self): #TODO: Finish this self.GUIState = STATE_SHOWSTRETCH self.Refresh() def repaint(self): #Set up projection matrix glMatrixMode(GL_PROJECTION) glLoadIdentity() farDist = (self.camera.eye - self.bbox.getCenter()).Length()*2 #This is to make sure we can see on the inside farDist = max(farDist, self.bbox.getDiagLength()*2) nearDist = farDist/50.0 gluPerspective(180.0*self.camera.yfov/M_PI, float(self.size.x)/self.size.y, nearDist, farDist) #Set up modelview matrix self.camera.gotoCameraFrame() glClearColor(0.0, 0.0, 0.0, 0.0) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLightfv(GL_LIGHT0, GL_POSITION, [3.0, 4.0, 5.0, 0.0]); glLightfv(GL_LIGHT1, GL_POSITION, [-3.0, -2.0, -3.0, 0.0]); glEnable(GL_LIGHTING) glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, [0.8, 0.8, 0.8, 1.0]); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [0.2, 0.2, 0.2, 1.0]) glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, 64) self.zCenter = (self.bbox.zmax + self.bbox.zmin) / 2.0 if self.GUIState == STATE_INTRO: self.handleIntroState() elif self.GUIState == STATE_SHOWPOINTS: self.handleShowPointsState() elif self.GUIState == STATE_SHOWICP: self.handleICPState() elif self.GUIState == STATE_DECAY: self.handleDecayState() self.SwapBuffers() def initGL(self): glLightModelfv(GL_LIGHT_MODEL_AMBIENT, [0.2, 0.2, 0.2, 1.0]) glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE) glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1.0]) glEnable(GL_LIGHT0) glLightfv(GL_LIGHT1, GL_DIFFUSE, [0.5, 0.5, 0.5, 1.0]) glEnable(GL_LIGHT1) glEnable(GL_NORMALIZE) glEnable(GL_LIGHTING) glEnable(GL_DEPTH_TEST) def handleMouseStuff(self, x, y): #Invert y from what the window manager says y = self.size.height - y self.MousePos = [x, y] def MouseDown(self, evt): x, y = evt.GetPosition() self.CaptureMouse() self.handleMouseStuff(x, y) self.Refresh() def MouseUp(self, evt): x, y = evt.GetPosition() self.handleMouseStuff(x, y) self.ReleaseMouse() self.Refresh() def MouseMotion(self, evt): x, y = evt.GetPosition() [lastX, lastY] = self.MousePos self.handleMouseStuff(x, y) dX = self.MousePos[0] - lastX dY = self.MousePos[1] - lastY if evt.Dragging(): if evt.MiddleIsDown(): self.camera.translate(dX, dY) elif evt.RightIsDown(): self.camera.zoom(-dY)#Want to zoom in as the mouse goes up elif evt.LeftIsDown(): self.camera.orbitLeftRight(dX) self.camera.orbitUpDown(dY) self.Refresh()