Source

glutton / glutton / utils.py

# -*- coding: utf-8 -*-
__license__ = """Copyright (c) 2011, Toni Ruža, All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE."""

__author__ = u"Toni Ruža <gmr.gaf@gmail.com>"
__url__ = "http://bitbucket.org/raz/glutton"

import array
from contextlib import contextmanager

import wx
from OpenGL.GL import *
from OpenGL.GL.EXT.framebuffer_object import *

import structs


class FramebuferObject:
    def __init__(self, width, height, depth=False, linear=False):
        self.width, self.height = width, height
        self.texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self.texture)
        scaling = GL_LINEAR if linear else GL_NEAREST

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, scaling)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, scaling)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
        glTexImage2D(
            GL_TEXTURE_2D, 0, GL_RGBA8,
            width, height, 0, GL_RGBA,
            GL_UNSIGNED_BYTE, None # no data transferred
        )

        self.fbo = glGenFramebuffersEXT(1)
        self.bind()

        if depth:
            renderbuffer = glGenRenderbuffersEXT(1)
            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderbuffer)
            glRenderbufferStorageEXT(
                GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24,
                width, height
            )
            glFramebufferRenderbufferEXT(
                GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                GL_RENDERBUFFER_EXT, renderbuffer
            )

        glFramebufferTexture2DEXT(
            GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
            GL_TEXTURE_2D, self.texture,
            0 # mipmap level, normally 0
        )

        status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)
        assert status == GL_FRAMEBUFFER_COMPLETE_EXT, status
        self.unbind()

    def bind(self):
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self.fbo)

    def unbind(self):
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)

    def delete(self):
        glDeleteFramebuffersEXT(1, [int(self.fbo)])


class Texture2D:
    def __init__(self, bitmap, linear=True):
        self.width = bitmap.Width
        self.height = bitmap.Height

        self.name = glGenTextures(1)
        self.bind()

        if bitmap.HasAlpha():
            kind = GL_RGBA
            data = array.array('B', [0] * self.width * self.height * 4)
            bitmap.CopyToBuffer(data, wx.BitmapBufferFormat_RGBA)
        else:
            kind = GL_RGB
            data = array.array('B', [0] * self.width * self.height * 3)
            bitmap.CopyToBuffer(data, wx.BitmapBufferFormat_RGB)

        glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
        glTexImage2D(
            GL_TEXTURE_2D, 0, kind, self.width, self.height, 0,
            kind, GL_UNSIGNED_BYTE, data.tostring()
        )

        if linear:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        else:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)

    def bind(self):
        glBindTexture(GL_TEXTURE_2D, self.name)

    def delete(self):
        glDeleteTextures(self.name)


class Sprite:
    """ a simple sprite class for the trivial use cases """

    def __init__(self, texture, keep_aspect=True):
        self.texture = texture
        self.keep_aspect = keep_aspect
        self.rotation = 0
        self.position = structs.Position2D()
        self.size = structs.Size2D(1, 1)

    @contextmanager
    def transformation_matrix(self):
        try:
            glPushMatrix()
            glTranslatef(self.position.x, self.position.y, 0)
            glScalef(self.size.width, self.size.height, 1)
            glRotatef(self.rotation, 0, 0, 1)
            yield
        finally:
            glPopMatrix()

    def draw(self):
        self.texture.bind()

        if self.keep_aspect:
            w, h = self.texture.width, self.texture.height
            if w > h:
                min_x, max_x = -0.5, 0.5
                max_y = 0.5 * h / w
                min_y = -max_y
            else:
                min_y, max_y = -0.5, 0.5
                max_x = 0.5 * w / h
                min_x = -max_x
        else:
            min_x, max_x = min_y, max_y = -0.5, 0.5

        with self.transformation_matrix():

            glBegin(GL_QUADS)
            glTexCoord2f(0.0, 1.0); glVertex2f(min_x, min_y) # bottom left 
            glTexCoord2f(1.0, 1.0); glVertex2f(max_x, min_y)  # bottom right 
            glTexCoord2f(1.0, 0.0); glVertex2f(max_x, max_y)   # top right 
            glTexCoord2f(0.0, 0.0); glVertex2f(min_x, max_y)  # top left
            glEnd()

    def draw_rect(self):
        with self.transformation_matrix():
            glBegin(GL_QUADS)
            glVertex2f(-0.5, -0.5) # bottom left 
            glVertex2f(0.5, -0.5)  # bottom right 
            glVertex2f(0.5, 0.5)   # top right 
            glVertex2f(-0.5, 0.5)  # top left
            glEnd()

    def draw_outline(self):
        with self.transformation_matrix():
            glBegin(GL_LINE_LOOP)
            glVertex2f(-0.5, -0.5) # bottom left 
            glVertex2f(0.5, -0.5)  # bottom right 
            glVertex2f(0.5, 0.5)   # top right 
            glVertex2f(-0.5, 0.5)  # top left
            glEnd()