changeset 108:2a03940deea3

Move everything graphical to pytouhou.opengl!
author Thibaut Girka <thib@sitedethib.com>
date Tue, 06 Sep 2011 00:26:13 +0200
parents 5d9052b9a4e8
children e93a7ed4f203
files eclviewer.py pytouhou/game/background.py pytouhou/game/bullet.py pytouhou/game/enemy.py pytouhou/game/sprite.py pytouhou/opengl/background.py pytouhou/opengl/gamerenderer.py pytouhou/opengl/sprite.py
diffstat 8 files changed, 302 insertions(+), 255 deletions(-) [+]
line wrap: on
line diff
--- a/eclviewer.py
+++ b/eclviewer.py
@@ -16,68 +16,30 @@
 import sys
 import os
 
-import struct
-from math import degrees, radians
-from itertools import chain
-
 import pygame
 
 from pytouhou.resource.loader import Loader
 from pytouhou.game.background import Background
-from pytouhou.opengl.texture import TextureManager
+from pytouhou.opengl.gamerenderer import GameRenderer
 from pytouhou.game.game import Game
 from pytouhou.game.player import Player
 
-import OpenGL
-OpenGL.FORWARD_COMPATIBLE_ONLY = True
-from OpenGL.GL import *
-from OpenGL.GLU import *
-
 
 def main(path, stage_num):
-    # Initialize pygame
-    pygame.init()
-    window = pygame.display.set_mode((384, 448), pygame.OPENGL | pygame.DOUBLEBUF)
-
-    # Initialize OpenGL
-    glMatrixMode(GL_PROJECTION)
-    glLoadIdentity()
-    gluPerspective(30, float(window.get_width())/window.get_height(), 101010101./2010101., 101010101./10101.)
-
-    glEnable(GL_BLEND)
-    glEnable(GL_TEXTURE_2D)
-    glEnable(GL_FOG)
-    glHint(GL_FOG_HINT, GL_NICEST)
-    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
-    glEnableClientState(GL_COLOR_ARRAY)
-    glEnableClientState(GL_VERTEX_ARRAY)
-    glEnableClientState(GL_TEXTURE_COORD_ARRAY)
-
     resource_loader = Loader()
-    texture_manager = TextureManager(resource_loader)
     resource_loader.scan_archives(os.path.join(path, name)
                                     for name in ('CM.DAT', 'ST.DAT'))
     game = Game(resource_loader, [Player()], stage_num, 3, 16)
 
-    # Load common data
-    etama_anm_wrappers = (resource_loader.get_anm_wrapper(('etama3.anm',)),
-                          resource_loader.get_anm_wrapper(('etama4.anm',)))
-    effects_anm_wrapper = resource_loader.get_anm_wrapper(('eff00.anm',))
-
     # Load stage data
     stage = resource_loader.get_stage('stage%d.std' % stage_num)
-    enemies_anm_wrapper = resource_loader.get_anm_wrapper2(('stg%denm.anm' % stage_num,
-                                                            'stg%denm2.anm' % stage_num))
 
     background_anm_wrapper = resource_loader.get_anm_wrapper(('stg%dbg.anm' % stage_num,))
     background = Background(stage, background_anm_wrapper)
 
-    # Preload textures
-    for anm_wrapper in chain(etama_anm_wrappers,
-                             (background_anm_wrapper, enemies_anm_wrapper,
-                              effects_anm_wrapper)):
-        texture_manager.preload(anm_wrapper)
+    # Renderer
+    renderer = GameRenderer(resource_loader, game, background)
+    renderer.start()
 
     # Let's go!
     print(stage.name)
@@ -99,78 +61,10 @@ def main(path, stage_num):
         game.run_iter(keystate)
 
         # Draw everything
