This code makes OpenGL simpler to get started with. It provides these classes:
GL_Texture - Loads texture data and provides a draw function
Textureset - Container for textures
GL_Image - Bootstraps off of Textureset; it provides a more sophisticated draw method and contains default values for said method
CImage - Multiple GLImages cached together, for ex. a tiled landscape
DCImage - Dynamic version of Cimage; much lower performance
LDCImage - Simplified version of DCImage with midrange performance but no special drawing arguments
The test code will run if you add a 32x32 .bmp image to "/data". It demos all of the featured classes and functions, but not every included method
import pygame
import os
from OpenGL.GL import *
from OpenGL.GLU import *
def initializeDisplay(w, h):
pygame.display.set_mode((w,h), pygame.OPENGL|pygame.DOUBLEBUF)
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity();
# this puts us in quadrant 1, rather than quadrant 4
gluOrtho2D(0, w, h, 0)
glMatrixMode(GL_MODELVIEW)
# set up texturing
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
def render_init(w,h):
"""Finds the smallest available resolution that fits the desired
viewfield."""
pygame.init()
modelist = pygame.display.list_modes()
nextmode = [l for l in modelist if l[0]>=w and l[1]>=h]
bestx, besty = -1,-1
for l in nextmode:
if (bestx==-1 or bestx>=l[0]) and (besty==-1 or besty>=l[1]):
bestx, besty = l[0],l[1]
print "resolution: ",bestx, besty
initializeDisplay(bestx, besty)
def loadImage(image):
textureSurface = pygame.image.load(image)
textureData = pygame.image.tostring(textureSurface, "RGBA", 1)
width = textureSurface.get_width()
height = textureSurface.get_height()
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, textureData)
return texture, width, height
def SurfaceClip(surface, rect):
textureSurface = surface.subsurface(rect)
textureData = pygame.image.tostring(textureSurface, "RGBA", 1)
width = textureSurface.get_width()
height = textureSurface.get_height()
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, textureData)
return texture, width, height
def delTexture(texture):
glDeleteTextures(texture)
def createTexDL(texture, width, height):
newList = glGenLists(1)
glNewList(newList,GL_COMPILE);
glBindTexture(GL_TEXTURE_2D, texture)
glBegin(GL_QUADS)
# Bottom Left Of The Texture and Quad
glTexCoord2f(0, 0); glVertex2f(0, 0)
# Top Left Of The Texture and Quad
glTexCoord2f(0, 1); glVertex2f(0, height)
# Top Right Of The Texture and Quad
glTexCoord2f(1, 1); glVertex2f( width, height)
# Bottom Right Of The Texture and Quad
glTexCoord2f(1, 0); glVertex2f(width, 0)
glEnd()
glEndList()
return newList
def delDL(list):
glDeleteLists(list, 1)
def render(layers):
for l in layers:
l.render()
class GL_Texture:
def __init__(s, texname=None, texappend=".png"):
filename = os.path.join('data', texname)
filename += texappend
s.texture, s.width, s.height = loadImage(filename)
s.displaylist = createTexDL(s.texture, s.width, s.height)
def __del__(self):
if self.texture != None:
delTexture(self.texture)
self.texture = None
if self.displaylist != None:
delDL(self.displaylist)
self.displaylist = None
def __repr__(s):
return s.texture.__repr__()
class Textureset:
"""Texturesets contain and name textures."""
def __init__(s):
s.textures = {}
def load(s, texname=None, texappend=".png"):
s.textures[texname] = GL_Texture(texname, texappend)
def set(s, texname, data):
s.textures[texname] = data
def delete(s, texname):
del s.textures[texname]
def __del__(s):
s.textures.clear()
del s.textures
def get(s, name):
return s.textures[name]
class GL_Image:
def __init__(self, texset, texname):
self.texture = texset.get(texname)
self.width = self.texture.width
self.height = self.texture.height
self.abspos=None
self.relpos=None
self.color=(1,1,1,1)
self.rotation=0
self.rotationCenter=None
def draw(self, abspos=None, relpos=None, width=None, height=None,
color=None, rotation=None, rotationCenter=None):
if color==None:
color = self.color
glColor4fv(color)
if abspos:
glLoadIdentity()
glTranslate(abspos[0],abspos[1],0)
elif relpos:
glTranslate(relpos[0],relpos[1],0)
if rotation==None:
rotation=self.rotation
if rotation != 0:
if rotationCenter == None:
rotationCenter = (self.width / 2, self.height / 2)
# (w,h) = rotationCenter
glTranslate(rotationCenter[0],rotationCenter[1],0)
glRotate(rotation,0,0,-1)
glTranslate(-rotationCenter[0],-rotationCenter[1],0)
if width or height:
if not width:
width = self.width
elif not height:
height = self.height
glScalef(width/(self.width*1.0), height/(self.height*1.0), 1.0)
glCallList(self.texture.displaylist)
if rotation != 0: # reverse
glTranslate(rotationCenter[0],rotationCenter[1],0)
glRotate(-rotation,0,0,-1)
glTranslate(-rotationCenter[0],-rotationCenter[1],0)
class CImage:
"""CImage is a "composed image" that refs multiple GLImages.
format is [(GLImage,argstoimage)...()..()]
Cimage is fast but immutable - it has to recreate
the display list to be changed."""
def __init__(s, ilist):
newlist = glGenLists(1)
glNewList(newlist,GL_COMPILE)
# see GL_Image.draw
for i in ilist:
if i[1][0] == None:
i[0].draw(i[1][0], i[1][1], i[1][2], i[1][3], i[1][4],
i[1][5], i[1][6])
else: # absolute positioning normally resets the identity
i[0].draw(None,i[1][0], i[1][2], i[1][3], i[1][4], i[1][5],
i[1][6])
glTranslate(-i[1][0][0], -i[1][0][1],0)
glEndList()
s.displaylist = newlist
def __del__(s):
if s.displaylist != None:
delDL(s.displaylist)
s.displaylist = None
def draw(s, abspos=None,relpos=None):
if abspos:
glLoadIdentity()
glTranslate(abspos[0],abspos[1],0)
elif relpos:
glTranslate(relpos[0],relpos[1],0)
glCallList(s.displaylist)
class DCImage:
"""Dynamic Composite Image - elements are mutable, at the caveat of
runtime performance."""
def __init__(s, ilist):
s.ilist = ilist
def draw(s, abspos):
glLoadIdentity()
glTranslate(abspos[0],abspos[1],0)
for i in s.ilist:
i[0].draw(i[1])
class LDCImage:
"""Limited Dynamic Composite Image. LDCImage uses only the
texture display lists for drawing, which makes it useful for simpler
applications like text and tiles that don't need the features of DCImage.
Remember not to mistake this for *LCD* Image!"""
def __init__(s, cache):
"""cache format is: (texture ref, (absx, absy))"""
s.cache = cache
def draw(s, abspos):
glLoadIdentity()
glTranslate(abspos[0],abspos[1],0)
for c in s.cache:
glTranslate(c[1][0], c[1][1],0)
glCallList(c[0].displaylist)
glTranslate(-c[1][0], -c[1][1],0)
def main():
render_init(640,480)
tset = Textureset()
tset.load('foo','.bmp')
fooimage = GL_Image(tset, 'foo')
rawfootex = tset.get('foo')
compositelist = []
examplegrid = []
for x in xrange(4):
for y in xrange(4):
examplegrid.append((rawfootex,(x*32,y*32)))
compositelist.append((fooimage, ((x*32,y*32), None, None,
None, (1,1,1,1), 1, None)))
ldcimg = LDCImage(examplegrid)
foocomposite = CImage(compositelist)
clock = pygame.time.Clock()
t = 0
glLoadIdentity()
for count in range(100):
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
if t==0:
ldcimg.draw((10,10))
fooimage.draw((0,0))
t=1
else:
t=0
foocomposite.draw((320,200))
clock.tick()
pygame.display.flip()
pygame.event.pump()
print "result: "+str(clock.get_fps())+" FPS"
if __name__=="__main__":
main()