2
0

opengl_viewer.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. #!/usr/bin/env python
  2. #-*- coding: UTF-8 -*-
  3. """ This program demonstrate the use of pyassimp to render
  4. objects in OpenGL.
  5. It loads a 3D model with ASSIMP and display it.
  6. Materials are supported but textures are currently ignored.
  7. Half-working keyboard + mouse navigation is supported.
  8. This sample is based on several sources, including:
  9. - http://www.lighthouse3d.com/tutorials
  10. - http://www.songho.ca/opengl/gl_transform.html
  11. - http://code.activestate.com/recipes/325391/
  12. - ASSIMP's C++ SimpleOpenGL viewer
  13. """
  14. import os, sys
  15. from OpenGL.GLUT import *
  16. from OpenGL.GLU import *
  17. from OpenGL.GL import *
  18. from OpenGL.arrays import ArrayDatatype
  19. import logging;logger = logging.getLogger("assimp_opengl")
  20. logging.basicConfig(level=logging.INFO)
  21. import math
  22. import numpy
  23. from pyassimp import core as pyassimp
  24. from pyassimp.postprocess import *
  25. from pyassimp.helper import *
  26. name = 'pyassimp OpenGL viewer'
  27. height = 600
  28. width = 900
  29. class GLRenderer():
  30. def __init__(self):
  31. self.scene = None
  32. self.drot = 0.0
  33. self.dp = 0.0
  34. self.angle = 0.0
  35. self.x = 1.0
  36. self.z = 3.0
  37. self.lx = 0.0
  38. self.lz = 0.0
  39. self.using_fixed_cam = False
  40. self.current_cam_index = 0
  41. self.x_origin = -1 # x position of the mouse when pressing left btn
  42. # for FPS calculation
  43. self.prev_time = 0
  44. self.prev_fps_time = 0
  45. self.frames = 0
  46. def prepare_gl_buffers(self, mesh):
  47. mesh.gl = {}
  48. # Fill the buffer for vertex positions
  49. mesh.gl["vertices"] = glGenBuffers(1)
  50. glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
  51. glBufferData(GL_ARRAY_BUFFER,
  52. mesh.vertices,
  53. GL_STATIC_DRAW)
  54. # Fill the buffer for normals
  55. mesh.gl["normals"] = glGenBuffers(1)
  56. glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
  57. glBufferData(GL_ARRAY_BUFFER,
  58. mesh.normals,
  59. GL_STATIC_DRAW)
  60. # Fill the buffer for vertex positions
  61. mesh.gl["triangles"] = glGenBuffers(1)
  62. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
  63. glBufferData(GL_ELEMENT_ARRAY_BUFFER,
  64. mesh.faces,
  65. GL_STATIC_DRAW)
  66. # Unbind buffers
  67. glBindBuffer(GL_ARRAY_BUFFER,0)
  68. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0)
  69. def load_dae(self, path, postprocess = None):
  70. logger.info("Loading model:" + path + "...")
  71. if postprocess:
  72. self.scene = pyassimp.load(path, postprocess)
  73. else:
  74. self.scene = pyassimp.load(path)
  75. logger.info("Done.")
  76. scene = self.scene
  77. #log some statistics
  78. logger.info(" meshes: %d" % len(scene.meshes))
  79. logger.info(" total faces: %d" % sum([len(mesh.faces) for mesh in scene.meshes]))
  80. logger.info(" materials: %d" % len(scene.materials))
  81. self.bb_min, self.bb_max = get_bounding_box(self.scene)
  82. logger.info(" bounding box:" + str(self.bb_min) + " - " + str(self.bb_max))
  83. self.scene_center = [(a + b) / 2. for a, b in zip(self.bb_min, self.bb_max)]
  84. for index, mesh in enumerate(scene.meshes):
  85. self.prepare_gl_buffers(mesh)
  86. # Finally release the model
  87. pyassimp.release(scene)
  88. def cycle_cameras(self):
  89. self.current_cam_index
  90. if not self.scene.cameras:
  91. return None
  92. self.current_cam_index = (self.current_cam_index + 1) % len(self.scene.cameras)
  93. cam = self.scene.cameras[self.current_cam_index]
  94. logger.info("Switched to camera " + str(cam))
  95. return cam
  96. def set_default_camera(self):
  97. if not self.using_fixed_cam:
  98. glLoadIdentity()
  99. gluLookAt(self.x ,1., self.z, # pos
  100. self.x + self.lx - 1.0, 1., self.z + self.lz - 3.0, # look at
  101. 0.,1.,0.) # up vector
  102. def set_camera(self, camera):
  103. if not camera:
  104. return
  105. self.using_fixed_cam = True
  106. znear = camera.clipplanenear
  107. zfar = camera.clipplanefar
  108. aspect = camera.aspect
  109. fov = camera.horizontalfov
  110. glMatrixMode(GL_PROJECTION)
  111. glLoadIdentity()
  112. # Compute gl frustrum
  113. tangent = math.tan(fov/2.)
  114. h = znear * tangent
  115. w = h * aspect
  116. # params: left, right, bottom, top, near, far
  117. glFrustum(-w, w, -h, h, znear, zfar)
  118. # equivalent to:
  119. #gluPerspective(fov * 180/math.pi, aspect, znear, zfar)
  120. glMatrixMode(GL_MODELVIEW)
  121. glLoadIdentity()
  122. cam = transform(camera.position, camera.transformation)
  123. at = transform(camera.lookat, camera.transformation)
  124. gluLookAt(cam[0], cam[2], -cam[1],
  125. at[0], at[2], -at[1],
  126. 0, 1, 0)
  127. def fit_scene(self, restore = False):
  128. """ Compute a scale factor and a translation to fit and center
  129. the whole geometry on the screen.
  130. """
  131. x_max = self.bb_max[0] - self.bb_min[0]
  132. y_max = self.bb_max[1] - self.bb_min[1]
  133. tmp = max(x_max, y_max)
  134. z_max = self.bb_max[2] - self.bb_min[2]
  135. tmp = max(z_max, tmp)
  136. if not restore:
  137. tmp = 1. / tmp
  138. logger.info("Scaling the scene by %.03f" % tmp)
  139. glScalef(tmp, tmp, tmp)
  140. # center the model
  141. direction = -1 if not restore else 1
  142. glTranslatef( direction * self.scene_center[0],
  143. direction * self.scene_center[1],
  144. direction * self.scene_center[2] )
  145. return x_max, y_max, z_max
  146. def apply_material(self, mat):
  147. """ Apply an OpenGL, using one OpenGL list per material to cache
  148. the operation.
  149. """
  150. if not hasattr(mat, "gl_mat"): # evaluate once the mat properties, and cache the values in a glDisplayList.
  151. diffuse = mat.properties.get("$clr.diffuse", numpy.array([0.8, 0.8, 0.8, 1.0]))
  152. specular = mat.properties.get("$clr.specular", numpy.array([0., 0., 0., 1.0]))
  153. ambient = mat.properties.get("$clr.ambient", numpy.array([0.2, 0.2, 0.2, 1.0]))
  154. emissive = mat.properties.get("$clr.emissive", numpy.array([0., 0., 0., 1.0]))
  155. shininess = min(mat.properties.get("$mat.shininess", 1.0), 128)
  156. wireframe = mat.properties.get("$mat.wireframe", 0)
  157. twosided = mat.properties.get("$mat.twosided", 1)
  158. from OpenGL.raw import GL
  159. setattr(mat, "gl_mat", GL.GLuint(0))
  160. mat.gl_mat = glGenLists(1)
  161. glNewList(mat.gl_mat, GL_COMPILE)
  162. glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse)
  163. glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular)
  164. glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient)
  165. glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emissive)
  166. glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
  167. glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if wireframe else GL_FILL)
  168. glDisable(GL_CULL_FACE) if twosided else glEnable(GL_CULL_FACE)
  169. glEndList()
  170. glCallList(mat.gl_mat)
  171. def do_motion(self):
  172. gl_time = glutGet(GLUT_ELAPSED_TIME)
  173. # Compute the new position of the camera and set it
  174. self.x += self.dp * self.lx * 0.01 * (gl_time-self.prev_time)
  175. self.z += self.dp * self.lz * 0.01 * (gl_time-self.prev_time)
  176. self.angle += self.drot * 0.1 * (gl_time-self.prev_time)
  177. self.lx = math.sin(self.angle)
  178. self.lz = -math.cos(self.angle)
  179. self.set_default_camera()
  180. self.prev_time = gl_time
  181. # Compute FPS
  182. self.frames += 1
  183. if gl_time - self.prev_fps_time >= 1000:
  184. current_fps = self.frames * 1000 / (gl_time - self.prev_fps_time)
  185. logger.info('%.0f fps' % current_fps)
  186. self.frames = 0
  187. self.prev_fps_time = gl_time
  188. glutPostRedisplay()
  189. def recursive_render(self, node):
  190. """ Main recursive rendering method.
  191. """
  192. # save model matrix and apply node transformation
  193. glPushMatrix()
  194. m = node.transformation.transpose() # OpenGL row major
  195. glMultMatrixf(m)
  196. for mesh in node.meshes:
  197. self.apply_material(mesh.material)
  198. glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
  199. glEnableClientState(GL_VERTEX_ARRAY)
  200. glVertexPointer(3, GL_FLOAT, 0, None)
  201. glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
  202. glEnableClientState(GL_NORMAL_ARRAY)
  203. glNormalPointer(GL_FLOAT, 0, None)
  204. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
  205. glDrawElements(GL_TRIANGLES,len(mesh.faces) * 3, GL_UNSIGNED_INT, None)
  206. glDisableClientState(GL_VERTEX_ARRAY)
  207. glDisableClientState(GL_NORMAL_ARRAY)
  208. glBindBuffer(GL_ARRAY_BUFFER, 0)
  209. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
  210. for child in node.children:
  211. self.recursive_render(child)
  212. glPopMatrix()
  213. def display(self):
  214. """ GLUT callback to redraw OpenGL surface
  215. """
  216. glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
  217. self.recursive_render(self.scene.rootnode)
  218. glutSwapBuffers()
  219. self.do_motion()
  220. return
  221. ####################################################################
  222. ## GLUT keyboard and mouse callbacks ##
  223. ####################################################################
  224. def onkeypress(self, key, x, y):
  225. if key == 'c':
  226. self.fit_scene(restore = True)
  227. self.set_camera(self.cycle_cameras())
  228. if key == 'q':
  229. sys.exit(0)
  230. def onspecialkeypress(self, key, x, y):
  231. fraction = 0.05
  232. if key == GLUT_KEY_UP:
  233. self.dp = 0.5
  234. if key == GLUT_KEY_DOWN:
  235. self.dp = -0.5
  236. if key == GLUT_KEY_LEFT:
  237. self.drot = -0.01
  238. if key == GLUT_KEY_RIGHT:
  239. self.drot = 0.01
  240. def onspecialkeyrelease(self, key, x, y):
  241. if key == GLUT_KEY_UP:
  242. self.dp = 0.
  243. if key == GLUT_KEY_DOWN:
  244. self.dp = 0.
  245. if key == GLUT_KEY_LEFT:
  246. self.drot = 0.0
  247. if key == GLUT_KEY_RIGHT:
  248. self.drot = 0.0
  249. def onclick(self, button, state, x, y):
  250. if button == GLUT_LEFT_BUTTON:
  251. if state == GLUT_UP:
  252. self.drot = 0
  253. self.x_origin = -1
  254. else: # GLUT_DOWN
  255. self.x_origin = x
  256. def onmousemove(self, x, y):
  257. if self.x_origin >= 0:
  258. self.drot = (x - self.x_origin) * 0.001
  259. def render(self, filename=None, fullscreen = False, autofit = True, postprocess = None):
  260. """
  261. :param autofit: if true, scale the scene to fit the whole geometry
  262. in the viewport.
  263. """
  264. # First initialize the openGL context
  265. glutInit(sys.argv)
  266. glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
  267. if not fullscreen:
  268. glutInitWindowSize(width, height)
  269. glutCreateWindow(name)
  270. else:
  271. glutGameModeString("1024x768")
  272. if glutGameModeGet(GLUT_GAME_MODE_POSSIBLE):
  273. glutEnterGameMode()
  274. else:
  275. print("Fullscreen mode not available!")
  276. sys.exit(1)
  277. self.load_dae(filename, postprocess = postprocess)
  278. glClearColor(0.1,0.1,0.1,1.)
  279. #glShadeModel(GL_SMOOTH)
  280. glEnable(GL_LIGHTING)
  281. glEnable(GL_CULL_FACE)
  282. glEnable(GL_DEPTH_TEST)
  283. #lightZeroPosition = [10.,4.,10.,1.]
  284. #lightZeroColor = [0.8,1.0,0.8,1.0] #green tinged
  285. #glLightfv(GL_LIGHT0, GL_POSITION, lightZeroPosition)
  286. #glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor)
  287. #glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1)
  288. #glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05)
  289. glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
  290. glEnable(GL_NORMALIZE)
  291. glEnable(GL_LIGHT0)
  292. glutDisplayFunc(self.display)
  293. glMatrixMode(GL_PROJECTION)
  294. glLoadIdentity()
  295. gluPerspective(35.0, width/float(height) , 0.10, 100.0)
  296. glMatrixMode(GL_MODELVIEW)
  297. self.set_default_camera()
  298. if autofit:
  299. # scale the whole asset to fit into our view frustum·
  300. self.fit_scene()
  301. glPushMatrix()
  302. # Register GLUT callbacks for keyboard and mouse
  303. glutKeyboardFunc(self.onkeypress)
  304. glutSpecialFunc(self.onspecialkeypress)
  305. glutIgnoreKeyRepeat(1)
  306. glutSpecialUpFunc(self.onspecialkeyrelease)
  307. glutMouseFunc(self.onclick)
  308. glutMotionFunc(self.onmousemove)
  309. glutMainLoop()
  310. if __name__ == '__main__':
  311. if not len(sys.argv) > 1:
  312. print("Usage: " + __file__ + " <model>")
  313. sys.exit(0)
  314. glrender = GLRenderer()
  315. glrender.render(sys.argv[1], fullscreen = False, postprocess = aiProcessPreset_TargetRealtime_MaxQuality)