-#            glClearColor(0.0, 0.0, 1.0, 0)
-        glClear(GL_DEPTH_BUFFER_BIT)
-
-        fog_b, fog_g, fog_r, _, fog_start, fog_end = background.fog_interpolator.values
-        x, y, z = background.position_interpolator.values
-        dx, dy, dz = background.position2_interpolator.values
-
-        glFogi(GL_FOG_MODE, GL_LINEAR)
-        glFogf(GL_FOG_START, fog_start)
-        glFogf(GL_FOG_END,  fog_end)
-        glFogfv(GL_FOG_COLOR, (fog_r / 255., fog_g / 255., fog_b / 255., 1.))
-
-        #TODO
-        glMatrixMode(GL_MODELVIEW)
-        glLoadIdentity()
-        # Some explanations on the magic constants:
-        # 192. = 384. / 2. = width / 2.
-        # 224. = 448. / 2. = height / 2.
-        # 835.979370 = 224./math.tan(math.radians(15)) = (height/2.)/math.tan(math.radians(fov/2))
-        # This is so that objects on the (O, x, y) plane use pixel coordinates
-        gluLookAt(192., 224., - 835.979370 * dz,
-                  192. + dx, 224. - dy, 0., 0., -1., 0.)
-        glTranslatef(-x, -y, -z)
-
-        glEnable(GL_DEPTH_TEST)
-        for (texture_key, blendfunc), (nb_vertices, vertices, uvs, colors) in background.objects_by_texture.items():
-            glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
-            glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key])
-            glVertexPointer(3, GL_FLOAT, 0, vertices)
-            glTexCoordPointer(2, GL_FLOAT, 0, uvs)
-            glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors)
-            glDrawArrays(GL_QUADS, 0, nb_vertices)
-        glDisable(GL_DEPTH_TEST)
-
-        #TODO
-        glMatrixMode(GL_MODELVIEW)
-        glLoadIdentity()
-        # Some explanations on the magic constants:
-        # 192. = 384. / 2. = width / 2.
-        # 224. = 448. / 2. = height / 2.
-        # 835.979370 = 224./math.tan(math.radians(15)) = (height/2.)/math.tan(math.radians(fov/2))
-        # This is so that objects on the (O, x, y) plane use pixel coordinates
-        gluLookAt(192., 224., - 835.979370,
-                  192., 224., 0., 0., -1., 0.)
-
-        glDisable(GL_FOG)
-        objects_by_texture = {}
-        for enemy in game.enemies:
-            enemy.get_objects_by_texture(objects_by_texture)
-        for (texture_key, blendfunc), (vertices, uvs, colors) in objects_by_texture.items():
-            nb_vertices = len(vertices)
-            glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
-            glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key])
-            glVertexPointer(3, GL_FLOAT, 0, struct.pack(str(3 * nb_vertices) + 'f', *chain(*vertices)))
-            glTexCoordPointer(2, GL_FLOAT, 0, struct.pack(str(2 * nb_vertices) + 'f', *chain(*uvs)))
-            glColorPointer(4, GL_UNSIGNED_BYTE, 0, struct.pack(str(4 * nb_vertices) + 'B', *chain(*colors)))
-            glDrawArrays(GL_QUADS, 0, nb_vertices)
-
-        objects_by_texture = {}
-        for bullet in game.game_state.bullets:
-            bullet.get_objects_by_texture(objects_by_texture)
-        for (texture_key, blendfunc), (vertices, uvs, colors) in objects_by_texture.items():
-            nb_vertices = len(vertices)
-            glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
-            glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key])
-            glVertexPointer(3, GL_FLOAT, 0, struct.pack(str(3 * nb_vertices) + 'f', *chain(*vertices)))
-            glTexCoordPointer(2, GL_FLOAT, 0, struct.pack(str(2 * nb_vertices) + 'f', *chain(*uvs)))
-            glColorPointer(4, GL_UNSIGNED_BYTE, 0, struct.pack(str(4 * nb_vertices) + 'B', *chain(*colors)))
-            glDrawArrays(GL_QUADS, 0, nb_vertices)
-        glEnable(GL_FOG)
+        renderer.render()
 
         pygame.display.flip()
+
         clock.tick(120)
 
 
--- a/pytouhou/game/background.py
+++ b/pytouhou/game/background.py
@@ -13,11 +13,6 @@
 ##
 
 
