PyGLWidget.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. # -*- coding: utf-8 -*-
  2. #===============================================================================
  3. #
  4. # PyGLWidget.py
  5. #
  6. # A simple GL Viewer.
  7. #
  8. # Copyright (c) 2011, Arne Schmitz <arne.schmitz@gmx.net>
  9. # All rights reserved.
  10. #
  11. # Redistribution and use in source and binary forms, with or without
  12. # modification, are permitted provided that the following conditions are met:
  13. # * Redistributions of source code must retain the above copyright
  14. # notice, this list of conditions and the following disclaimer.
  15. # * Redistributions in binary form must reproduce the above copyright
  16. # notice, this list of conditions and the following disclaimer in the
  17. # documentation and/or other materials provided with the distribution.
  18. # * Neither the name of the <organization> nor the
  19. # names of its contributors may be used to endorse or promote products
  20. # derived from this software without specific prior written permission.
  21. #
  22. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  23. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  24. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  25. # DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  26. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  27. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  28. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  29. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  30. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  31. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32. #
  33. #===============================================================================
  34. from PyQt5 import QtCore, QtGui, QtOpenGL
  35. import math
  36. import numpy
  37. import numpy.linalg as linalg
  38. import OpenGL
  39. OpenGL.ERROR_CHECKING = True
  40. from OpenGL.GL import *
  41. from OpenGL.GLU import *
  42. class PyGLWidget(QtOpenGL.QGLWidget):
  43. # Qt signals
  44. signalGLMatrixChanged = QtCore.pyqtSignal()
  45. rotationBeginEvent = QtCore.pyqtSignal()
  46. rotationEndEvent = QtCore.pyqtSignal()
  47. def __init__(self, parent = None):
  48. format = QtOpenGL.QGLFormat()
  49. format.setSampleBuffers(True)
  50. QtOpenGL.QGLWidget.__init__(self, format, parent)
  51. self.setCursor(QtCore.Qt.OpenHandCursor)
  52. self.setMouseTracking(True)
  53. self.modelview_matrix_ = [
  54. [1,0,0,0],
  55. [0,1,0,0],
  56. [0,0,1,0],
  57. [-10.53889084,-2.01535511,-25.95999146,1. ]]
  58. self.translate_vector_ = [0, 0, 0.0]
  59. self.viewport_matrix_ = []
  60. self.projection_matrix_ = []
  61. self.near_ = 0.1
  62. self.far_ = 100.0
  63. self.fovy_ = 45.0
  64. self.radius_ = 2.0
  65. self.last_point_2D_ = QtCore.QPoint()
  66. self.last_point_ok_ = False
  67. self.last_point_3D_ = [1.0, 0.0, 0.0]
  68. self.isInRotation_ = False
  69. # connections
  70. #self.signalGLMatrixChanged.connect(self.printModelViewMatrix)
  71. @QtCore.pyqtSlot()
  72. def printModelViewMatrix(self):
  73. print (self.modelview_matrix_)
  74. def initializeGL(self):
  75. # OpenGL state
  76. glClearColor(0.6, 0.6, 0.6, 0.5)
  77. glEnable(GL_DEPTH_TEST)
  78. self.reset_view()
  79. self.translate([0,11.5,-35.])
  80. def resizeGL(self, width, height):
  81. glViewport(0, 0, width, height );
  82. self.set_projection( self.near_, self.far_, self.fovy_ );
  83. self.updateGL()
  84. def paintGL(self):
  85. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
  86. glMatrixMode(GL_MODELVIEW)
  87. glLoadMatrixd(self.modelview_matrix_)
  88. #self.printModelViewMatrix()
  89. def set_projection(self, _near, _far, _fovy):
  90. self.near_ = _near
  91. self.far_ = _far
  92. self.fovy_ = _fovy
  93. self.makeCurrent()
  94. glMatrixMode( GL_PROJECTION )
  95. glLoadIdentity()
  96. gluPerspective( self.fovy_, float(self.width()) / float(self.height()),
  97. self.near_, self.far_ )
  98. self.updateGL()
  99. def set_center(self, _cog):
  100. self.center_ = _cog
  101. self.view_all()
  102. def set_radius(self, _radius):
  103. self.radius_ = _radius
  104. self.set_projection(_radius / 100.0, _radius * 100.0, self.fovy_)
  105. self.reset_view()
  106. self.translate([0, 0, -_radius * 2.0])
  107. self.view_all()
  108. self.updateGL()
  109. def reset_view(self):
  110. # scene pos and size
  111. glMatrixMode( GL_MODELVIEW )
  112. glLoadIdentity();
  113. self.modelview_matrix_ = glGetDoublev( GL_MODELVIEW_MATRIX )
  114. self.set_center([0.0, 0.0, 0.0])
  115. def reset_rotation(self):
  116. self.modelview_matrix_[0] = [1.0, 0.0, 0.0, 0.0]
  117. self.modelview_matrix_[1] = [0.0, 1.0, 0.0, 0.0]
  118. self.modelview_matrix_[2] = [0.0, 0.0, 1.0, 0.0]
  119. glMatrixMode(GL_MODELVIEW)
  120. glLoadMatrixd(self.modelview_matrix_)
  121. self.updateGL()
  122. def translate(self, _trans):
  123. # Translate the object by _trans
  124. # Update modelview_matrix_
  125. self.makeCurrent()
  126. glMatrixMode(GL_MODELVIEW)
  127. glLoadIdentity()
  128. glTranslated(_trans[0], _trans[1], _trans[2])
  129. glMultMatrixd(self.modelview_matrix_)
  130. self.modelview_matrix_ = glGetDoublev(GL_MODELVIEW_MATRIX)
  131. self.translate_vector_[0] = self.modelview_matrix_[3][0]
  132. self.translate_vector_[1] = self.modelview_matrix_[3][1]
  133. self.translate_vector_[2] = self.modelview_matrix_[3][2]
  134. self.signalGLMatrixChanged.emit()
  135. def rotate(self, _axis, _angle):
  136. t = [self.modelview_matrix_[0][0] * self.center_[0] +
  137. self.modelview_matrix_[1][0] * self.center_[1] +
  138. self.modelview_matrix_[2][0] * self.center_[2] +
  139. self.modelview_matrix_[3][0],
  140. self.modelview_matrix_[0][1] * self.center_[0] +
  141. self.modelview_matrix_[1][1] * self.center_[1] +
  142. self.modelview_matrix_[2][1] * self.center_[2] +
  143. self.modelview_matrix_[3][1],
  144. self.modelview_matrix_[0][2] * self.center_[0] +
  145. self.modelview_matrix_[1][2] * self.center_[1] +
  146. self.modelview_matrix_[2][2] * self.center_[2] +
  147. self.modelview_matrix_[3][2]]
  148. self.makeCurrent()
  149. glLoadIdentity()
  150. glTranslatef(t[0], t[1], t[2])
  151. glRotated(_angle, _axis[0], _axis[1], _axis[2])
  152. glTranslatef(-t[0], -t[1], -t[2])
  153. glMultMatrixd(self.modelview_matrix_)
  154. self.modelview_matrix_ = glGetDoublev(GL_MODELVIEW_MATRIX)
  155. self.signalGLMatrixChanged.emit()
  156. def view_all(self):
  157. self.translate( [ -( self.modelview_matrix_[0][0] * self.center_[0] +
  158. self.modelview_matrix_[0][1] * self.center_[1] +
  159. self.modelview_matrix_[0][2] * self.center_[2] +
  160. self.modelview_matrix_[0][3]),
  161. -( self.modelview_matrix_[1][0] * self.center_[0] +
  162. self.modelview_matrix_[1][1] * self.center_[1] +
  163. self.modelview_matrix_[1][2] * self.center_[2] +
  164. self.modelview_matrix_[1][3]),
  165. -( self.modelview_matrix_[2][0] * self.center_[0] +
  166. self.modelview_matrix_[2][1] * self.center_[1] +
  167. self.modelview_matrix_[2][2] * self.center_[2] +
  168. self.modelview_matrix_[2][3] +
  169. self.radius_ / 2.0 )])
  170. def map_to_sphere(self, _v2D):
  171. _v3D = [0.0, 0.0, 0.0]
  172. # inside Widget?
  173. if (( _v2D.x() >= 0 ) and ( _v2D.x() <= self.width() ) and
  174. ( _v2D.y() >= 0 ) and ( _v2D.y() <= self.height() ) ):
  175. # map Qt Coordinates to the centered unit square [-0.5..0.5]x[-0.5..0.5]
  176. x = float( _v2D.x() - 0.5 * self.width()) / self.width()
  177. y = float( 0.5 * self.height() - _v2D.y()) / self.height()
  178. _v3D[0] = x;
  179. _v3D[1] = y;
  180. # use Pythagoras to comp z-coord (the sphere has radius sqrt(2.0*0.5*0.5))
  181. z2 = 2.0*0.5*0.5-x*x-y*y;
  182. # numerical robust sqrt
  183. _v3D[2] = math.sqrt(max( z2, 0.0 ))
  184. # normalize direction to unit sphere
  185. n = linalg.norm(_v3D)
  186. _v3D = numpy.array(_v3D) / n
  187. return True, _v3D
  188. else:
  189. return False, _v3D
  190. def wheelEvent(self, _event):
  191. # Use the mouse wheel to zoom in/out
  192. d = (_event.angleDelta().y()) / 250.0 * self.radius_
  193. self.translate([0.0, 0.0, d])
  194. self.updateGL()
  195. _event.accept()
  196. def mousePressEvent(self, _event):
  197. self.last_point_2D_ = _event.pos()
  198. self.last_point_ok_, self.last_point_3D_ = self.map_to_sphere(self.last_point_2D_)
  199. def mouseMoveEvent(self, _event):
  200. newPoint2D = _event.pos()
  201. if ((newPoint2D.x() < 0) or (newPoint2D.x() > self.width()) or
  202. (newPoint2D.y() < 0) or (newPoint2D.y() > self.height())):
  203. return
  204. # Left button: rotate around center_
  205. # Middle button: translate object
  206. # Left & middle button: zoom in/out
  207. value_y = 0
  208. newPoint_hitSphere, newPoint3D = self.map_to_sphere(newPoint2D)
  209. dx = float(newPoint2D.x() - self.last_point_2D_.x())
  210. dy = float(newPoint2D.y() - self.last_point_2D_.y())
  211. w = float(self.width())
  212. h = float(self.height())
  213. # enable GL context
  214. self.makeCurrent()
  215. # move in z direction
  216. if (((_event.buttons() & QtCore.Qt.LeftButton) and (_event.buttons() & QtCore.Qt.MidButton))
  217. or (_event.buttons() & QtCore.Qt.LeftButton and _event.modifiers() & QtCore.Qt.ControlModifier)):
  218. value_y = self.radius_ * dy * 2.0 / h;
  219. self.translate([0.0, 0.0, value_y])
  220. #rotate
  221. elif (_event.buttons() & QtCore.Qt.MidButton
  222. or (_event.buttons() & QtCore.Qt.LeftButton and _event.modifiers() & QtCore.Qt.ShiftModifier)):
  223. #中间键 旋转
  224. '''if (not self.isInRotation_):
  225. self.isInRotation_ = True
  226. self.rotationBeginEvent.emit()
  227. axis = [0.0, 0.0, 0.0]
  228. angle = 0.0
  229. if (self.last_point_ok_ and newPoint_hitSphere):
  230. axis = numpy.cross(self.last_point_3D_, newPoint3D)
  231. cos_angle = numpy.dot(self.last_point_3D_, newPoint3D)
  232. if (abs(cos_angle) < 1.0):
  233. angle = math.acos(cos_angle) * 180.0 / math.pi
  234. angle *= 2.0
  235. self.rotate(axis, angle)'''
  236. # move in x,y direction
  237. elif (_event.buttons() & QtCore.Qt.LeftButton):
  238. #左键移动
  239. z = - (self.modelview_matrix_[0][2] * self.center_[0] +
  240. self.modelview_matrix_[1][2] * self.center_[1] +
  241. self.modelview_matrix_[2][2] * self.center_[2] +
  242. self.modelview_matrix_[3][2]) / (self.modelview_matrix_[0][3] * self.center_[0] +
  243. self.modelview_matrix_[1][3] * self.center_[1] +
  244. self.modelview_matrix_[2][3] * self.center_[2] +
  245. self.modelview_matrix_[3][3])
  246. fovy = 45.0
  247. aspect = w / h
  248. n = 0.01 * self.radius_
  249. up = math.tan(fovy / 2.0 * math.pi / 180.0) * n
  250. right = aspect * up
  251. self.translate( [2.0 * dx / w * right / n * z,
  252. -2.0 * dy / h * up / n * z,
  253. 0.0] )
  254. # remember this point
  255. self.last_point_2D_ = newPoint2D
  256. self.last_point_3D_ = newPoint3D
  257. self.last_point_ok_ = newPoint_hitSphere
  258. # trigger redraw
  259. self.updateGL()
  260. def mouseReleaseEvent(self, _event):
  261. if (self.isInRotation_):
  262. self.isInRotation_ = False
  263. self.rotationEndEvent.emit()
  264. last_point_ok_ = False
  265. #===============================================================================
  266. #
  267. # Local Variables:
  268. # mode: Python
  269. # indent-tabs-mode: nil
  270. # End:
  271. #
  272. #===============================================================================