PyOpenGLでえせPLYファイルビューワ


bunny

horse

PyOpenGLでPLYファイルのてきとうなパーサを書いてスタンフォードバニーを表示させた。

超がつくほどのてきとうな仕様のパーサで色々と決め打ち。法線は描画時に面法線を計算して設定する。・・・いまのプログラムだと法線の方向が逆になる場合がある気がするけどいいや。

以下を参考にして、
http://local.wasp.uwa.edu.au/~pbourke/dataformats/ply/
以下のurlから参照できるplyファイルで動作を確認した。
http://www.cs.virginia.edu/~gfx/Courses/2001/Advanced.spring.01/plymodels/

やっぱりビジュアルなプログラムは達成感があっていいなー。

# coding: utf-8
# x,y,zの順で頂点がならんでいるのが前提
# 面は3点からなるのが前提
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import sys

class PlyModel(object):
    def __init__(self):
        self.vertices = [] # [(x,y,z)]
        self.faces = [] # [(xi,yi,zi)]
        self.nvertex = None
        self.nface = None
        self.maxx = -1e10
        self.maxy = -1e10
        self.maxz = -1e10
    def load(self, filepath):
        global cx,cy,cz
        f = open(filepath, "rb")
        # read header
        while True:
            lst = f.readline().split()
            if lst[0] == "end_header":
                break
            if lst[0] == "element":
                if lst[1] == "vertex":
                    self.nvertex = int(lst[2])
                if lst[1] == "face":
                    self.nface = int(lst[2])
        # read vertex
        for i in xrange(self.nvertex):
            v = map(float, f.readline().split()[:3])
            self.maxx = max(self.maxx, v[0])
            self.maxy = max(self.maxy, v[1])
            self.maxz = max(self.maxz, v[2])
            self.vertices.append(v)
        # read face
        for i in xrange(self.nface):
            x = map(int, f.readline().split()[1:])
            self.faces.append(x)
        f.close()
    def draw(self):
        # 面法線を設定して描画する
        glBegin(GL_TRIANGLES)
        for index in self.faces:
            p1,p2,p3 = map(lambda i: self.vertices[i], index)
            u = map(lambda x,y: x-y, p2, p1)
            v = map(lambda x,y: x-y, p3, p1)
            cross = ( # 法線の計算
                u[1]*v[2] - u[2]*v[1],
                u[2]*v[0] - u[0]*v[2],
                u[0]*v[1] - u[1]*v[0],
                )
            # 正規化
            norm = sum(map(lambda x:x*x, cross))**.5
            cross = map(lambda x:x/norm, cross)
            glNormal3fv(cross)
            glVertex3fv(p1)
            glVertex3fv(p2)
            glVertex3fv(p3)
        glEnd()

cx,cy,cz=100,100,100
sx,sy,sz=0,0,0
displist=None
def display():
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(60, float(glutGet(GLUT_WINDOW_WIDTH))/glutGet(GLUT_WINDOW_HEIGHT), .1, 1e8)
    gluLookAt(cx,cy,cz, sx,sy,sz, 0,1,0)
    glMatrixMode(GL_MODELVIEW)

    glCallList(displist)
    glutSwapBuffers()

def reshape(w, h):
    glViewport(0, 0, w, h)
    glutPostRedisplay()

def keyboard(k, x, y):
    global cx,cy,cz,sx,sy,sz
    if k in "qQ\33":
        sys.exit()
    if k == 'u': cx += 1
    if k == 'j': cx -= 1
    if k == 'i': cy += 1
    if k == 'k': cy -= 1
    if k == 'o': cz += 1
    if k == 'l': cz -= 1
    if k == 'p': sx += 1
    if k == ';': sx -= 1
    if k == '@': sy += 1
    if k == ':': sy -= 1
    if k == '[': sz += 1
    if k == ']': sz -= 1
    glutPostRedisplay()

def init():
    global displist,cx,cy,cz
    glClearColor(1,1,1,1)
    
    glEnable(GL_DEPTH_TEST)
    
    glEnable(GL_LIGHTING)
    glEnable(GL_LIGHT0)
    glLightfv(GL_LIGHT0, GL_POSITION, [200,300,105,0])
    glLightfv(GL_LIGHT0, GL_AMBIENT, [.1,.1,.1,1])
    
    ply = PlyModel()
    ply.load(sys.argv[1])
    displist = glGenLists(1)
    glNewList(displist, GL_COMPILE)
    ply.draw()
    glEndList()
    cx,cy,cz = ply.maxx,ply.maxy,ply.maxz

def main():
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
    glutCreateWindow("ply file view")
    
    init()
    
    glutDisplayFunc(display)
    glutReshapeFunc(reshape)
    glutKeyboardFunc(keyboard)
    
    glutMainLoop()

if __name__ == '__main__':
    main()