-from io import BytesIO
-import os
-import struct
-from itertools import chain
-
 from pytouhou.utils.interpolator import Interpolator
 from pytouhou.vm.anmrunner import ANMRunner
 from pytouhou.game.sprite import Sprite
@@ -27,9 +22,10 @@ class Background(object):
     def __init__(self, stage, anm_wrapper):
         self.stage = stage
         self.anm_wrapper = anm_wrapper
+
         self.models = []
         self.object_instances = []
-        self.objects_by_texture = {}
+        self.anm_runners = []
 
         self.position_interpolator = Interpolator((0, 0, 0))
         self.fog_interpolator = Interpolator((0, 0, 0, 0, 0))
@@ -41,63 +37,31 @@ class Background(object):
 
     def build_object_instances(self):
         self.object_instances = []
-        for obj, ox, oy, oz in self.stage.object_instances:
-
-            obj_instance = []
-            for face_vertices, face_uvs, face_colors in self.models[obj]:
-                obj_instance.append((tuple((x + ox, y + oy, z + oz)
-                                        for x, y, z in face_vertices),
-                                    face_uvs,
-                                    face_colors))
-            self.object_instances.append(obj_instance)
+        for model_id, ox, oy, oz in self.stage.object_instances:
+            self.object_instances.append((ox, oy, oz, model_id, self.models[model_id]))
         # Z-sorting
         def keyfunc(obj):
-            return min(z for face in obj for x, y, z in face[0])
+            bounding_box = self.stage.models[obj[3]].bounding_box
+            return obj[2] + min(bounding_box[2], bounding_box[5])
         self.object_instances.sort(key=keyfunc, reverse=True)
 
 
-    def object_instances_to_vertices_uvs_colors(self):
-        vertices = tuple(vertex for obj in self.object_instances
-                            for face in obj for vertex in face[0])
-        uvs = tuple(uv for obj in self.object_instances
-                            for face in obj for uv in face[1])
-        colors = tuple(color for obj in self.object_instances
-                            for face in obj for color in face[2])
-        return vertices, uvs, colors
-
-
     def build_models(self):
         self.models = []
-        for i, obj in enumerate(self.stage.models):
-            faces = []
+        for obj in self.stage.models:
+            quads = []
             for script_index, ox, oy, oz, width_override, height_override in obj.quads:
                 #TODO: per-texture rendering
                 sprite = Sprite()
                 anm_runner = ANMRunner(self.anm_wrapper, script_index, sprite)
                 anm_runner.run_frame()
                 sprite.update(width_override, height_override)
-                if sprite._changed:
-                    sprite.update_vertices_uvs_colors()
-                uvs, vertices = sprite._uvs, tuple((x + ox, y + oy, z + oz) for x, y, z in sprite._vertices)
-                colors = sprite._colors
-                faces.append((vertices, uvs, colors))
-            self.models.append(faces)
+                quads.append((ox, oy, oz, width_override, height_override, sprite))
+                self.anm_runners.append(anm_runner)
+            self.models.append(quads)
 
 
     def update(self, frame):
-        if not self.objects_by_texture:
-            vertices, uvs, colors = self.object_instances_to_vertices_uvs_colors()
-            nb_vertices = len(vertices)
-            vertices_format = 'f' * (3 * nb_vertices)
-            uvs_format = 'f' * (2 * nb_vertices)
-            colors_format = 'B' * (4 * nb_vertices)
-            vertices = struct.pack(vertices_format, *chain(*vertices))
-            uvs = struct.pack(uvs_format, *chain(*uvs))
-            colors = struct.pack(colors_format, *chain(*colors))
-            assert len(self.anm_wrapper.anm_files) == 1 #TODO
-            anm = self.anm_wrapper.anm_files[0]
-            self.objects_by_texture = {((anm.first_name, anm.secondary_name), 0): (nb_vertices, vertices, uvs, colors)} #TODO: blendfunc
-
         for frame_num, message_type, args in self.stage.script:
             if frame_num == frame:
                 if message_type == 0:
