changeset 205:ee6dfd14a785

Rename pytouhou.opengl to pytouhou.ui, makes much more sense that way.
author Thibaut Girka <thib@sitedethib.com>
date Tue, 01 Nov 2011 13:50:33 +0100
parents 88361534c77e
children eca53abdfeab
files eclviewer.py pytouhou/__init__.py pytouhou/opengl/__init__.py pytouhou/opengl/background.py pytouhou/opengl/gamerenderer.pyx pytouhou/opengl/gamerunner.py pytouhou/opengl/sprite.pxd pytouhou/opengl/sprite.pyx pytouhou/opengl/texture.py pytouhou/ui/__init__.py pytouhou/ui/background.py pytouhou/ui/gamerenderer.pyx pytouhou/ui/gamerunner.py pytouhou/ui/sprite.pxd pytouhou/ui/sprite.pyx pytouhou/ui/texture.py
diffstat 14 files changed, 493 insertions(+), 492 deletions(-) [+]
line wrap: on
line diff
--- a/eclviewer.py	Tue Nov 01 13:46:03 2011 +0100
+++ b/eclviewer.py	Tue Nov 01 13:50:33 2011 +0100
@@ -21,7 +21,7 @@
 
 from pytouhou.resource.loader import Loader
 from pytouhou.game.background import Background
-from pytouhou.opengl.gamerunner import GameRunner
+from pytouhou.ui.gamerunner import GameRunner
 from pytouhou.games.eosd import EoSDGame
 from pytouhou.game.player import PlayerState
 from pytouhou.formats.t6rp import T6RP
--- a/pytouhou/__init__.py	Tue Nov 01 13:46:03 2011 +0100
+++ b/pytouhou/__init__.py	Tue Nov 01 13:50:33 2011 +0100
@@ -8,6 +8,7 @@
 game -- game logic
 games -- game-specific classes
 resource -- resource loading
