|
- #!/usr/bin/env python
- #-*- coding: UTF-8 -*-
-
- """ This program demonstrates the use of pyassimp to load and
- render objects with OpenGL.
-
- 'c' cycles between cameras (if any available)
- 'q' to quit
-
- This example mixes 'old' OpenGL fixed-function pipeline with
- Vertex Buffer Objects.
-
- Materials are supported but textures are currently ignored.
-
- For a more advanced example (with shaders + keyboard/mouse
- controls), check scripts/sdl_viewer.py
-
- Author: Séverin Lemaignan, 2012
-
- This sample is based on several sources, including:
- - http://www.lighthouse3d.com/tutorials
- - http://www.songho.ca/opengl/gl_transform.html
- - http://code.activestate.com/recipes/325391/
- - ASSIMP's C++ SimpleOpenGL viewer
- """
-
- import os, sys
- from OpenGL.GLUT import *
- from OpenGL.GLU import *
- from OpenGL.GL import *
-
- import logging;logger = logging.getLogger("pyassimp_opengl")
- logging.basicConfig(level=logging.INFO)
-
- import math
- import numpy
-
- import pyassimp
- from pyassimp.postprocess import *
- from pyassimp.helper import *
-
-
- name = 'pyassimp OpenGL viewer'
- height = 600
- width = 900
-
- class GLRenderer():
- def __init__(self):
-
- self.scene = None
-
- self.using_fixed_cam = False
- self.current_cam_index = 0
-
- # store the global scene rotation
- self.angle = 0.
-
- # for FPS calculation
- self.prev_time = 0
- self.prev_fps_time = 0
- self.frames = 0
-
- def prepare_gl_buffers(self, mesh):
- """ Creates 3 buffer objets for each mesh,
- to store the vertices, the normals, and the faces
- indices.
- """
-
- mesh.gl = {}
-
- # Fill the buffer for vertex positions
- mesh.gl["vertices"] = glGenBuffers(1)
- glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
- glBufferData(GL_ARRAY_BUFFER,
- mesh.vertices,
- GL_STATIC_DRAW)
-
- # Fill the buffer for normals
- mesh.gl["normals"] = glGenBuffers(1)
- glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
- glBufferData(GL_ARRAY_BUFFER,
- mesh.normals,
- GL_STATIC_DRAW)
-
-
- # Fill the buffer for vertex positions
- mesh.gl["triangles"] = glGenBuffers(1)
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
- glBufferData(GL_ELEMENT_ARRAY_BUFFER,
- mesh.faces,
- GL_STATIC_DRAW)
-
- # Unbind buffers
- glBindBuffer(GL_ARRAY_BUFFER,0)
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0)
-
- def load_model(self, path, postprocess = None):
- logger.info("Loading model:" + path + "...")
-
- if postprocess:
- self.scene = pyassimp.load(path, postprocess)
- else:
- self.scene = pyassimp.load(path)
- logger.info("Done.")
-
- scene = self.scene
- #log some statistics
- logger.info(" meshes: %d" % len(scene.meshes))
- logger.info(" total faces: %d" % sum([len(mesh.faces) for mesh in scene.meshes]))
- logger.info(" materials: %d" % len(scene.materials))
- self.bb_min, self.bb_max = get_bounding_box(self.scene)
- logger.info(" bounding box:" + str(self.bb_min) + " - " + str(self.bb_max))
-
- self.scene_center = [(a + b) / 2. for a, b in zip(self.bb_min, self.bb_max)]
-
- for index, mesh in enumerate(scene.meshes):
- self.prepare_gl_buffers(mesh)
-
- # Finally release the model
- pyassimp.release(scene)
-
- def cycle_cameras(self):
- self.current_cam_index
- if not self.scene.cameras:
- return None
- self.current_cam_index = (self.current_cam_index + 1) % len(self.scene.cameras)
- cam = self.scene.cameras[self.current_cam_index]
- logger.info("Switched to camera " + str(cam))
- return cam
-
- def set_default_camera(self):
-
- if not self.using_fixed_cam:
- glLoadIdentity()
-
- gluLookAt(0.,0.,3.,
- 0.,0.,-5.,
- 0.,1.,0.)
-
-
-
- def set_camera(self, camera):
-
- if not camera:
- return
-
- self.using_fixed_cam = True
-
- znear = camera.clipplanenear
- zfar = camera.clipplanefar
- aspect = camera.aspect
- fov = camera.horizontalfov
-
- glMatrixMode(GL_PROJECTION)
- glLoadIdentity()
-
- # Compute gl frustrum
- tangent = math.tan(fov/2.)
- h = znear * tangent
- w = h * aspect
-
- # params: left, right, bottom, top, near, far
- glFrustum(-w, w, -h, h, znear, zfar)
- # equivalent to:
- #gluPerspective(fov * 180/math.pi, aspect, znear, zfar)
-
- glMatrixMode(GL_MODELVIEW)
- glLoadIdentity()
-
- cam = transform(camera.position, camera.transformation)
- at = transform(camera.lookat, camera.transformation)
- gluLookAt(cam[0], cam[2], -cam[1],
- at[0], at[2], -at[1],
- 0, 1, 0)
-
- def fit_scene(self, restore = False):
- """ Compute a scale factor and a translation to fit and center
- the whole geometry on the screen.
- """
-
- x_max = self.bb_max[0] - self.bb_min[0]
- y_max = self.bb_max[1] - self.bb_min[1]
- tmp = max(x_max, y_max)
- z_max = self.bb_max[2] - self.bb_min[2]
- tmp = max(z_max, tmp)
-
- if not restore:
- tmp = 1. / tmp
-
- logger.info("Scaling the scene by %.03f" % tmp)
- glScalef(tmp, tmp, tmp)
-
- # center the model
- direction = -1 if not restore else 1
- glTranslatef( direction * self.scene_center[0],
- direction * self.scene_center[1],
- direction * self.scene_center[2] )
-
- return x_max, y_max, z_max
-
- def apply_material(self, mat):
- """ Apply an OpenGL, using one OpenGL display list per material to cache
- the operation.
- """
-
- if not hasattr(mat, "gl_mat"): # evaluate once the mat properties, and cache the values in a glDisplayList.
- diffuse = numpy.array(mat.properties.get("diffuse", [0.8, 0.8, 0.8, 1.0]))
- specular = numpy.array(mat.properties.get("specular", [0., 0., 0., 1.0]))
- ambient = numpy.array(mat.properties.get("ambient", [0.2, 0.2, 0.2, 1.0]))
- emissive = numpy.array(mat.properties.get("emissive", [0., 0., 0., 1.0]))
- shininess = min(mat.properties.get("shininess", 1.0), 128)
- wireframe = mat.properties.get("wireframe", 0)
- twosided = mat.properties.get("twosided", 1)
-
- setattr(mat, "gl_mat", glGenLists(1))
- glNewList(mat.gl_mat, GL_COMPILE)
-
- glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse)
- glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular)
- glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient)
- glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emissive)
- glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if wireframe else GL_FILL)
- glDisable(GL_CULL_FACE) if twosided else glEnable(GL_CULL_FACE)
-
- glEndList()
-
- glCallList(mat.gl_mat)
-
-
-
- def do_motion(self):
-
- gl_time = glutGet(GLUT_ELAPSED_TIME)
-
- self.angle = (gl_time - self.prev_time) * 0.1
-
- self.prev_time = gl_time
-
- # Compute FPS
- self.frames += 1
- if gl_time - self.prev_fps_time >= 1000:
- current_fps = self.frames * 1000 / (gl_time - self.prev_fps_time)
- logger.info('%.0f fps' % current_fps)
- self.frames = 0
- self.prev_fps_time = gl_time
-
- glutPostRedisplay()
-
- def recursive_render(self, node):
- """ Main recursive rendering method.
- """
-
- # save model matrix and apply node transformation
- glPushMatrix()
- m = node.transformation.transpose() # OpenGL row major
- glMultMatrixf(m)
-
- for mesh in node.meshes:
- self.apply_material(mesh.material)
-
- glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
- glEnableClientState(GL_VERTEX_ARRAY)
- glVertexPointer(3, GL_FLOAT, 0, None)
-
- glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
- glEnableClientState(GL_NORMAL_ARRAY)
- glNormalPointer(GL_FLOAT, 0, None)
-
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
- glDrawElements(GL_TRIANGLES,len(mesh.faces) * 3, GL_UNSIGNED_INT, None)
-
- glDisableClientState(GL_VERTEX_ARRAY)
- glDisableClientState(GL_NORMAL_ARRAY)
-
- glBindBuffer(GL_ARRAY_BUFFER, 0)
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
-
- for child in node.children:
- self.recursive_render(child)
-
- glPopMatrix()
-
-
- def display(self):
- """ GLUT callback to redraw OpenGL surface
- """
- glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
-
- glRotatef(self.angle,0.,1.,0.)
- self.recursive_render(self.scene.rootnode)
-
- glutSwapBuffers()
- self.do_motion()
- return
-
- ####################################################################
- ## GLUT keyboard and mouse callbacks ##
- ####################################################################
- def onkeypress(self, key, x, y):
- if key == 'c':
- self.fit_scene(restore = True)
- self.set_camera(self.cycle_cameras())
- if key == 'q':
- sys.exit(0)
-
- def render(self, filename=None, fullscreen = False, autofit = True, postprocess = None):
- """
-
- :param autofit: if true, scale the scene to fit the whole geometry
- in the viewport.
- """
-
- # First initialize the openGL context
- glutInit(sys.argv)
- glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
- if not fullscreen:
- glutInitWindowSize(width, height)
- glutCreateWindow(name)
- else:
- glutGameModeString("1024x768")
- if glutGameModeGet(GLUT_GAME_MODE_POSSIBLE):
- glutEnterGameMode()
- else:
- print("Fullscreen mode not available!")
- sys.exit(1)
-
- self.load_model(filename, postprocess = postprocess)
-
-
- glClearColor(0.1,0.1,0.1,1.)
- #glShadeModel(GL_SMOOTH)
-
- glEnable(GL_LIGHTING)
-
- glEnable(GL_CULL_FACE)
- glEnable(GL_DEPTH_TEST)
-
- glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
- glEnable(GL_NORMALIZE)
- glEnable(GL_LIGHT0)
-
- glutDisplayFunc(self.display)
-
-
- glMatrixMode(GL_PROJECTION)
- glLoadIdentity()
- gluPerspective(35.0, width/float(height) , 0.10, 100.0)
- glMatrixMode(GL_MODELVIEW)
- self.set_default_camera()
-
- if autofit:
- # scale the whole asset to fit into our view frustum·
- self.fit_scene()
-
- glPushMatrix()
-
- glutKeyboardFunc(self.onkeypress)
- glutIgnoreKeyRepeat(1)
-
- glutMainLoop()
-
-
- if __name__ == '__main__':
- if not len(sys.argv) > 1:
- print("Usage: " + __file__ + " <model>")
- sys.exit(0)
-
- glrender = GLRenderer()
- glrender.render(sys.argv[1], fullscreen = False, postprocess = aiProcessPreset_TargetRealtime_MaxQuality)
|