@@ -116,6 +80,14 @@ class Background(object):
                 self.position_interpolator.set_interpolation_end(frame_num, args)
                 break
 
+        for anm_runner in tuple(self.anm_runners):
+            if not anm_runner.run_frame():
+                self.anm_runners.remove(anm_runner)
+
+        for model in self.models:
+            for ox, oy, oz, width_override, height_override, sprite in model:
+                sprite.update(width_override, height_override)
+
         self.position2_interpolator.update(frame)
         self.fog_interpolator.update(frame)
         self.position_interpolator.update(frame)
--- a/pytouhou/game/bullet.py
+++ b/pytouhou/game/bullet.py
@@ -98,18 +98,6 @@ class Bullet(object):
         return True
 
 
-    def get_objects_by_texture(self, objects_by_texture):
-        sprite = self._sprite
-        sprite.update_vertices_uvs_colors()
-        key = sprite.anm.first_name, sprite.anm.secondary_name
-        key = (key, sprite.blendfunc)
-        rec = objects_by_texture.setdefault(key, ([], [], []))
-        vertices = ((x + self.x, y + self.y, z) for x, y, z in sprite._vertices)
-        rec[0].extend(vertices)
-        rec[1].extend(sprite._uvs)
-        rec[2].extend(sprite._colors)
-
-
     def set_anim(self, anim_idx=None, sprite_idx_offset=None):
         if anim_idx is not None:
             self.anim_idx = anim_idx
--- a/pytouhou/game/enemy.py
+++ b/pytouhou/game/enemy.py
@@ -184,22 +184,6 @@ class Enemy(object):
         return True
 
 
-    def get_objects_by_texture(self, objects_by_texture):
-        if not self._sprite:
-            return
-
-        sprite = self._sprite
-        sprite.update_vertices_uvs_colors()
-
-        key = sprite.anm.first_name, sprite.anm.secondary_name
-        key = (key, sprite.blendfunc)
-        rec = objects_by_texture.setdefault(key, ([], [], []))
-        vertices = ((x + self.x, y + self.y, z) for x, y, z in sprite._vertices)
-        rec[0].extend(vertices)
-        rec[1].extend(sprite._uvs)
-        rec[2].extend(sprite._colors)
-
-
     def update(self):
         x, y = self.x, self.y
         if self.interpolator:
--- a/pytouhou/game/sprite.py
+++ b/pytouhou/game/sprite.py
@@ -12,9 +12,7 @@
 ## GNU General Public License for more details.
 ##
 
-from math import pi
 
-from pytouhou.utils.matrix import Matrix
 from pytouhou.utils.interpolator import Interpolator
 
 
@@ -50,9 +48,8 @@ class Sprite(object):
         self.frame = 0
         self.color = (255, 255, 255)
         self.alpha = 255
-        self._uvs = []
-        self._vertices = []
-        self._colors = []
+
+        self._rendering_data = None
 
 
     def fade(self, duration, alpha, formula):
@@ -76,69 +73,6 @@ class Sprite(object):
             self.offset_interpolator.set_interpolation_end(self.frame + duration - 1, (x, y, z))
 
 