+ui -- user interface related classes
 utils -- various helpers and non Touhou-specific functions
 vm -- virtual machines for enemies, etc.
 """
--- a/pytouhou/opengl/background.py	Tue Nov 01 13:46:03 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-# -*- 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', *uvs)
-    colors = pack(str(4 * nb_vertices) + 'B', *colors)
-
-    background._rendering_data = [(key, (nb_vertices, vertices, uvs, colors))]
-
-    return background._rendering_data
--- a/pytouhou/opengl/gamerenderer.pyx	Tue Nov 01 13:46:03 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +0,0 @@
-# -*- 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 libc.stdlib cimport malloc, free
-
-import ctypes
-
-from struct import pack
-from itertools import chain
-
-from pyglet.gl import *
-
-from pytouhou.opengl.texture import TextureManager
-from pytouhou.opengl.sprite cimport get_sprite_rendering_data
-from pytouhou.opengl.background import get_background_rendering_data
-
-
-MAX_ELEMENTS = 10000
-
-
-cdef struct Vertex:
-    int x, y, z
-    float u, v
-    unsigned char r, g, b, a
-
-
-cdef class GameRenderer:
-    cdef public texture_manager
-    cdef public game
-    cdef public background
-
-    cdef Vertex *vertex_buffer
-
-
-    def __cinit__(self):
-        # Allocate buffers
-        self.vertex_buffer = <Vertex*> malloc(MAX_ELEMENTS * sizeof(Vertex))
-
-
-    def __dealloc__(self):
-        free(self.vertex_buffer)
-
-
-    def __init__(self, resource_loader, game=None, background=None):
-        self.texture_manager = TextureManager(resource_loader)
-
-        self.game = game
-        self.background = background
-
-
-    cdef render_elements(self, elements):
-        cdef unsigned short nb_vertices = 0
-
-        indices_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 = indices_by_texture.setdefault(key, [])
-
-                # Pack data in buffer
-                (x1, y1, z1), (x2, y2, z2), (x3, y3, z3), (x4, y4, z4) = vertices
-                r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3, r4, g4, b4, a4 = colors
-                u1, v1, u2, v2, u3, v3, u4, v4 = uvs
-                self.vertex_buffer[nb_vertices] = Vertex(x1 + ox, y1 + oy, z1, u1, v1, r1, g1, b1, a1)
-                self.vertex_buffer[nb_vertices+1] = Vertex(x2 + ox, y2 + oy, z2, u2, v2, r2, g2, b2, a2)
-                self.vertex_buffer[nb_vertices+2] = Vertex(x3 + ox, y3 + oy, z3, u3, v3, r3, g3, b3, a3)
-                self.vertex_buffer[nb_vertices+3] = Vertex(x4 + ox, y4 + oy, z4, u4, v4, r4, g4, b4, a4)
-
-                # Add indices
-                index = nb_vertices
-                rec.extend((index, index + 1, index + 2, index + 3))
-
-                nb_vertices += 4
-
-        for (texture_key, blendfunc), indices in indices_by_texture.items():
-            glVertexPointer(3, GL_INT, 24, <long> &self.vertex_buffer[0].x)
-            glTexCoordPointer(2, GL_FLOAT, 24, <long> &self.vertex_buffer[0].u)
-            glColorPointer(4, GL_UNSIGNED_BYTE, 24, <long> &self.vertex_buffer[0].r)
-
-            nb_indices = len(indices)
-            indices = pack(str(nb_indices) + 'H', *indices)
-            glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
-            glBindTexture(GL_TEXTURE_2D, self.texture_manager[texture_key].id)
-            glDrawElements(GL_QUADS, nb_indices, GL_UNSIGNED_SHORT, indices)
-
-
-    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, (GLfloat * 4)(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].id)
-                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.effects)
-            self.render_elements(chain(game.players_bullets,
-                                       game.players,
-                                       *(player.objects() for player in game.players)))
-            self.render_elements(chain(game.bullets, game.cancelled_bullets, game.items))
-            #TODO: display item indicators
-            glEnable(GL_FOG)
-
--- a/pytouhou/opengl/gamerunner.py	Tue Nov 01 13:46:03 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-# -*- 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 pyglet
-import traceback
-
-from pyglet.gl import (glMatrixMode, glLoadIdentity, glEnable,
-                       glHint, glEnableClientState, glViewport,
-                       gluPerspective, gluLookAt,
-                       GL_MODELVIEW, GL_PROJECTION,
-                       GL_TEXTURE_2D, GL_BLEND, GL_FOG,
-                       GL_PERSPECTIVE_CORRECTION_HINT, GL_FOG_HINT, GL_NICEST,
-                       GL_COLOR_ARRAY, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY)
-
-from pytouhou.opengl.gamerenderer import GameRenderer
-
-
-class GameRunner(pyglet.window.Window, GameRenderer):
-    def __init__(self, resource_loader, game=None, background=None, replay=None):
-        GameRenderer.__init__(self, resource_loader, game, background)
-        pyglet.window.Window.__init__(self, caption='PyTouhou', resizable=False)
-        self.replay_level = None
-        if not replay or not replay.levels[game.stage-1]:
-            self.keys = pyglet.window.key.KeyStateHandler()
-            self.push_handlers(self.keys)
-        else:
-            self.keys = 0
-            self.replay_level = replay.levels[game.stage-1]
-
-        self.fps_display = pyglet.clock.ClockDisplay()
-
-
-    def start(self, width=384, height=448):
-        self.set_size(width, height)
-
-        # 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)
-
-        # Use our own loop to ensure 60 (for now, 120) fps
-        pyglet.clock.set_fps_limit(120)
-        while not self.has_exit:
-            pyglet.clock.tick()
-            self.dispatch_events()
-            self.update()
-            self.on_draw()
-            self.flip()
-
-
-    def on_resize(self, width, height):
-        glViewport(0, 0, width, height)
-
-
-    def _event_text_symbol(self, ev):
-        # XXX: Ugly workaround to a pyglet bug on X11
-        #TODO: fix that bug in pyglet
-        try:
-            return pyglet.window.Window._event_text_symbol(self, ev)
-        except Exception as exc:
-            print('*WARNING* Pyglet error:')
-            traceback.print_exc(exc)
-            return None, None
-
-
-    def on_key_press(self, symbol, modifiers):
-        if symbol == pyglet.window.key.ESCAPE:
-            self.has_exit = True
-        # XXX: Fullscreen will be enabled the day pyglet stops sucking
-        elif symbol == pyglet.window.key.F11:
-            self.set_fullscreen(not self.fullscreen)
-
-
-    def update(self):
-        if self.background:
-            self.background.update(self.game.frame)
-        if self.game:
-            if not self.replay_level:
-                #TODO: allow user settings
-                keystate = 0
-                if self.keys[pyglet.window.key.W]:
-                    keystate |= 1
-                if self.keys[pyglet.window.key.X]:
-                    keystate |= 2
-                #TODO: on some configurations, LSHIFT is Shift_L when pressed
-                # and ISO_Prev_Group when released, confusing the hell out of pyglet
-                # and leading to a always-on LSHIFT...
-                if self.keys[pyglet.window.key.LSHIFT]:
-                    keystate |= 4
-                if self.keys[pyglet.window.key.UP]:
-                    keystate |= 16
-                if self.keys[pyglet.window.key.DOWN]:
-                    keystate |= 32
-                if self.keys[pyglet.window.key.LEFT]:
-                    keystate |= 64
-                if self.keys[pyglet.window.key.RIGHT]:
-                    keystate |= 128
-                if self.keys[pyglet.window.key.LCTRL]:
-                    keystate |= 256
-                self.game.run_iter(keystate)
-            else:
-                keystate = 0
-                for frame, _keystate, unknown in self.replay_level.keys:
-                    if self.game.frame < frame:
-                        break
-                    else:
-                        keystate = _keystate
-
-                self.game.run_iter(keystate)
-
-
-    def on_draw(self):
-        GameRenderer.render(self)
-
-        #TODO
-        glMatrixMode(GL_MODELVIEW)
-        glLoadIdentity()
-        gluLookAt(192., 224., 835.979370,
-                  192, 224., 0., 0., 1., 0.)
-        self.fps_display.draw()
-
--- a/pytouhou/opengl/sprite.pxd	Tue Nov 01 13:46:03 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-cpdef object get_sprite_rendering_data(object sprite)
--- a/pytouhou/opengl/sprite.pyx	Tue Nov 01 13:46:03 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-# -*- 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 cimport Matrix
-
-
-cpdef object get_sprite_rendering_data(object sprite):
-    cdef Matrix vertmat
-
-    if not sprite._changed:
-        return sprite._rendering_data
-
-    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[0], sprite.dest_offset[1], sprite.dest_offset[2])
-
-    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)]
-
-    (x1, x2 , x3, x4), (y1, y2, y3, y4), (z1, z2, z3, z4), _ = vertmat.data
-
-    key = (sprite.anm.first_name, sprite.anm.secondary_name), sprite.blendfunc
-    r, g, b = sprite.color
-    values = ((x1, y1, z1), (x2, y2, z2), (x3, y3, z3), (x4, y4, z4)), uvs, [r, g, b, sprite.alpha] * 4
-    sprite._rendering_data = key, values
-    sprite._changed = False
-
-    return sprite._rendering_data
-
--- a/pytouhou/opengl/texture.py	Tue Nov 01 13:46:03 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-# -*- 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 pyglet
-from pyglet.gl import (glTexParameteri,
-                       GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
-import os
-
-
-class TextureManager(object):
-    def __init__(self, loader=None):
-        self.loader = loader
-        self.textures = {}
-
-
-    def __getitem__(self, key):
-        if not key in self.textures:
-            self.textures[key] = self.load_texture(key)
-        return self.textures[key]
-
-
-    def preload(self, anm_wrapper):
-        try:
-            anms = anm_wrapper.anm_files
-        except AttributeError:
-            anms = anm_wrapper
-
-        for anm in anms:
-            key = anm.first_name, anm.secondary_name
-            texture = self[key]
-
-
-    def load_texture(self, key):
-        first_name, secondary_name = key
-
-        image_file = pyglet.image.load(first_name, file=self.loader.get_file(os.path.basename(first_name)))
-
-        if secondary_name:
-            alpha_file = pyglet.image.load(secondary_name, file=self.loader.get_file(os.path.basename(secondary_name)))
-            assert (image_file.width, image_file.height) == (alpha_file.width, image_file.height)
-
-            data = image_file.get_data('RGB', image_file.width * 3)
-            alpha_data = alpha_file.get_data('RGB', image_file.width * 3)
-            image_file = pyglet.image.ImageData(image_file.width, image_file.height, 'RGBA', b''.join(data[i*3:i*3+3] + alpha_data[i*3] for i in range(image_file.width * image_file.height)))
-
-            #TODO
-
-        texture = image_file.get_texture()
-
-        glTexParameteri(texture.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
-        glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
-
-        return texture
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/background.py	Tue Nov 01 13:50:33 2011 +0100
@@ -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 .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', *uvs)
+    colors = pack(str(4 * nb_vertices) + 'B', *colors)
+
+    background._rendering_data = [(key, (nb_vertices, vertices, uvs, colors))]
+
+    return background._rendering_data
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/gamerenderer.pyx	Tue Nov 01 13:50:33 2011 +0100
@@ -0,0 +1,161 @@
+# -*- 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 libc.stdlib cimport malloc, free
+
+import ctypes
+
+from struct import pack
+from itertools import chain
+
+from pyglet.gl import *
+
+from .texture import TextureManager
+from .sprite cimport get_sprite_rendering_data
+from .background import get_background_rendering_data
+
+
+MAX_ELEMENTS = 10000
+
+
+cdef struct Vertex:
+    int x, y, z
+    float u, v
+    unsigned char r, g, b, a
+
+
+cdef class GameRenderer:
+    cdef public texture_manager
+    cdef public game
+    cdef public background
+
+    cdef Vertex *vertex_buffer
+
+
+    def __cinit__(self):
+        # Allocate buffers
+        self.vertex_buffer = <Vertex*> malloc(MAX_ELEMENTS * sizeof(Vertex))
+
+
+    def __dealloc__(self):
+        free(self.vertex_buffer)
+
+
+    def __init__(self, resource_loader, game=None, background=None):
+        self.texture_manager = TextureManager(resource_loader)
+
+        self.game = game
+        self.background = background
+
+
+    cdef render_elements(self, elements):
+        cdef unsigned short nb_vertices = 0
+
+        indices_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 = indices_by_texture.setdefault(key, [])
+
+                # Pack data in buffer
+                (x1, y1, z1), (x2, y2, z2), (x3, y3, z3), (x4, y4, z4) = vertices
+                r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3, r4, g4, b4, a4 = colors
+                u1, v1, u2, v2, u3, v3, u4, v4 = uvs
+                self.vertex_buffer[nb_vertices] = Vertex(x1 + ox, y1 + oy, z1, u1, v1, r1, g1, b1, a1)
+                self.vertex_buffer[nb_vertices+1] = Vertex(x2 + ox, y2 + oy, z2, u2, v2, r2, g2, b2, a2)
+                self.vertex_buffer[nb_vertices+2] = Vertex(x3 + ox, y3 + oy, z3, u3, v3, r3, g3, b3, a3)
+                self.vertex_buffer[nb_vertices+3] = Vertex(x4 + ox, y4 + oy, z4, u4, v4, r4, g4, b4, a4)
+
+                # Add indices
+                index = nb_vertices
+                rec.extend((index, index + 1, index + 2, index + 3))
+
+                nb_vertices += 4
+
+        for (texture_key, blendfunc), indices in indices_by_texture.items():
+            glVertexPointer(3, GL_INT, 24, <long> &self.vertex_buffer[0].x)
+            glTexCoordPointer(2, GL_FLOAT, 24, <long> &self.vertex_buffer[0].u)
+            glColorPointer(4, GL_UNSIGNED_BYTE, 24, <long> &self.vertex_buffer[0].r)
+
+            nb_indices = len(indices)
+            indices = pack(str(nb_indices) + 'H', *indices)
+            glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
+            glBindTexture(GL_TEXTURE_2D, self.texture_manager[texture_key].id)
+            glDrawElements(GL_QUADS, nb_indices, GL_UNSIGNED_SHORT, indices)
+
+
+    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, (GLfloat * 4)(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].id)
+                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.effects)
+            self.render_elements(chain(game.players_bullets,
+                                       game.players,
+                                       *(player.objects() for player in game.players)))
+            self.render_elements(chain(game.bullets, game.cancelled_bullets, game.items))
+            #TODO: display item indicators
+            glEnable(GL_FOG)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/gamerunner.py	Tue Nov 01 13:50:33 2011 +0100
@@ -0,0 +1,142 @@
+# -*- 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 pyglet
+import traceback
+
+from pyglet.gl import (glMatrixMode, glLoadIdentity, glEnable,
+                       glHint, glEnableClientState, glViewport,
+                       gluPerspective, gluLookAt,
+                       GL_MODELVIEW, GL_PROJECTION,
+                       GL_TEXTURE_2D, GL_BLEND, GL_FOG,
+                       GL_PERSPECTIVE_CORRECTION_HINT, GL_FOG_HINT, GL_NICEST,
+                       GL_COLOR_ARRAY, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY)
+
+from .gamerenderer import GameRenderer
+
+
+class GameRunner(pyglet.window.Window, GameRenderer):
+    def __init__(self, resource_loader, game=None, background=None, replay=None):
+        GameRenderer.__init__(self, resource_loader, game, background)
+        pyglet.window.Window.__init__(self, caption='PyTouhou', resizable=False)
+        self.replay_level = None
+        if not replay or not replay.levels[game.stage-1]:
+            self.keys = pyglet.window.key.KeyStateHandler()
+            self.push_handlers(self.keys)
+        else:
+            self.keys = 0
+            self.replay_level = replay.levels[game.stage-1]
+
+        self.fps_display = pyglet.clock.ClockDisplay()
+
+
+    def start(self, width=384, height=448):
+        self.set_size(width, height)
+
+        # 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)
+
+        # Use our own loop to ensure 60 (for now, 120) fps
+        pyglet.clock.set_fps_limit(120)
+        while not self.has_exit:
+            pyglet.clock.tick()
+            self.dispatch_events()
+            self.update()
+            self.on_draw()
+            self.flip()
+
+
+    def on_resize(self, width, height):
+        glViewport(0, 0, width, height)
+
+
+    def _event_text_symbol(self, ev):
+        # XXX: Ugly workaround to a pyglet bug on X11
+        #TODO: fix that bug in pyglet
+        try:
+            return pyglet.window.Window._event_text_symbol(self, ev)
+        except Exception as exc:
+            print('*WARNING* Pyglet error:')
+            traceback.print_exc(exc)
+            return None, None
+
+
+    def on_key_press(self, symbol, modifiers):
+        if symbol == pyglet.window.key.ESCAPE:
+            self.has_exit = True
+        # XXX: Fullscreen will be enabled the day pyglet stops sucking
+        elif symbol == pyglet.window.key.F11:
+            self.set_fullscreen(not self.fullscreen)
+
+
+    def update(self):
+        if self.background:
+            self.background.update(self.game.frame)
+        if self.game:
+            if not self.replay_level:
+                #TODO: allow user settings
+                keystate = 0
+                if self.keys[pyglet.window.key.W]:
+                    keystate |= 1
+                if self.keys[pyglet.window.key.X]:
+                    keystate |= 2
+                #TODO: on some configurations, LSHIFT is Shift_L when pressed
+                # and ISO_Prev_Group when released, confusing the hell out of pyglet
+                # and leading to a always-on LSHIFT...
+                if self.keys[pyglet.window.key.LSHIFT]:
+                    keystate |= 4
+                if self.keys[pyglet.window.key.UP]:
+                    keystate |= 16
+                if self.keys[pyglet.window.key.DOWN]:
+                    keystate |= 32
+                if self.keys[pyglet.window.key.LEFT]:
+                    keystate |= 64
+                if self.keys[pyglet.window.key.RIGHT]:
+                    keystate |= 128
+                if self.keys[pyglet.window.key.LCTRL]:
+                    keystate |= 256
+                self.game.run_iter(keystate)
+            else:
+                keystate = 0
+                for frame, _keystate, unknown in self.replay_level.keys:
+                    if self.game.frame < frame:
+                        break
+                    else:
+                        keystate = _keystate
+
+                self.game.run_iter(keystate)
+
+
+    def on_draw(self):
+        GameRenderer.render(self)
+
+        #TODO
+        glMatrixMode(GL_MODELVIEW)
+        glLoadIdentity()
+        gluLookAt(192., 224., 835.979370,
+                  192, 224., 0., 0., 1., 0.)
+        self.fps_display.draw()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/sprite.pxd	Tue Nov 01 13:50:33 2011 +0100
@@ -0,0 +1,1 @@
+cpdef object get_sprite_rendering_data(object sprite)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/sprite.pyx	Tue Nov 01 13:50:33 2011 +0100
@@ -0,0 +1,76 @@
+# -*- 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 cimport Matrix
+
+
+cpdef object get_sprite_rendering_data(object sprite):
+    cdef Matrix vertmat
+
+    if not sprite._changed:
+        return sprite._rendering_data
+
+    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[0], sprite.dest_offset[1], sprite.dest_offset[2])
+
+    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)]
+
+    (x1, x2 , x3, x4), (y1, y2, y3, y4), (z1, z2, z3, z4), _ = vertmat.data
+
+    key = (sprite.anm.first_name, sprite.anm.secondary_name), sprite.blendfunc
+    r, g, b = sprite.color
+    values = ((x1, y1, z1), (x2, y2, z2), (x3, y3, z3), (x4, y4, z4)), uvs, [r, g, b, sprite.alpha] * 4
+    sprite._rendering_data = key, values
+    sprite._changed = False
+
+    return sprite._rendering_data
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/texture.py	Tue Nov 01 13:50:33 2011 +0100
@@ -0,0 +1,65 @@
+# -*- 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 pyglet
+from pyglet.gl import (glTexParameteri,
+                       GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
+import os
+
+
+class TextureManager(object):
+    def __init__(self, loader=None):
+        self.loader = loader
+        self.textures = {}
+
+
+    def __getitem__(self, key):
+        if not key in self.textures:
+            self.textures[key] = self.load_texture(key)
+        return self.textures[key]
+
+
+    def preload(self, anm_wrapper):
+        try:
+            anms = anm_wrapper.anm_files
+        except AttributeError:
+            anms = anm_wrapper
+
+        for anm in anms:
+            key = anm.first_name, anm.secondary_name
+            texture = self[key]
+
+
+    def load_texture(self, key):
+        first_name, secondary_name = key
+
+        image_file = pyglet.image.load(first_name, file=self.loader.get_file(os.path.basename(first_name)))
+
+        if secondary_name:
+            alpha_file = pyglet.image.load(secondary_name, file=self.loader.get_file(os.path.basename(secondary_name)))
+            assert (image_file.width, image_file.height) == (alpha_file.width, image_file.height)
+
+            data = image_file.get_data('RGB', image_file.width * 3)
+            alpha_data = alpha_file.get_data('RGB', image_file.width * 3)
+            image_file = pyglet.image.ImageData(image_file.width, image_file.height, 'RGBA', b''.join(data[i*3:i*3+3] + alpha_data[i*3] for i in range(image_file.width * image_file.height)))
+
+            #TODO
+
+        texture = image_file.get_texture()
+
+        glTexParameteri(texture.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
+        glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
+
+        return texture
+