import sys, struct, array, math, os, zlibfrom OpenGL.GL import *from OpenGL.GLU import *from OpenGL.GL.ARB.texture_rectangle import *from OpenGL.GL.ARB.multitexture import *import pygamefrom pygame.locals import *bcxMap = {}normals = []def vecDot(a,b): return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]def vecNormal(a): lenSqr = vecDot(a,a) if lenSqr > 0.0: ool = 1.0 / math.sqrt(lenSqr) return (a[0] * ool, a[1] * ool, a[2] * ool) else: return (0,0,0)def loadLzs(name): # read compressed data f = open(name, "rb") buf = f.read() f.close() (size,) = struct.unpack_from("<I", buf, 0) if size + 4 != len(buf): del buf return None ibuf = array.array("B", buf) obuf = array.array("B") # decompress; iofs = 4 cmd = 0 bit = 0 while iofs < len(ibuf): if bit == 0: cmd = ibuf[iofs] bit = 8 iofs += 1 if cmd & 1: obuf.append(ibuf[iofs]) iofs += 1 else: a = ibuf[iofs] iofs += 1 b = ibuf[iofs] iofs += 1 o = a | ((b & 0xF0) << 4) l = (b & 0xF) + 3; rofs = len(obuf) - ((len(obuf) - 18 - o) & 0xFFF) for j in xrange(l): if rofs < 0: obuf.append(0) else: obuf.append(obuf[rofs]) rofs += 1 cmd >>= 1 bit -= 1 return obuf.tostring()def loadFieldBin(name): # read compressed data f = open(name, "rb") buf = f.read() f.close() (size,) = struct.unpack_from("<I", buf, 0) # decompress data = zlib.decompress(buf[8:], 16 | 15) if len(data) != size: del buf del data return None return data def readModel(data, num_bones, offset_skeleton, num_parts, offset_parts): model = {} skeleton = [] for i in xrange(num_bones): (length, parent, has_mesh) = struct.unpack_from("<hbB", data, offset_skeleton + i * 4) skeleton.append((length, parent, has_mesh != 0)) model["skeleton"] = skeleton parts = [] for i in xrange(num_parts): part = {} (u1, bone_index, num_vertex, num_texcoord, num_quad_color_tex, num_tri_color_tex, num_quad_mono_tex, num_tri_mono_tex, num_tri_mono, num_quad_mono, num_tri_color, num_quad_color, u3, offset_poly, offset_texcoord, offset_flags, offset_control, u4, offset_vertex) = struct.unpack_from("<BBBBBBBBBBBBHHHHHHH", data, offset_parts + i * 0x20) # print (u1, bone_index, num_vertex, num_texcoord, num_quad_color_tex, num_tri_color_tex, num_quad_mono_tex, num_tri_mono_tex, num_tri_mono, num_quad_mono, num_tri_color, num_quad_color, u3, offset_poly, offset_texcoord, offset_flags, offset_control, u4, offset_vertex) #print "offset_vertex %x %x %x" % (offset_vertex, num_vertex, offset_vertex + 4 + num_vertex * 8) #print "offset_texcoord %x %x %x" % (offset_vertex + offset_texcoord, num_texcoord, offset_vertex + offset_texcoord + num_texcoord * 2) part["bone_index"] = bone_index # print (u1, u3, u4) vertices = [] for j in xrange(num_vertex): (x, y, z) = struct.unpack_from("<hhh", data, offset_vertex + 4 + j * 8) vertices.append((x,y,z)) part["vertices"] = vertices texcoords = [] for j in xrange(num_texcoord): (u, v) = struct.unpack_from("<BB", data, offset_vertex + offset_texcoord + j * 2) texcoords.append((u,v)) part["texcoords"] = texcoords cur_poly = offset_vertex + offset_poly cur_flags = offset_vertex + offset_flags cur_control = offset_vertex + offset_control # print "u4 : %4.4x" % (offset_vertex + u4) # print "cur_poly : %4.4x" % (cur_poly) # print "cur_flags : %4.4x" % (cur_flags) # print "cur_control : %4.4x" % (cur_control) quad_color_tex = [] for j in xrange(num_quad_color_tex): control = struct.unpack_from("<B", data, cur_control) cur_control += 1 (av, bv, cv, dv, ar, ag, ab, an, br, bg, bb, bn, cr, cg, cb, cn, dr, dg, db, dn, at, bt, ct, dt) = struct.unpack_from("<BBBBBBBBBBBBBBBBBBBBBBBB", data, cur_poly) poly = ((av, at, (ar, ag, ab), an), (bv, bt, (br, bg, bb), bn), (dv, dt, (dr, dg, db), dn), (cv, ct, (cr, cg, cb), cn)) # print "quad_color_tex %i %i %i %i / %i %i %i %i" % (av, bv, cv, dv, an, bn, cn, dn) quad_color_tex.append(poly) cur_poly += 0x18 part["quad_color_tex"] = quad_color_tex tri_color_tex = [] for j in xrange(num_tri_color_tex): control = struct.unpack_from("<B", data, cur_control) cur_control += 1 (av, bv, cv, xv, ar, ag, ab, an, br, bg, bb, bn, cr, cg, cb, cn, at, bt, ct, xt) = struct.unpack_from("<BBBBBBBBBBBBBBBBBBBB", data, cur_poly) poly = ((av, at, (ar, ag, ab), an), (bv, bt, (br, bg, bb), bn), (cv, ct, (cr, cg, cb), cn), xv, xt) # print "tri_color_tex %i %i %i / %x %x %x" % (av, bv, cv, an, bn, cn) tri_color_tex.append(poly) cur_poly += 0x14 part["tri_color_tex"] = tri_color_tex quad_mono_tex = [] for j in xrange(num_quad_mono_tex): control = struct.unpack_from("<B", data, cur_control) cur_control += 1 (av, bv, cv, dv, r, g, b, n, at, bt, ct, dt) = struct.unpack_from("<BBBBBBBBBBBB", data, cur_poly) # print "quad_mono_tex", x poly = ((av, at), (bv, bt), (dv, dt), (cv, ct), (r, g, b), n) quad_mono_tex.append(poly) cur_poly += 0x0C part["quad_mono_tex"] = quad_mono_tex tri_mono_tex = [] for j in xrange(num_tri_mono_tex): control = struct.unpack_from("<B", data, cur_control) cur_control += 1 (av, bv, cv, xv, r, g, b, n, at, bt, ct, xt) = struct.unpack_from("<BBBBBBBBBBBB", data, cur_poly) poly = ((av, at), (bv, bt), (cv, ct), (r, g, b), n, xv, xt) # print "tri_mono_tex", x, xv, xt tri_mono_tex.append(poly) cur_poly += 0x0C part["tri_mono_tex"] = tri_mono_tex tri_mono = [] for j in xrange(num_tri_mono): (av, bv, cv, xv, r, g, b, n) = struct.unpack_from("<BBBBBBBB", data, cur_poly) poly = ((av,), (bv,), (cv,), (r, g, b), n, xv) # print "tri_mono", x, xv tri_mono.append(poly) cur_poly += 8 part["tri_mono"] = tri_mono quad_mono = [] for j in xrange(num_quad_mono): (av, bv, cv, dv, r, g, b, n) = struct.unpack_from("<BBBBBBBB", data, cur_poly) poly = ((av,), (bv,), (dv,), (cv,), (r, g, b), n) # print "quad_mono", x quad_mono.append(poly) cur_poly += 8 part["quad_mono"] = quad_mono tri_color = [] for j in xrange(num_tri_color): (av, bv, cv, xv, ar, ag, ab, an, br, bg, bb, bn, cr, cg, cb, cn) = struct.unpack_from("<BBBBBBBBBBBBBBBB", data, cur_poly) poly = ((av,(ar, ag, ab), an), (bv, (br, bg, bb), bn), (cv, (cr, cg, cb), cn), xv) # print "tri_color %i %i %i / %i %i %i %i / %i" % (av, bv, cv, an, bn, cn, xv, len(vertices)) tri_color.append(poly) cur_poly += 0x10 part["tri_color"] = tri_color quad_color = [] for j in xrange(num_quad_color): (av, bv, cv, dv, ar, ag, ab, an, br, bg, bb, bn, cr, cg, cb, cn, dr, dg, db, dn) = struct.unpack_from("<BBBBBBBBBBBBBBBBBBBB", data, cur_poly) poly = ((av,(ar, ag, ab), an), (bv, (br, bg, bb), bn), (dv, (dr, dg, db), dn), (cv, (cr, cg, cb), cn)) # print "quad_color %i %i %i %i / %x %x %x %x" % (av, bv, cv, dv, an, bn, cn, dn) quad_color.append(poly) cur_poly += 0x14 part["quad_color"] = quad_color # print "end_poly : %4.4x" % (cur_poly) # print "end_flags : %4.4x" % (cur_flags) # print "end_control : %4.4x" % (cur_control) parts.append(part) model["parts"] = parts return model def readAnimations(data, num_animations, offset_animations): animations = [] for i in xrange(num_animations): (num_frames, num_channel, num_frames_translation, num_static_translation, num_frames_rotation, offset_frames_translation, offset_static_translation, offset_frames_rotation, offset_data) = struct.unpack_from("<HBBBBHHHI", data, offset_animations + i * 0x10) offset_data &= 0x7FFFFFFF # print "animation:", (offset_animations + i * 0x10, num_frames, num_channel, num_frames_translation, num_static_translation, num_frames_rotation, offset_frames_translation, offset_static_translation, offset_frames_rotation, offset_data) animation = [] for k in xrange(num_channel): (flag, rx, ry, rz, tx, ty, tz) = struct.unpack_from("<BBBBBBB", data, offset_data + 0x04 + k * 8) channel = [] for j in xrange(num_frames): # rotation if flag & 0x01: rotation_x = 360.0 * float(ord(data[offset_data + offset_frames_rotation + rx * num_frames + j])) / 255.0 else: rotation_x = 360.0 * rx / 255.0 if flag & 0x02: rotation_y = 360.0 * float(ord(data[offset_data + offset_frames_rotation + ry * num_frames + j])) / 255.0 else: rotation_y = 360.0 * ry / 255.0 if flag & 0x04: rotation_z = 360.0 * float(ord(data[offset_data + offset_frames_rotation + rz * num_frames + j])) / 255.0 else: rotation_z = 360.0 * rz / 255.0 # translation if flag & 0x10: (translation_x,) = struct.unpack_from("<h", data, offset_data + offset_frames_translation + tx * num_frames * 2 + j * 2) elif tx != 0xFF: (translation_x,) = struct.unpack_from("<h", data, offset_data + offset_static_translation + tx * 2) else: translation_x = 0 if flag & 0x20: (translation_y,) = struct.unpack_from("<h", data, offset_data + offset_frames_translation + ty * num_frames * 2 + j * 2); elif ty != 0xFF: (translation_y,) = struct.unpack_from("<h", data, offset_data + offset_static_translation + ty * 2) else: translation_y = 0 if flag & 0x40: (translation_z,) = struct.unpack_from("<h", data, offset_data + offset_frames_translation + tz * num_frames * 2 + j * 2) elif tz != 0xFF: (translation_z,) = struct.unpack_from("<h", data, offset_data + offset_static_translation + tz * 2) else: translation_z = 0 channel.append(((translation_x, translation_y, translation_z), (rotation_x, rotation_y, rotation_z))) animation.append(channel) animations.append(animation) return animationsdef loadBcx(name): data = loadLzs(name) (size, header_offset) = struct.unpack_from("<II", data, 0) assert(size == len(data)) (u1, num_bones, num_parts, num_animations, u2, u3, u4, offset_skeleton) = struct.unpack_from("<HBBB19sHHI", data, header_offset) offset_skeleton &= 0x7FFFFFFF offset_parts = offset_skeleton + num_bones * 4 offset_animations = offset_parts + num_parts * 32 # print "header", (header_offset, u1, num_bones, num_parts, num_animations, u2, u3, u4, offset_skeleton, offset_parts, offset_animations) object = {} object["model"] = readModel(data, num_bones, offset_skeleton, num_parts, offset_parts) object["animations"] = readAnimations(data, num_animations, offset_animations) return object def loadBsx(name): # get compressed file data = loadLzs(name) (size, header_offset) = struct.unpack_from("<II", data, 0) assert(size == len(data)) # decode individual objects object_list = [] (u1, num_models) = struct.unpack_from("<II", data, header_offset) for i in xrange(num_models): model_offset = header_offset + 0x10 + i * 0x30 (model_id,u2,u3,offset_skeleton,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15,u16,u17,u18,u19,u20,num_bones,u22,u23,u24,u25,u26,u27,u28,u29,u30,u31,u32,num_parts,u34,u35,u36,u37,u38,u39,u40,u41,u42,u43,u44,num_animations) = struct.unpack_from("<HBBHBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", data, model_offset) # print (model_id,u2,u3,offset_skeleton,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15,u16,u17,u18,u19,u20,num_bones,u22,u23,u24,u25,u26,u27,u28,u29,u30,u31,u32,num_parts,u34,u35,u36,u37,u38,u39,u40,u41,u42,u43,u44,num_animations) offset_skeleton += model_offset offset_parts = offset_skeleton + num_bones * 4 offset_animations = offset_parts + num_parts * 32 if model_id == 0x0100: object = loadBcx(bcxMap["cloud"]) elif model_id == 0x0302: object = loadBcx(bcxMap["ballet"]) elif model_id == 0x0403: object = loadBcx(bcxMap["tifa"]) elif model_id == 0x0605: object = loadBcx(bcxMap["cid"]) elif model_id == 0x0706: object = loadBcx(bcxMap["yufi"]) elif num_parts > 0: object = {} object["model"] = readModel(data, num_bones, offset_skeleton, num_parts, offset_parts) object["animations"] = [] else: print "unknown model id: %x" % model_id object["animations"].extend(readAnimations(data, num_animations, offset_animations)) object_list.append(object) return object_list class OpenGLObject: def __init__(self, object): self.object = object self.time = 0 self.animation = 0 self.draw_normals = False self.draw_skeleton = True self.part_map = {} model = object["model"] for idx, part in enumerate(model["parts"]): self.part_map[part["bone_index"]] = idx def nextAnim(self): self.animation = (self.animation + 1) % len(self.object["animations"]) self.time = 0 def drawPart(self, part): glEnable(GL_TEXTURE_2D) glBegin(GL_TRIANGLES) for tri in part["tri_color_tex"]: for i in xrange(3): pos = part["vertices"][tri[i][0]] texcoord = part["texcoords"][tri[i][1]] col = tri[i][2] glTexCoord2i(texcoord[0], texcoord[1]) glColor3ub(col[0], col[1], col[2]) glVertex3i(pos[0], pos[1], pos[2]) for tri in part["tri_mono_tex"]: col = tri[3] glColor3ub(col[0], col[1], col[2]) for i in xrange(3): pos = part["vertices"][tri[i][0]] texcoord = part["texcoords"][tri[i][1]] glTexCoord2i(texcoord[0], texcoord[1]) glColor3ub(col[0], col[1], col[2]) glVertex3i(pos[0], pos[1], pos[2]) glEnd() glBegin(GL_QUADS) for quad in part["quad_color_tex"]: for i in xrange(4): pos = part["vertices"][quad[i][0]] texcoord = part["texcoords"][quad[i][1]] col = quad[i][2] glTexCoord2i(texcoord[0], texcoord[1]) glColor3ub(col[0], col[1], col[2]) glVertex3i(pos[0], pos[1], pos[2]) for quad in part["quad_mono_tex"]: col = quad[4] glColor3ub(col[0], col[1], col[2]) for i in xrange(4): pos = part["vertices"][quad[i][0]] texcoord = part["texcoords"][quad[i][1]] glTexCoord2i(texcoord[0], texcoord[1]) glVertex3i(pos[0], pos[1], pos[2]) glEnd() glDisable(GL_TEXTURE_2D) glBegin(GL_TRIANGLES) for tri in part["tri_color"]: for i in xrange(3): pos = part["vertices"][tri[i][0]] col = tri[i][1] glColor3ub(col[0], col[1], col[2]) glVertex3i(pos[0], pos[1], pos[2]) for tri in part["tri_mono"]: col = tri[3] glColor3ub(col[0], col[1], col[2]) for i in xrange(3): pos = part["vertices"][tri[i][0]] glVertex3i(pos[0], pos[1], pos[2]) glEnd() glBegin(GL_QUADS) for quad in part["quad_color"]: for i in xrange(4): pos = part["vertices"][quad[i][0]] col = quad[i][1] glColor3ub(col[0], col[1], col[2]) glVertex3i(pos[0], pos[1], pos[2]) for quad in part["quad_mono"]: col = quad[4] glColor3ub(col[0], col[1], col[2]) for i in xrange(4): pos = part["vertices"][quad[i][0]] glVertex3i(pos[0], pos[1], pos[2]) glEnd() # draw normals (I'm not sure about the mono primitives. Maybe they're not lit at all?) if self.draw_normals: global normals glBegin(GL_LINES) glColor3f(1,1,0) for quad in part["quad_mono_tex"]: for i in xrange(4): pos = part["vertices"][quad[i][0]] nrm = normals[quad[5]] glVertex3i(pos[0], pos[1], pos[2]) glVertex3i(int(pos[0]+nrm[0]*50), int(pos[1]+nrm[1]*50), int(pos[2]+nrm[2]*50)) for tri in part["tri_mono_tex"]: for i in xrange(3): pos = part["vertices"][tri[i][0]] nrm = normals[tri[4]] glVertex3i(pos[0], pos[1], pos[2]) glVertex3i(int(pos[0]+nrm[0]*50), int(pos[1]+nrm[1]*50), int(pos[2]+nrm[2]*50)) for quad in part["quad_mono"]: for i in xrange(4): pos = part["vertices"][quad[i][0]] nrm = normals[quad[5]] glVertex3i(pos[0], pos[1], pos[2]) glVertex3i(int(pos[0]+nrm[0]*50), int(pos[1]+nrm[1]*50), int(pos[2]+nrm[2]*50)) for tri in part["tri_mono"]: for i in xrange(3): pos = part["vertices"][tri[i][0]] nrm = normals[tri[4]] glVertex3i(pos[0], pos[1], pos[2]) glVertex3i(int(pos[0]+nrm[0]*50), int(pos[1]+nrm[1]*50), int(pos[2]+nrm[2]*50)) for quad in part["quad_color_tex"]: for i in xrange(4): pos = part["vertices"][quad[i][0]] nrm = normals[quad[i][3]] glVertex3i(pos[0], pos[1], pos[2]) glVertex3i(int(pos[0]+nrm[0]*50), int(pos[1]+nrm[1]*50), int(pos[2]+nrm[2]*50)) for tri in part["tri_color_tex"]: for i in xrange(3): pos = part["vertices"][tri[i][0]] nrm = normals[tri[i][3]] glVertex3i(pos[0], pos[1], pos[2]) glVertex3i(int(pos[0]+nrm[0]*50), int(pos[1]+nrm[1]*50), int(pos[2]+nrm[2]*50)) for quad in part["quad_color"]: for i in xrange(4): pos = part["vertices"][quad[i][0]] nrm = normals[quad[i][2]] glVertex3i(pos[0], pos[1], pos[2]) glVertex3i(int(pos[0]+nrm[0]*50), int(pos[1]+nrm[1]*50), int(pos[2]+nrm[2]*50)) for tri in part["tri_color"]: for i in xrange(3): pos = part["vertices"][tri[i][0]] nrm = normals[tri[i][2]] glVertex3i(pos[0], pos[1], pos[2]) glVertex3i(int(pos[0]+nrm[0]*50), int(pos[1]+nrm[1]*50), int(pos[2]+nrm[2]*50)) glEnd() def draw(self): glMatrixMode(GL_MODELVIEW) glRotatef(180, 1, 0, 0) model = self.object["model"] animation = self.object["animations"][self.animation] skeleton = model["skeleton"] self.time = (self.time + 1) % len(animation[0]) parent = [-1] for idx, bone in enumerate(model["skeleton"]): ((translation_x, translation_y, translation_z), (rotation_x, rotation_y, rotation_z)) = animation[idx][self.time] while parent[-1] != bone[1]: parent.pop() glPopMatrix() parent.append(idx) glPushMatrix() if self.draw_skeleton: glBegin(GL_LINES) glColor3f(1.0, 0.0, 1.0) glVertex3i(0, 0, 0) glVertex3i(0, 0, bone[0]) glColor3f(1.0, 0.0, 0.0) glVertex3i(0, 0, 0) glVertex3i(50, 0, 0) glColor3f(0.0, 1.0, 0.0) glVertex3i(0, 0, 0) glVertex3i(0, 50, 0) glColor3f(0.0, 0.0, 1.0) glVertex3i(0, 0, 0) glVertex3i(0, 0, 50) glEnd() glTranslatef(0, 0, bone[0]) glRotatef(rotation_y, 0, 1, 0) glRotatef(rotation_x, 1, 0, 0) glRotatef(rotation_z, 0, 0, 1) glTranslatef(translation_x, translation_y, translation_z) if bone[2]: self.drawPart(model["parts"][self.part_map[idx]]) while parent[-1] != -1: parent.pop() glPopMatrix() def main(*argv): pygame.init() screen = pygame.display.set_mode((640, 480), HWSURFACE|DOUBLEBUF|OPENGL) glViewport(0, 0, 640, 480) glMatrixMode(GL_PROJECTION) glLoadIdentity() kFOVy = 0.57735 kZNear = 1000.0 kZFar = 3000.0 aspect = (640.0 / 480.0) * kZNear * kFOVy glFrustum(-aspect, aspect, -(kZNear * kFOVy), (kZNear * kFOVy), kZNear, kZFar) glEnable(GL_DEPTH_TEST) glDepthFunc(GL_LEQUAL) glDisable(GL_CULL_FACE) glDisable(GL_LIGHTING) glDisable(GL_BLEND) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) filePath = os.path.abspath(argv[0]) # get list of BCX, which should be in the same dir as the .bsx # (This is done this way to support operating sytems with a case-sensitive file systems.) global bcxMap bcxMap = {} fieldInfo = {} dir = os.path.dirname(filePath) for path in os.listdir(dir): file = os.path.splitext(path) if file[1].lower() == ".bcx": bcxMap[file[0].lower()] = os.path.join(dir, path) elif path.lower() == "field.bin": fieldInfo["field.bin"] = os.path.join(dir, path) elif path.lower() == "field.tdb": fieldInfo["field.tdb"] = os.path.join(dir, path) # get normals fieldBin = loadFieldBin(fieldInfo["field.bin"]) global normals normals = [] for i in xrange(240): n = struct.unpack_from("<hhh", fieldBin, 0x3f520 + i * 8) normals.append(vecNormal(n)) # load BCX or BSX file if os.path.splitext(os.path.basename(filePath))[1].lower() == ".bcx": data = [loadBcx(filePath)] else: data = loadBsx(filePath) clock = pygame.time.Clock() key_up = key_down = key_left = key_right = False rot_x = 0.0 rot_y = 0.0 cur_model = 0 object = OpenGLObject(data[cur_model]) wireframe = True while True: for event in pygame.event.get(): if event.type == QUIT: exit() elif event.type == KEYDOWN: if event.key == pygame.K_UP: key_up = True elif event.key == pygame.K_DOWN: key_down = True elif event.key == pygame.K_LEFT: key_left = True elif event.key == pygame.K_RIGHT: key_right = True elif event.key == pygame.K_ESCAPE: exit() elif event.key == pygame.K_a: object.nextAnim() elif event.key == pygame.K_n: object.draw_normals = not object.draw_normals elif event.key == pygame.K_s: object.draw_skeleton = not object.draw_skeleton elif event.key == pygame.K_m: cur_model = (cur_model + 1) % len(data) del object object = OpenGLObject(data[cur_model]) elif event.key == pygame.K_w: wireframe = not wireframe if wireframe: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) else: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) elif event.type == KEYUP: if event.key == pygame.K_UP: key_up = False elif event.key == pygame.K_DOWN: key_down = False elif event.key == pygame.K_LEFT: key_left = False elif event.key == pygame.K_RIGHT: key_right = False if key_up: rot_x += 1.0 elif key_down: rot_x -= 1.0 if key_left: rot_y += 1.0 elif key_right: rot_y -= 1.0 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glMatrixMode(GL_PROJECTION) glLoadIdentity() glFrustum(-aspect, aspect, -(kZNear * kFOVy), (kZNear * kFOVy), kZNear, 100000.0) glTranslatef(0, 0, -2000) glRotatef(rot_y * 360.0 / 256.0, 0.0, 1.0, 0.0) glRotatef(rot_x * 360.0 / 256.0, 1.0, 0.0, 0.0) glMatrixMode(GL_MODELVIEW) glLoadIdentity() object.draw() clock.tick(60) pygame.display.flip()if __name__ == "__main__": main(*sys.argv[1:])