-    def update_vertices_uvs_colors(self):
-        if not self._changed:
-            return
-
-        if self.fade_interpolator:
-            self.fade_interpolator.update(self.frame)
-            self.alpha = int(self.fade_interpolator.values[0])
-
-        if self.scale_interpolator:
-            self.scale_interpolator.update(self.frame)
-            self.rescale = self.scale_interpolator.values
-
-        if self.offset_interpolator:
-            self.offset_interpolator.update(self.frame)
-            self.dest_offset = self.offset_interpolator.values
-
-        vertmat = Matrix([[-.5,     .5,     .5,    -.5],
-                          [-.5,    -.5,     .5,     .5],
-                          [ .0,     .0,     .0,     .0],
-                          [ 1.,     1.,     1.,     1.]])
-
-        tx, ty, tw, th = self.texcoords
-        sx, sy = self.rescale
-        width = self.width_override or (tw * sx)
-        height = self.height_override or (th * sy)
-
-        vertmat.scale2d(width, height)
-        if self.mirrored:
-            vertmat.flip()
-
-        rx, ry, rz = self.rotations_3d
-        if self.automatic_orientation:
-            rz += pi/2. - self.angle
-        elif self.force_rotation:
-            rz += self.angle
-
-        if (rx, ry, rz) != (0., 0., 0.):
-            if rx:
-                vertmat.rotate_x(-rx)
-            if ry:
-                vertmat.rotate_y(ry)
-            if rz:
-                vertmat.rotate_z(-rz) #TODO: minus, really?
-        if self.corner_relative_placement: # Reposition
-            vertmat.translate(width / 2., height / 2., 0.)
-        if self.allow_dest_offset:
-            vertmat.translate(*self.dest_offset)
-
-        x_1 = 1. / self.anm.size[0]
-        y_1 = 1. / self.anm.size[1]
-        tox, toy = self.texoffsets
-        uvs = [(tx * x_1 + tox,         1. - (ty * y_1) + toy),
-               ((tx + tw) * x_1 + tox,  1. - (ty * y_1) + toy),
-               ((tx + tw) * x_1 + tox,  1. - ((ty + th) * y_1 + toy)),
-               (tx * x_1 + tox,         1. - ((ty + th) * y_1 + toy))]
-
-        d = vertmat.data
-        assert (d[3][0], d[3][1], d[3][2], d[3][3]) == (1., 1., 1., 1.)
-        self._colors = [(self.color[0], self.color[1], self.color[2], self.alpha)] * 4
-        self._uvs, self._vertices = uvs, zip(d[0], d[1], d[2])
-        self._changed = False
-
-
     def update(self, override_width=0, override_height=0, angle_base=0., force_rotation=False):
         if (override_width != self.width_override
             or override_height != self.height_override
new file mode 100644
--- /dev/null
+++ b/pytouhou/opengl/background.py
@@ -0,0 +1,46 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+#TODO: lots of things
+
+from struct import pack
+from itertools import chain
+
+from pytouhou.opengl.sprite import get_sprite_rendering_data
+
+def get_background_rendering_data(background):
+    #TODO
+    try:
+        return background._rendering_data
+    except AttributeError:
+        pass
+
+    vertices = []
+    uvs = []
+    colors = []
+    for ox, oy, oz, model_id, model in background.object_instances:
+        for ox2, oy2, oz2, width_override, height_override, sprite in model:
+            key, (vertices2, uvs2, colors2) = get_sprite_rendering_data(sprite)
+            vertices.extend((x + ox + ox2, y + oy + oy2, z + oz + oz2) for x, y, z in vertices2)
+            uvs.extend(uvs2)
+            colors.extend(colors2)
+
+    nb_vertices = len(vertices)
+    vertices = pack(str(3 * nb_vertices) + 'f', *chain(*vertices))
+    uvs = pack(str(2 * nb_vertices) + 'f', *chain(*uvs))
+    colors = pack(str(4 * nb_vertices) + 'B', *chain(*colors))
+
+    background._rendering_data = [(key, (nb_vertices, vertices, uvs, colors))]
+
+    return background._rendering_data
new file mode 100644
--- /dev/null
+++ b/pytouhou/opengl/gamerenderer.py
@@ -0,0 +1,143 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+import struct
+from itertools import chain
+
+import pygame
+
+import OpenGL
+OpenGL.FORWARD_COMPATIBLE_ONLY = True
+from OpenGL.GL import *
+from OpenGL.GLU import *
+
+
+from pytouhou.opengl.texture import TextureManager
+from pytouhou.opengl.sprite import get_sprite_rendering_data
+from pytouhou.opengl.background import get_background_rendering_data
+
+
+class GameRenderer(object):
+    def __init__(self, resource_loader, game=None, background=None):
+        self.texture_manager = TextureManager(resource_loader)
+
+        self.game = game
+        self.background = background
+
+        self.window = None
+
+
+    def start(self, width=384, height=448):
+        # Initialize pygame
+        pygame.init()
+        self.window = pygame.display.set_mode((width, height),
+                                              pygame.OPENGL | pygame.DOUBLEBUF)
+
+        # Initialize OpenGL
+        glMatrixMode(GL_PROJECTION)
+        glLoadIdentity()
+        gluPerspective(30, float(width)/float(height),
+                       101010101./2010101., 101010101./10101.)
+
+        glEnable(GL_BLEND)
+        glEnable(GL_TEXTURE_2D)
+        glEnable(GL_FOG)
+        glHint(GL_FOG_HINT, GL_NICEST)
+        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
+        glEnableClientState(GL_COLOR_ARRAY)
+        glEnableClientState(GL_VERTEX_ARRAY)
+        glEnableClientState(GL_TEXTURE_COORD_ARRAY)
+
+
+    def render_elements(self, elements):
+        texture_manager = self.texture_manager
+        objects_by_texture = {}
+        for element in elements:
+            sprite = element._sprite
+            if sprite:
+                ox, oy = element.x, element.y
+                key, (vertices, uvs, colors) = get_sprite_rendering_data(sprite)
+                rec = objects_by_texture.setdefault(key, ([], [], []))
+                vertices = ((x + ox, y + oy, z) for x, y, z in vertices)
+                rec[0].extend(vertices)
+                rec[1].extend(uvs)
+                rec[2].extend(colors)
+
+        for (texture_key, blendfunc), (vertices, uvs, colors) in objects_by_texture.items():
+            nb_vertices = len(vertices)
+            glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
+            glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key])
+            glVertexPointer(3, GL_FLOAT, 0, struct.pack(str(3 * nb_vertices) + 'f', *chain(*vertices)))
+            glTexCoordPointer(2, GL_FLOAT, 0, struct.pack(str(2 * nb_vertices) + 'f', *chain(*uvs)))
+            glColorPointer(4, GL_UNSIGNED_BYTE, 0, struct.pack(str(4 * nb_vertices) + 'B', *chain(*colors)))
+            glDrawArrays(GL_QUADS, 0, nb_vertices)
+
+
+    def render(self):
+        glClear(GL_DEPTH_BUFFER_BIT)
+
+        back = self.background
+        game = self.game
+        texture_manager = self.texture_manager
+
+        if back is not None:
+            fog_b, fog_g, fog_r, _, fog_start, fog_end = back.fog_interpolator.values
+            x, y, z = back.position_interpolator.values
+            dx, dy, dz = back.position2_interpolator.values
+
+            glFogi(GL_FOG_MODE, GL_LINEAR)
+            glFogf(GL_FOG_START, fog_start)
+            glFogf(GL_FOG_END,  fog_end)
+            glFogfv(GL_FOG_COLOR, (fog_r / 255., fog_g / 255., fog_b / 255., 1.))
+
+            glMatrixMode(GL_MODELVIEW)
+            glLoadIdentity()
+            # Some explanations on the magic constants:
+            # 192. = 384. / 2. = width / 2.
+            # 224. = 448. / 2. = height / 2.
+            # 835.979370 = 224./math.tan(math.radians(15)) = (height/2.)/math.tan(math.radians(fov/2))
+            # This is so that objects on the (O, x, y) plane use pixel coordinates
+            gluLookAt(192., 224., - 835.979370 * dz,
+                      192. + dx, 224. - dy, 0., 0., -1., 0.)
+            glTranslatef(-x, -y, -z)
+
+            glEnable(GL_DEPTH_TEST)
+            for (texture_key, blendfunc), (nb_vertices, vertices, uvs, colors) in get_background_rendering_data(back):
+                glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
+                glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key])
+                glVertexPointer(3, GL_FLOAT, 0, vertices)
+                glTexCoordPointer(2, GL_FLOAT, 0, uvs)
+                glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors)
+                glDrawArrays(GL_QUADS, 0, nb_vertices)
+            glDisable(GL_DEPTH_TEST)
+        else:
+            glClear(GL_COLOR_BUFFER_BIT)
+
+
+        if game is not None:
+            glMatrixMode(GL_MODELVIEW)
+            glLoadIdentity()
+            # Some explanations on the magic constants:
+            # 192. = 384. / 2. = width / 2.
+            # 224. = 448. / 2. = height / 2.
+            # 835.979370 = 224./math.tan(math.radians(15)) = (height/2.)/math.tan(math.radians(fov/2))
+            # This is so that objects on the (O, x, y) plane use pixel coordinates
+            gluLookAt(192., 224., - 835.979370,
+                      192., 224., 0., 0., -1., 0.)
+
+            glDisable(GL_FOG)
+            self.render_elements(game.enemies)
+            self.render_elements(game.game_state.bullets)
+            glEnable(GL_FOG)
+
new file mode 100644
--- /dev/null
+++ b/pytouhou/opengl/sprite.py
@@ -0,0 +1,86 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+
+from math import pi
+
+from pytouhou.utils.matrix import Matrix
+
+
+def get_sprite_rendering_data(sprite):
+    if not sprite._changed:
+        return sprite._rendering_data
+
+    if sprite.fade_interpolator:
+        sprite.fade_interpolator.update(sprite.frame)
+        sprite.alpha = int(sprite.fade_interpolator.values[0])
+
+    if sprite.scale_interpolator:
+        sprite.scale_interpolator.update(sprite.frame)
+        sprite.rescale = sprite.scale_interpolator.values
+
+    if sprite.offset_interpolator:
+        sprite.offset_interpolator.update(sprite.frame)
+        sprite.dest_offset = sprite.offset_interpolator.values
+
+    vertmat = Matrix([[-.5,     .5,     .5,    -.5],
+                      [-.5,    -.5,     .5,     .5],
+                      [ .0,     .0,     .0,     .0],
+                      [ 1.,     1.,     1.,     1.]])
+
+    tx, ty, tw, th = sprite.texcoords
+    sx, sy = sprite.rescale
+    width = sprite.width_override or (tw * sx)
+    height = sprite.height_override or (th * sy)
+
+    vertmat.scale2d(width, height)
+    if sprite.mirrored:
+        vertmat.flip()
+
+    rx, ry, rz = sprite.rotations_3d
+    if sprite.automatic_orientation:
+        rz += pi/2. - sprite.angle
+    elif sprite.force_rotation:
+        rz += sprite.angle
+
+    if (rx, ry, rz) != (0., 0., 0.):
+        if rx:
+            vertmat.rotate_x(-rx)
+        if ry:
+            vertmat.rotate_y(ry)
+        if rz:
+            vertmat.rotate_z(-rz) #TODO: minus, really?
+    if sprite.corner_relative_placement: # Reposition
+        vertmat.translate(width / 2., height / 2., 0.)
+    if sprite.allow_dest_offset:
+        vertmat.translate(*sprite.dest_offset)
+
+    x_1 = 1. / sprite.anm.size[0]
+    y_1 = 1. / sprite.anm.size[1]
+    tox, toy = sprite.texoffsets
+    uvs = [(tx * x_1 + tox,         1. - (ty * y_1) + toy),
+           ((tx + tw) * x_1 + tox,  1. - (ty * y_1) + toy),
+           ((tx + tw) * x_1 + tox,  1. - ((ty + th) * y_1 + toy)),
+           (tx * x_1 + tox,         1. - ((ty + th) * y_1 + toy))]
+
+    d = vertmat.data
+    assert (d[3][0], d[3][1], d[3][2], d[3][3]) == (1., 1., 1., 1.)
+
+    key = (sprite.anm.first_name, sprite.anm.secondary_name), sprite.blendfunc
+    values = zip(d[0], d[1], d[2]), uvs, [(sprite.color[0], sprite.color[1], sprite.color[2], sprite.alpha)] * 4
+    sprite._rendering_data = key, values
+    sprite._changed = False
+
+    return sprite._rendering_data
+