Trying to move the view.
[krufty_fps.git] / lamina.py
blob85b8e0c20b01b2d592e78907119632b74c900d7e
1 """
2 Lamina module
4 When you set the display mode for OpenGL, you enable all the coolness of 3D rendering,
5 but you disable the bread-and-butter raster SDL functionality like fill() and blit().
6 Since the GUI libraries use those surface methods extensively, they cannot readily be
7 used in OpenGL displays.
9 Lamina provides the LaminaPanelSurface and LaminaScreenSurface classes, which bridge
10 between the two.
12 The 'surf' attribute is a surface, and can be drawn on, blitted to, and passed to GUI
13 rendering functions for alteration. The 'display' method displays the surface as a
14 transparent textured quad in the OpenGL model-space. The 'refresh' method indicates that
15 the surface has changed, and that the texture needs regeneration. The 'clear' method
16 restores the blank and transparent original condition.
18 Usage is vaguely like this incomplete pseudocode:
20 # create gui with appropriate constructor
21 gui = GUI_Constructor()
23 # create LaminaPanelSurface
24 gui_screen = lamina.LaminaPanelSurface( (640,480), (-1,1,2,2) )
26 # draw widgets on surface
27 gui.draw( gui_screen.surf )
29 # hide mouse cursor
30 pygame.mouse.set_visible(0)
32 while 1:
34 # do input events ....
35 # pass events to gui
36 gui.doevent(...)
38 # detect changes to surface
39 changed = gui.update( gui_screen.surf )
40 if changed:
41 # and mark for update
42 gui_screen.refresh()
44 # draw opengl geometry .....
46 # display gui
47 # opengl code to set modelview matrix for desired gui surface pos
48 gui_screen.display()
51 If your gui screen is not sized to match the display, hide the system
52 mouse cursor, and use the convertMousePos method to position your own
53 OpenGL mouse cursor (a cone or something). The result of the
54 convertMousePos is 2D, (x,y) so you need to draw the mouse with the same
55 modelview matrix as drawing the LaminaPanelSurface itself.
57 mouse_pos = gui_screen.convertMousePos(mouseX, mouseY)
58 glTranslate(*mouse_pos)
59 # opengl code to display your mouse object (a cone?)
62 The distribution package includes several demo scripts which are functional. Refer
63 to them for details of working code.
64 """
66 import OpenGL.GLU as oglu
67 import OpenGL.GL as ogl
69 import pygame
70 import spyre
71 import math
73 def load_texture(surf):
74 """Load surface into texture object. Return texture object.
75 @param surf: surface to make texture from.
76 """
77 txtr = ogl.glGenTextures(1)
78 textureData = pygame.image.tostring(surf, "RGBA", 1)
80 ogl.glEnable(ogl.GL_TEXTURE_2D)
81 ogl.glBindTexture(ogl.GL_TEXTURE_2D, txtr)
82 width, height = surf.get_size()
83 ogl.glTexImage2D( ogl.GL_TEXTURE_2D, 0, ogl.GL_RGBA, width, height, 0,
84 ogl.GL_RGBA, ogl.GL_UNSIGNED_BYTE, textureData )
85 ogl.glTexParameterf(ogl.GL_TEXTURE_2D, ogl.GL_TEXTURE_MAG_FILTER, ogl.GL_NEAREST)
86 ogl.glTexParameterf(ogl.GL_TEXTURE_2D, ogl.GL_TEXTURE_MIN_FILTER, ogl.GL_NEAREST)
87 ogl.glDisable(ogl.GL_TEXTURE_2D)
88 return txtr
90 def overlay_texture(txtr, surf, r):
91 """Load surface into texture object, replacing part of txtr
92 given by rect r.
93 @param txtr: texture to add to
94 @param surf: surface to copy from
95 @param r: rectangle indicating area to overlay.
96 """
97 subsurf = surf.subsurface(r)
98 textureData = pygame.image.tostring(subsurf, "RGBA", 1)
100 hS, wS = surf.get_size()
101 rect = pygame.Rect(r.x,hS-(r.y+r.height),r.width,r.height)
102 #print 'overlaying x %i y %i (%i-%i) w %i h %i' % \
103 # (rect.x, rect.y, hS, r.y, r.width, r.height)
105 ogl.glEnable(ogl.GL_TEXTURE_2D)
106 ogl.glBindTexture(ogl.GL_TEXTURE_2D, txtr)
107 #print 'overlay', rect.x, rect.y, rect.width, rect.height, 'img', len(textureData)
108 ogl.glTexSubImage2D(ogl.GL_TEXTURE_2D, 0, rect.x, rect.y, rect.width, rect.height,
109 ogl.GL_RGBA, ogl.GL_UNSIGNED_BYTE, textureData )
110 ogl.glDisable(ogl.GL_TEXTURE_2D)
112 class LaminaPanelSurface(object):
113 """Surface for imagery to overlay.
114 @ivar surf: surface
115 @ivar dims: tuple with corners of quad
118 def __init__(self, quadDims=(-1,-1,2,2), winSize=None):
119 """Initialize new instance.
120 @param winSize: tuple (width, height)
121 @param quadDims: tuple (left, top, width, height)
123 if not winSize:
124 winSize = pygame.display.get_surface().get_size()
125 self._winSize = winSize
126 left, top, width, height = quadDims
127 right, bottom = left+width, top-height
128 self._qdims = quadDims
129 self.dims = (left,top,0), (right,top,0), (right,bottom,0), (left,bottom,0)
130 self.clear()
132 def clear(self):
133 """Restore the total transparency to the surface. """
134 powerOfTwo = 64
135 while powerOfTwo < max(*self._winSize):
136 powerOfTwo *= 2
137 raw = pygame.Surface((powerOfTwo, powerOfTwo), pygame.SRCALPHA, 32)
138 self._surfTotal = raw.convert_alpha()
139 self._usable = 1.0*self._winSize[0]/powerOfTwo, 1.0*self._winSize[1]/powerOfTwo
140 self.surf = self._surfTotal.subsurface(0,0,self._winSize[0],self._winSize[1])
141 self._txtr = None
143 def regen(self):
144 """Force regen of texture object. Call after change to the GUI appearance. """
145 if self._txtr:
146 ogl.glDeleteTextures([self._txtr])
147 self._txtr = None
149 def refresh(self, dirty=None):
150 """Refresh the texture from the surface.
151 @param dirty: list of rectangles to update, None for whole panel
153 if not self._txtr:
154 self._txtr = load_texture(self._surfTotal)
155 else:
156 wS, hS = self._surfTotal.get_size()
157 if dirty is None:
158 dirty = [pygame.Rect(0,0,wS,hS)]
159 else: pass
160 #dirty = [pygame.Rect(r.x,hS-r.y,r.width,r.height) for r in dirty]
161 for r in dirty:
162 overlay_texture(self._txtr,self._surfTotal,r)
164 def convertMousePos(self, pos):
165 """Converts 2d pixel mouse pos to 2d gl units.
166 @param pos: 2-tuple with x,y of mouse
168 x0, y0 = pos
169 x = x0/self._winSize[0]*self._qdims[2] + self._qdims[0]
170 y = y0/self._winSize[1]*self._qdims[3] + self._qdims[1]
171 return x, y
173 def display(self):
174 """Draw surface to a quad. Call as part of OpenGL rendering code."""
175 ogl.glEnable(ogl.GL_BLEND)
176 ogl.glBlendFunc(ogl.GL_SRC_ALPHA, ogl.GL_ONE_MINUS_SRC_ALPHA)
177 ogl.glEnable(ogl.GL_TEXTURE_2D)
178 ogl.glBindTexture(ogl.GL_TEXTURE_2D, self._txtr)
179 ogl.glTexEnvf(ogl.GL_TEXTURE_ENV, ogl.GL_TEXTURE_ENV_MODE, ogl.GL_REPLACE)
181 ogl.glBegin(ogl.GL_QUADS)
182 ogl.glTexCoord2f(0.0, 1.0)
183 ogl.glVertex3f(*self.dims[0])
184 ogl.glTexCoord2f(self._usable[0], 1.0)
185 ogl.glVertex3f(*self.dims[1])
186 ogl.glTexCoord2f(self._usable[0], 1-self._usable[1])
187 ogl.glVertex3f(*self.dims[2])
188 ogl.glTexCoord2f(0.0, 1-self._usable[1])
189 ogl.glVertex3f(*self.dims[3])
190 ogl.glEnd()
191 ogl.glDisable(ogl.GL_BLEND)
192 ogl.glDisable(ogl.GL_TEXTURE_2D)
194 def testMode(self):
195 """Draw red/transparent checkerboard. """
196 w, h = self._winSize[0]*0.25, self._winSize[1]*0.25
197 Rect = pygame.Rect
198 pygame.draw.rect(self.surf, (250,0,0), Rect(0,0,w,h), 0)
199 pygame.draw.rect(self.surf, (250,0,0), Rect(2*w,0,w,h), 0)
201 pygame.draw.rect(self.surf, (250,0,0), Rect(w,h,w,h), 0)
202 pygame.draw.rect(self.surf, (250,0,0), Rect(3*w,h,w,h), 0)
204 pygame.draw.rect(self.surf, (250,0,0), Rect(0,2*h,w,h), 0)
205 pygame.draw.rect(self.surf, (250,0,0), Rect(2*w,2*h,w,h), 0)
207 pygame.draw.rect(self.surf, (250,0,0), Rect(w,3*h,w,h), 0)
208 pygame.draw.rect(self.surf, (250,0,0), Rect(3*w,3*h,w,h), 0)
209 self.clear = None
212 class LaminaScreenSurface(LaminaPanelSurface):
213 """Surface for imagery to overlay. Autofits to actual display.
214 @ivar surf: surface
215 @ivar dims: tuple with corners of quad
218 def __init__(self, depth=0):
219 """Initialize new instance.
220 @param depth: (0-1) z-value, if you want to draw your own 3D
221 cursor, set this to a small non-zero value to allow room in
222 front of this overlay to draw the cursor. (0.1 is a first guess)
224 self._depth = depth
225 self.setup()
227 def setup(self):
228 """Setup stuff, after pygame is inited. """
229 self._winSize = pygame.display.get_surface().get_size()
230 self.refreshPosition()
231 self.clear()
233 def refreshPosition(self):
234 """Recalc where in modelspace quad needs to be to fill screen."""
235 depth = self._depth
236 bottomleft = oglu.gluUnProject(0, 0, depth)
237 bottomright = oglu.gluUnProject(self._winSize[0], 0, depth)
238 topleft = oglu.gluUnProject(0, self._winSize[1], depth)
239 topright = oglu.gluUnProject(self._winSize[0], self._winSize[1], depth)
240 self.dims = topleft, topright, bottomright, bottomleft
241 width = topright[0] - topleft[0]
242 height = topright[1] - bottomright[1]
243 self._qdims = topleft[0], topleft[1], width, height
246 class LaminaScreenSurface2(LaminaScreenSurface):
247 """Surface that defers initialization to setup method. """
249 def __init__(self, depth=0):
250 """Initialize new instance. """
251 self._depth = depth
253 def refreshPosition(self):
254 """Recalc where in modelspace quad needs to be to fill screen."""
255 self._dirty = True
257 def update(self):
258 pass
260 def commit(self):
261 pass
263 def display(self):
264 """Display texture. """
265 if self._dirty:
266 depth = self._depth
267 bottomleft = oglu.gluUnProject(0,0,depth)
268 bottomright = oglu.gluUnProject(self._winSize[0],0,depth)
269 topleft = oglu.gluUnProject(0,self._winSize[1],depth)
270 topright = oglu.gluUnProject(self._winSize[0],self._winSize[1],depth)
271 self.dims = topleft, topright, bottomright, bottomleft
272 width = topright[0] - topleft[0]
273 height = topright[1] - bottomright[1]
274 self._qdims = topleft[0], topleft[1], width, height
275 LaminaScreenSurface.display(self)