#!/usr/bin/env python # # TODO: watch for for rotation mode being changed during an autospin # TODO: when rotating, show the sphere; controlled by an option, of course # TODO: the demo should perhaps allow switching to the default OpenGL.Tk # rotation method from OpenGL.quaternion import * from OpenGL.Tk import * import Tkinter def win_to_sph(pnt_win, sph_center, radius): pnt = [pnt_win[0]-sph_center[0], pnt_win[1]-sph_center[1], 0] for i in range(3): pnt[i] /= radius r = pnt[0]**2 + pnt[1]**2 if r > 1: pnt[0] /= math.sqrt(r) pnt[1] /= math.sqrt(r) pnt[2] = 0 else: pnt[2] = math.sqrt(1 - r) return pnt def cross_product(x, y): return [x[1]*y[2] - x[2]*y[1], x[2]*y[0] - x[0]*y[2], x[0]*y[1] - x[1]*y[0]]; def dot_product(x, y): def do_elem(s,e): return s+e[0]*e[1] return reduce(do_elem, map(None,x,y), 0) def arcball(S, T, sph_center, radius): s = win_to_sph(S, sph_center, radius) t = win_to_sph(T, sph_center, radius) xp = cross_product(s,t) q = quaternion(xp[0], xp[1], xp[2]) q[3] = dot_product(s,t) return q # converts a quaternion to a rotation matrix def quat_to_rot_mtx(q): mat = [0]*16 x, y, z, w = q xx = 2*x*x xy = 2*x*y xz = 2*x*z xw = 2*x*w yy = 2*y*y yz = 2*y*z yw = 2*y*w zz = 2*z*z zw = 2*z*w mat[0] = 1 - (yy+zz) mat[4] = xy-zw mat[8] = xz+yw mat[1] = xy+zw mat[5] = 1 - (xx+zz) mat[9] = yz-xw mat[2] = xz-yw mat[6] = yz+xw mat[10] = 1 - (xx+yy) mat[15] = 1 return mat class OpenglArcball(Opengl): def __init__(self, master, cnf={}, **kw): #Opengl.__init__(self, master, cnf, kw) apply(Opengl.__init__, (self, master, cnf), kw) self.bind('', self.tkRotate) self.arcball_on = 1 def use_arcball(self, val): self.arcball_on = val def tkRotate(self, event): if self.arcball_on: self.activate() self.arcball_rot(event.x, event.y, self.xmouse, self.ymouse) self.tkRedraw() self.tkRecordMouse(event) else: Opengl.tkRotate(self, event) def arcball_rot(self, x, y, mousex, mousey): q = self.get_rot_quat(x, y, mousex, mousey) self.do_rot(q) def do_rot(self, q): glMatrixMode(GL_MODELVIEW) mat = glGetDoublev(GL_MODELVIEW_MATRIX) glLoadIdentity() glTranslatef(self.xcenter, self.ycenter, self.zcenter) mat2 = quat_to_rot_mtx(q) glMultMatrixd(mat2) glTranslatef(-self.xcenter, -self.ycenter, -self.zcenter) glMultMatrixd(mat) def get_rot_quat(self, newx, newy, oldx, oldy): w = self.winfo_width() h = self.winfo_height() sph_center = [w/2.0, h/2.0] radius = min(sph_center) # `y' in window coords is upside down (origin in upper left, # not cartesian lower left), so flip it newy = h - newy -1 oldy = h - oldy -1 q = arcball([oldx, oldy], [newx, newy], sph_center, radius) q.normalize() return q def tkAutoSpin(self, event): """Perform autospin of scene.""" if self.arcball_on: self.after(4) self.update_idletasks() x = self.winfo_pointerx() - self.winfo_rootx() y = self.winfo_pointery() - self.winfo_rooty() if self.autospin_allowed: if x != event.x and y != event.y: self.autospin = 1 self.autospin_q = self.get_rot_quat(x, y, event.x, event.y) self.after(10, self.do_AutoSpin) def do_AutoSpin(self): if self.arcball_on: self.activate() self.do_rot(self.autospin_q) self.tkRedraw() if self.autospin: self.after(10, self.do_AutoSpin) else: Opengl.do_AutoSpin(self) if __name__ == '__main__': # simple test o = OpenglArcball(Tkinter._default_root, width=200, height=200, double=1) Tkinter._default_root.wm_title("Arcball demo") print "Press 'q' to quit." o.pack(side='top') o.set_background(0,0,0) o.set_eyepoint(5) def redraw(self): glDisable(GL_LIGHTING) glLineWidth(3) glBegin(GL_LINES) glColor3f(1,0,0) glVertex3f(0,0,0) glVertex3f(1,0,0) glColor3f(0,1,0) glVertex3f(0,0,0) glVertex3f(0,1,0) glColor3f(0,0,1) glVertex3f(0,0,0) glVertex3f(0,0,1) glEnd() o.redraw = redraw o.bind_all('q', lambda dummy: sys.exit(0)) o.tk.mainloop()