描画した図形をマウスで操作する(9)
PythonのOpenGLライブラリを用いて、3次元図形の描画を行う練習をしている。
マウスで物体を回転したり平行移動したりしたかったのだが、回転と平行移動を組み合わせるとうまくいかない。そこで、とりあえずは平行移動をあきらめて、現状のコードをまとめる。
- このコードについて
- 外部ファイルに頂点の座標を定義しており、それを読み込んで物体を描画している。
- wxPythonのwxglcanvasモジュールを利用している。
- 描いた物体をマウスで回転、スケールの操作ができる。回転は左ボタンを押しながらドラッグする。スケールはマウスホイールを回すことで実現できる。
- 物体の配置を初期の位置に戻すには、キーボードのxキー(小文字)を押す。
現状でベストなコードは以下の通り。
# practice6mod.py import wx import sys import math from wx import glcanvas from OpenGL.GL import * from OpenGL.GLUT import * from OpenGL.GLU import * ESCAPE = 27 XKEY = 120 class ButtonPanel(wx.Panel): def __init__(self, parent, log): wx.Panel.__init__(self, parent, -1) self.log = log box = wx.BoxSizer(wx.VERTICAL) box.Add((20, 30)) btn = wx.Button(self, 0, 'My') box.Add(btn, 0, wx.ALIGN_CENTER|wx.ALL, 15) self.Bind(wx.EVT_BUTTON, self.OnButton, btn) self.SetAutoLayout(True) self.SetSizer(box) def OnButton(self, evt): canvasClass = eval('MyCanvas') frame = wx.Frame(None, -1, 'OpenGL', size=(400,400)) canvas = canvasClass(frame) frame.Show(True) class MyCanvasBase(glcanvas.GLCanvas): def __init__(self, parent): glcanvas.GLCanvas.__init__(self, parent, -1) self.init = False self.lastx = self.x = 0 self.lasty = self.y = 0 self.tranx = self.trany = 0 self.zooming = 1.0 self.size = None self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) self.Bind(wx.EVT_LEFT_UP, self.OnMouseUp) self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseDown) self.Bind(wx.EVT_RIGHT_UP, self.OnMouseUp) self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMouseDown) self.Bind(wx.EVT_MIDDLE_UP, self.OnMouseUp) self.Bind(wx.EVT_MOTION, self.OnMouseMotion) self.Bind(wx.EVT_CHAR, self.OnKeyboard) # self.Bind(wx.EVT_IDLE, self.OnIdle) self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) def OnEraseBackground(self, event): pass def OnSize(self, event): size = self.size = self.GetClientSize() if self.GetContext(): self.SetCurrent() glViewport(0, 0, size.width, size.height) event.Skip() def OnPaint(self, event): dc = wx.PaintDC(self) self.SetCurrent() if not self.init: self.InitGL() self.init = True self.OnDraw() def OnMouseDown(self, evt): self.CaptureMouse() self.x, self.y = self.lastx, self.lasty = evt.GetPosition() def OnMouseUp(self, evt): self.ReleaseMouse() def OnMouseMotion(self, evt): if evt.Dragging() and evt.LeftIsDown(): self.lastx, self.lasty = self.x, self.y self.x, self.y = evt.GetPosition() self.Refresh(False) elif evt.Dragging() and evt.RightIsDown(): lastx, lasty = self.x, self.y x, y = evt.GetPosition() self.tranx = 0.001*(x - lastx) self.trany = 0.001*(lasty - y) self.Refresh(False) def OnMouseWheel(self, evt): if evt.GetWheelRotation() > 0: self.zooming -= 0.1 self.Refresh(False) else: self.zooming += 0.1 self.Refresh(False) def OnKeyboard(self, evt): if evt.KeyCode == ESCAPE: sys.exit() elif evt.KeyCode == XKEY: self.x = self.y = 0 self.lastx = self.lasty = 0 self.tranx = self.trany = 0 self.InitGL() self.Refresh(True) class MyCanvas(MyCanvasBase): def InitGL(self): glClearDepth(1.0) glEnable(GL_DEPTH_TEST) glClearColor(0.0, 0.5, 0.0, 0.0) glShadeModel(GL_SMOOTH) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(30.0, 1.0, 1.0, 10.0) glMatrixMode(GL_MODELVIEW) glLoadIdentity() gluLookAt( 0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 ) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) # glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE) # glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ) # glEnable(GL_COLOR_MATERIAL) self.vpos = list() self.readpos() self.displaylist() def readpos(self): fp = open('coordpos.txt', 'r') num = int(fp.readline()) poss = fp.read().splitlines() for i in xrange(num): self.vpos.append([float(x) for x in poss[i].split(',')]) fp.close() def crossproduct(self, a, b, c): cross = [0.0, 0.0, 0.0] cross[0] = (a[1]-b[1])*(c[2]-b[2]) - (a[2]-b[2])*(c[1]-b[1]) cross[1] = (a[2]-b[2])*(c[0]-b[0]) - (a[0]-b[0])*(c[2]-b[2]) cross[2] = (a[0]-b[0])*(c[1]-b[1]) - (a[1]-b[1])*(c[0]-b[0]) cabs = math.sqrt(cross[0]**2+cross[1]**2+cross[2]**2) cc = [x/cabs for x in cross] return cc def displaylist(self): ps = self.vpos self.index = glGenLists(1) glNewList(self.index, GL_COMPILE_AND_EXECUTE) glBegin(GL_QUADS) glNormal3fv(self.crossproduct(ps[4],ps[1],ps[0])) glVertex3fv(ps[0]) glVertex3fv(ps[1]) glVertex3fv(ps[4]) glVertex3fv(ps[3]) # ... # 頂点の数が多いので、中略 # ... glNormal3fv(self.crossproduct(ps[25],ps[16],ps[15])) glVertex3fv(ps[15]) glVertex3fv(ps[16]) glVertex3fv(ps[25]) glVertex3fv(ps[24]) glEnd() glEndList() def OnDraw(self): if self.size is None: self.size = self.GetClientSize() w, h = self.size w = max(w, 1.0) h = max(h, 1.0) xScale = 180.0 / w yScale = 180.0 / h glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glPushMatrix() glCallList(self.index) glPopMatrix() glRotatef((self.y - self.lasty) * yScale, 1.0, 0.0, 0.0); glRotatef((self.x - self.lastx) * xScale, 0.0, 1.0, 0.0); # glTranslatef(self.tranx, self.trany, 0) glScalef(self.zooming, self.zooming, self.zooming) self.zooming = 1.0 self.SwapBuffers() def runTest(frame, nb, log): win = ButtonPanel(nb, log) return win if __name__ == '__main__': import sys,os import run run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])