Statistics
| Revision:

root / PyOpenGL-Demo / NeHe / lesson45.py @ 1

History | View | Annotate | Download (18 kB)

1
# /*******************************************
2
# *                                          *
3
# *   Paul Frazee's Vertex Array Example     *
4
# *           nehe.gamedev.net               *
5
# *                2003                      *
6
# *                                          *
7
# *******************************************/
8
#
9
#
10
# NeHe Tutorial Lesson: 45 - Vertex Buffer Objects
11
#
12
# Ported to PyOpenGL 2.0 by Brian Leair 2004
13
#
14
# This code was created by Jeff Molofee 2000
15
#
16
# The port was based on the PyOpenGL tutorials and from 
17
# PyOpenGLContext (tests/glprint.py)
18
#
19
# If you've found this code useful, feel free to let me know 
20
# at (Brian Leair telcom_sage@yahoo.com).
21
#
22
# See original source and C based tutorial at http://nehe.gamedev.net
23
#
24
# Note:
25
# -----
26
# This code is not an ideal example of Pythonic coding or use of OO 
27
# techniques. It is a simple and direct exposition of how to use the 
28
# Open GL API in Python via the PyOpenGL package. It also uses GLUT, 
29
# a high quality platform independent library. Due to using these APIs, 
30
# this code is more like a C program using procedural programming.
31
#
32
# To run this example you will need:
33
# Python         - www.python.org (v 2.3 as of 1/2004)
34
# PyOpenGL         - pyopengl.sourceforge.net (v 2.0.1.07 as of 1/2004)
35
# Numeric Python        - (v.22 of "numpy" as of 1/2004) numpy.sourceforge.net
36
# Python Image Library        - http://www.pythonware.com/products/pil/
37
#
38
# Make sure to get versions of Numeric, PyOpenGL, and PIL to match your
39
# version of python.
40
#
41
#
42
#
43
# PyOpenGL extension wrapper for GL_ARB_vertex_buffer_object is currently
44
# only availabel through cvs (no release build yet). When a new release
45
# is made of PyOpenGL updating this tutorial should be pretty easy.
46
# For now, in place of VBOs, vertex/texcoord arrays are used.
47
#
48
#
49

    
50

    
51
import OpenGL
52
#OpenGL.FULL_LOGGING = True
53
#OpenGL.ERROR_ON_COPY = True
54
import logging 
55
logging.basicConfig()
56
from OpenGL.GL import *
57
from OpenGL.GLUT import *
58
from OpenGL.GLU import *
59
try:
60
        import numpy as Numeric
61
except ImportError, err:
62
        try: 
63
                import Numeric
64
        except ImportError, err:
65
                print "This demo requires the numpy or Numeric extension, sorry"
66
                import sys
67
                sys.exit()
68
import traceback 
69

    
70
import Image                                 # PIL
71
import sys
72
# import win32api                                # GetTickCount
73
import time                                
74

    
75
# Note Yet Supported
76
from OpenGL.GL.ARB.vertex_buffer_object import *
77
# http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt
78

    
79

    
80

    
81

    
82
# *********************** Globals *********************** 
83
# Python 2.2 defines these directly
84
try:
85
        True
86
except NameError:
87
        True = 1==1
88
        False = 1==0
89

    
90

    
91
# Some api in the chain is translating the keystrokes to this octal string
92
# so instead of saying: ESCAPE = 27, we use the following.
93
ESCAPE = '\033'
94

    
95
# Number of the glut window.
96
window = 0
97

    
98
# PyOpenGL doesn't yet have the ARB for vertex_buffer_objects
99
NO_VBOS = True
100

    
101
g_fVBOSupported = False;                                                        # // ARB_vertex_buffer_object supported?
102
g_pMesh = None;                                                                                # // Mesh Data
103
g_flYRot = 0.0;                                                                                # // Rotation
104
g_nFPS = 0
105
g_nFrames = 0;                                                                                # // FPS and FPS Counter
106
g_dwLastFPS = 0;                                                                        # // Last FPS Check Time        
107

    
108

    
109

    
110

    
111
class CVert:
112
        """ // Vertex Class """
113
        def __init__ (self, x = 0.0, y = 0.0, z = 0.0):
114
                self.x = 0                                                                                        # // X Component
115
                self.y = 0                                                                                        # // Y Component
116
                self.z = 0                                                                                        # // Z Component
117

    
118
# // The Definitions Are Synonymous
119
CVec = CVert
120

    
121
class CTexCoord:
122
        """ // Texture Coordinate Class """
123
        def __init__ (self, u = 0.0, v = 0.0):
124
                self.u = u;                                                                                        # // U Component
125
                self.v = v;                                                                                        # // V Component
126

    
127
class CMesh:
128
        """ // Mesh Data """
129
        MESH_RESOLUTION = 4.0
130
        MESH_HEIGHTSCALE = 1.0
131

    
132
        def __init__ (self):
133
                self.m_nVertexCount = 0;                                                                # // Vertex Count
134

    
135
                self.m_pVertices = None # Numeric.array ( (), 'f')                 # // Vertex Data array
136
                self.m_pVertices_as_string = None                                                # raw memory string for VertexPointer ()
137

    
138
                self.m_pTexCoords = None # Numeric.array ( (), 'f')         # // Texture Coordinates array
139
                self.m_pTexCoords_as_string = None                                                # raw memory string for TexPointer ()
140

    
141
                self.m_nTextureId = None;                                                                # // Texture ID
142

    
143
                # // Vertex Buffer Object Names
144
                self.m_nVBOVertices = None;                                                                # // Vertex VBO Name
145
                self.m_nVBOTexCoords = None;                                                        # // Texture Coordinate VBO Name
146

    
147
                # // Temporary Data
148
                self.m_pTextureImage = None;                                                        # // Heightmap Data
149

    
150

    
151
        def LoadHeightmap( self, szPath, flHeightScale, flResolution ):
152
                """ // Heightmap Loader """
153

    
154
                # // Error-Checking
155
                # // Load Texture Data
156
                try:
157
                        self.m_pTextureImage = Image.open (szPath)                                                         # // Open The Image
158
                except:
159
                        traceback.print_exc()
160
                        return False
161

    
162
                # // Generate Vertex Field
163
                sizeX = self.m_pTextureImage.size [0]
164
                sizeY = self.m_pTextureImage.size [1]
165
                self.m_nVertexCount = int ( sizeX * sizeY * 6 / ( flResolution * flResolution ) );
166
                # self.m_pVertices = Numeric.zeros ((self.m_nVertexCount * 3), 'f')                         # // Vertex Data
167
                # Non strings approach
168
                self.m_pVertices = Numeric.zeros ((self.m_nVertexCount, 3), 'f')                         # // Vertex Data
169
                self.m_pTexCoords = Numeric.zeros ((self.m_nVertexCount, 2), 'f')                         # // Texture Coordinates
170

    
171
                nZ = 0
172
                nIndex = 0
173
                nTIndex = 0
174
                half_sizeX = float (sizeX) / 2.0
175
                half_sizeY = float (sizeY) / 2.0
176
                flResolution_int = int (flResolution)
177
                while (nZ < sizeY):
178
                        nX = 0
179
                        while (nX < sizeY):
180
                                for nTri in xrange (6):
181
                                        # // Using This Quick Hack, Figure The X,Z Position Of The Point
182
                                        flX = float (nX)
183
                                        if (nTri == 1) or (nTri == 2) or (nTri == 5):
184
                                                flX += flResolution
185
                                        flZ = float (nZ)
186
                                        if (nTri == 2) or (nTri == 4) or (nTri == 5):
187
                                                flZ += flResolution
188
                                        x = flX - half_sizeX
189
                                        y = self.PtHeight (int (flX), int (flZ)) * flHeightScale
190
                                        z = flZ - half_sizeY
191
                                        self.m_pVertices [nIndex, 0] = x
192
                                        self.m_pVertices [nIndex, 1] = y
193
                                        self.m_pVertices [nIndex, 2] = z
194
                                        self.m_pTexCoords [nTIndex, 0] = flX / sizeX
195
                                        self.m_pTexCoords [nTIndex, 1] =  flZ / sizeY
196
                                        nIndex += 1
197
                                        nTIndex += 1
198

    
199
                                nX += flResolution_int
200
                        nZ += flResolution_int
201

    
202
                self.m_pVertices_as_string = self.m_pVertices.tostring () 
203
                self.m_pTexCoords_as_string = self.m_pTexCoords.tostring () 
204

    
205
                # // Load The Texture Into OpenGL
206
                self.m_nTextureID = glGenTextures (1)                                                # // Get An Open ID
207
                glBindTexture( GL_TEXTURE_2D, self.m_nTextureID );                        # // Bind The Texture
208
                glTexImage2D( GL_TEXTURE_2D, 0, 3, sizeX, sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, 
209
                        self.m_pTextureImage.tostring ("raw", "RGB", 0, -1))
210
                glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
211
                glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
212

    
213
                # // Free The Texture Data
214
                self.m_pTextureImage = None
215
                return True;
216

    
217
        def PtHeight (self, nX, nY):
218
                """ // Calculate The Position In The Texture, Careful Not To Overflow """
219
                sizeX = self.m_pTextureImage.size [0]
220
                sizeY = self.m_pTextureImage.size [1]
221
                if (nX >= sizeX or nY >= sizeY):
222
                        return 0
223

    
224
                # Get The Red, Green, and Blue Components 
225
                # NOTE, Python Image library starts 0 at the top of the image - so to match the windows
226
                # code we reverse the Y order 
227
                pixel = self.m_pTextureImage.getpixel ((nX, sizeY - nY - 1))
228
                flR = float (pixel [0])
229
                flG = float (pixel [1])
230
                flB = float (pixel [2])
231
                pixel = self.m_pTextureImage.getpixel ((nY, nX))
232

    
233
                # // Calculate The Height Using The Luminance Algorithm
234
                return ( (0.299 * flR) + (0.587 * flG) + (0.114 * flB) );                        
235

    
236

    
237
        def BuildVBOs (self):
238
                """ // Generate And Bind The Vertex Buffer """
239
                if (g_fVBOSupported):
240
                        self.m_nVBOVertices = int(glGenBuffersARB( 1));                                                # // Get A Valid Name
241
                        glBindBufferARB( GL_ARRAY_BUFFER_ARB, self.m_nVBOVertices );        # // Bind The Buffer
242
                        # // Load The Data
243
                        glBufferDataARB( GL_ARRAY_BUFFER_ARB, self.m_pVertices, GL_STATIC_DRAW_ARB );
244

    
245
                        # // Generate And Bind The Texture Coordinate Buffer
246
                        self.m_nVBOTexCoords = int(glGenBuffersARB( 1));                                                # // Get A Valid Name
247
                        glBindBufferARB( GL_ARRAY_BUFFER_ARB, self.m_nVBOTexCoords );                # // Bind The Buffer
248
                        # // Load The Data
249
                        glBufferDataARB( GL_ARRAY_BUFFER_ARB, self.m_pTexCoords, GL_STATIC_DRAW_ARB );
250

    
251
                        # // Our Copy Of The Data Is No Longer Necessary, It Is Safe In The Graphics Card
252
                        self.m_pVertices = None
253
                        self.m_pVertices = None;
254
                        self.m_pTexCoords = None
255
                        self.m_pTexCoords = None;
256
                return
257

    
258
def IsExtensionSupported (TargetExtension):
259
        """ Accesses the rendering context to see if it supports an extension.
260
                Note, that this test only tells you if the OpenGL library supports
261
                the extension. The PyOpenGL system might not actually support the extension.
262
        """
263
        Extensions = glGetString (GL_EXTENSIONS)
264
        # python 2.3
265
        # if (not TargetExtension in Extensions):
266
        #        gl_supports_extension = False
267
        #        print "OpenGL does not support '%s'" % (TargetExtension)
268
        #        return False
269

    
270
        # python 2.2
271
        Extensions = Extensions.split ()
272
        found_extension = False
273
        for extension in Extensions:
274
                if extension == TargetExtension:
275
                        found_extension = True
276
                        break;
277
        if (found_extension == False):
278
                gl_supports_extension = False
279
                print "OpenGL rendering context does not support '%s'" % (TargetExtension)
280
                return False
281

    
282
        gl_supports_extension = True
283

    
284
        # Now determine if Python supports the extension
285
        # Exentsion names are in the form GL_<group>_<extension_name>
286
        # e.g.  GL_EXT_fog_coord 
287
        # Python divides extension into modules
288
        # g_fVBOSupported = IsExtensionSupported ("GL_ARB_vertex_buffer_object")
289
        # from OpenGL.GL.EXT.fog_coord import *
290
        if (TargetExtension [:3] != "GL_"):
291
                # Doesn't appear to following extension naming convention.
292
                # Don't have a means to find a module for this exentsion type.
293
                return False
294

    
295
        # extension name after GL_
296
        afterGL = TargetExtension [3:]
297
        try:
298
                group_name_end = afterGL.index ("_")
299
        except:
300
                # Doesn't appear to following extension naming convention.
301
                # Don't have a means to find a module for this exentsion type.
302
                return False
303

    
304
        group_name = afterGL [:group_name_end]
305
        extension_name = afterGL [len (group_name) + 1:]
306
        extension_module_name = "OpenGL.GL.ARB.%s" % (extension_name)
307

    
308
        import traceback
309
        try:
310
                __import__ (extension_module_name)
311
                print "PyOpenGL supports '%s'" % (TargetExtension)
312
        except:
313
                traceback.print_exc()
314
                print 'Failed to import', extension_module_name
315
                print "OpenGL rendering context supports '%s'" % (TargetExtension),
316
                return False
317

    
318
        return True
319

    
320

    
321

    
322
# // Any GL Init Code & User Initialiazation Goes Here
323
def InitGL(Width, Height):                                # We call this right after our OpenGL window is created.
324
        global g_pMesh,g_fVBOSupported
325

    
326
        # // Check for VBOs Supported
327
        g_fVBOSupported = IsExtensionSupported ("GL_ARB_vertex_buffer_object")
328
        # // TUTORIAL
329
        # // Load The Mesh Data
330
        g_pMesh = CMesh ()
331
        if (not g_pMesh.LoadHeightmap ("Terrain.bmp",
332
                CMesh.MESH_HEIGHTSCALE, CMesh.MESH_RESOLUTION)):
333
                print "Error Loading Heightmap"
334
                sys.exit(1)
335
                return False
336

    
337
        if (g_fVBOSupported):
338
                # // Get Pointers To The GL Functions
339
                # In python, calling Init for the extension functions will
340
                # fill in the function pointers (really function objects)
341
                # so that we call the Extension.
342

    
343
                if (not glInitVertexBufferObjectARB()):
344
                        print "Help!  No GL_ARB_vertex_buffer_object"
345
                        sys.exit(1)
346
                        return False
347
                # Now we can call to gl*Buffer* ()
348
                # glGenBuffersARB
349
                # glBindBufferARB
350
                # glBufferDataARB
351
                # glDeleteBuffersARB
352
                g_pMesh.BuildVBOs()
353

    
354

    
355
        # Setup GL States
356
        glClearColor (0.0, 0.0, 0.0, 0.5);                                                        # // Black Background
357
        glClearDepth (1.0);                                                                                        # // Depth Buffer Setup
358
        glDepthFunc (GL_LEQUAL);                                                                        # // The Type Of Depth Testing
359
        glEnable (GL_DEPTH_TEST);                                                                        # // Enable Depth Testing
360
        glShadeModel (GL_SMOOTH);                                                                        # // Select Smooth Shading
361
        glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);                        # // Set Perspective Calculations To Most Accurate
362
        glEnable(GL_TEXTURE_2D);                                                                        # // Enable Texture Mapping
363
        glColor4f (1.0, 6.0, 6.0, 1.0)
364

    
365
        return True;                                                                                                # // Return TRUE (Initialization Successful)
366

    
367

    
368
# The function called when our window is resized (which shouldn't happen if you enable fullscreen, below)
369
def ReSizeGLScene(Width, Height):
370
        if Height == 0:                                                # Prevent A Divide By Zero If The Window Is Too Small 
371
                Height = 1
372

    
373
        glViewport(0, 0, Width, Height)                # Reset The Current Viewport And Perspective Transformation
374
        glMatrixMode(GL_PROJECTION)
375
        glLoadIdentity()
376
        # // field of view, aspect ratio, near and far
377
        # This will squash and stretch our objects as the window is resized.
378
        gluPerspective(45.0, float(Width)/float(Height), 1, 1000.0)
379

    
380
        glMatrixMode(GL_MODELVIEW)
381
        glLoadIdentity()
382

    
383

    
384
g_prev_draw_time = 0.0
385
# The main drawing function. 
386
def DrawGLScene():
387
        global g_dwLastFPS, g_nFPS, g_nFrames, g_pMesh, g_fVBOSupported, g_flYRot, g_prev_draw_time
388

    
389
        glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);                                # // Clear Screen And Depth Buffer
390
        glLoadIdentity ();                                                                                                        # // Reset The Modelview Matrix
391

    
392

    
393
        # // Get FPS
394
        # milliseconds = win32api.GetTickCount() 
395
        milliseconds = time.clock () * 1000.0
396
        if (milliseconds - g_dwLastFPS >= 1000):                                        # // When A Second Has Passed...
397
                # g_dwLastFPS = win32api.GetTickCount();                                # // Update Our Time Variable
398
                g_dwLastFPS = time.clock () * 1000.0
399
                g_nFPS = g_nFrames;                                                                                # // Save The FPS
400
                g_nFrames = 0;                                                                                        # // Reset The FPS Counter
401

    
402
                # // Build The Title String
403
                szTitle = "Lesson 45: NeHe & Paul Frazee's VBO Tut - %d Triangles, %d FPS" % (g_pMesh.m_nVertexCount / 3, g_nFPS );
404
                if ( g_fVBOSupported ):                                                                        # // Include A Notice About VBOs
405
                        szTitle = szTitle + ", Using VBOs";
406
                else:
407
                        szTitle = szTitle + ", Not Using VBOs";
408

    
409
                glutSetWindowTitle ( szTitle );                                                        # // Set The Title
410

    
411
        g_nFrames += 1                                                                                                 # // Increment Our FPS Counter
412
        rot = (milliseconds - g_prev_draw_time) / 1000.0 * 25
413
        g_prev_draw_time = milliseconds
414
        g_flYRot += rot                                                                                         # // Consistantly Rotate The Scenery
415

    
416
        # // Move The Camera
417
        glTranslatef( 0.0, -220.0, 0.0 );                                                        # // Move Above The Terrain
418
        glRotatef( 10.0, 1.0, 0.0, 0.0 );                                                        # // Look Down Slightly
419
        glRotatef( g_flYRot, 0.0, 1.0, 0.0 );                                                # // Rotate The Camera
420

    
421
        # // Enable Pointers
422
        glEnableClientState( GL_VERTEX_ARRAY );                                                # // Enable Vertex Arrays
423
        glEnableClientState( GL_TEXTURE_COORD_ARRAY );                                # // Enable Texture Coord Arrays
424

    
425

    
426
        # // Set Pointers To Our Data
427
        if( g_fVBOSupported ):
428
#                import pdb
429
#                pdb.set_trace()
430
                glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh.m_nVBOVertices );
431
                glVertexPointer( 3, GL_FLOAT, 0, None );                                # // Set The Vertex Pointer To The Vertex Buffer
432
                glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh.m_nVBOTexCoords );
433
                glTexCoordPointer( 2, GL_FLOAT, 0, None );                                # // Set The TexCoord Pointer To The TexCoord Buffer
434
        else:
435
                # You can use the pythonism glVertexPointerf (), which will convert the numarray into 
436
                # the needed memory for VertexPointer. This has two drawbacks however:
437
                #        1) This does not work in Python 2.2 with PyOpenGL 2.0.0.44 
438
                #        2) In Python 2.3 with PyOpenGL 2.0.1.07 this is very slow.
439
                # See the PyOpenGL documentation. Section "PyOpenGL for OpenGL Programmers" for details
440
                # regarding glXPointer API.
441
                # Also see OpenGLContext Working with Numeric Python
442
                # glVertexPointerf ( g_pMesh.m_pVertices );         # // Set The Vertex Pointer To Our Vertex Data
443
                # glTexCoordPointerf ( g_pMesh.m_pTexCoords );         # // Set The Vertex Pointer To Our TexCoord Data
444
                #
445
                #
446
                # The faster approach is to make use of an opaque "string" that represents the
447
                # the data (vertex array and tex coordinates in this case).
448
                glVertexPointer( 3, GL_FLOAT, 0, g_pMesh.m_pVertices_as_string);          # // Set The Vertex Pointer To Our Vertex Data
449
                glTexCoordPointer( 2, GL_FLOAT, 0, g_pMesh.m_pTexCoords_as_string);         # // Set The Vertex Pointer To Our TexCoord Data
450

    
451

    
452
        # // Render
453
        glDrawArrays( GL_TRIANGLES, 0, g_pMesh.m_nVertexCount );                # // Draw All Of The Triangles At Once
454

    
455
        # // Disable Pointers
456
        glDisableClientState( GL_VERTEX_ARRAY );                                        # // Disable Vertex Arrays
457
        glDisableClientState( GL_TEXTURE_COORD_ARRAY );                                # // Disable Texture Coord Arrays
458

    
459
        glutSwapBuffers()                                                                                                         # // Flush The GL Rendering Pipeline
460
        return True
461

    
462

    
463

    
464

    
465

    
466
# The function called whenever a key is pressed. Note the use of Python tuples to pass in: (key, x, y)  
467
def keyPressed(*args):
468
        global window
469

    
470
        # If escape is pressed, kill everything.
471
        if args[0] == ESCAPE:
472
                sys.exit ()
473
        return
474

    
475
def main():
476
        global window
477
        # pass arguments to init
478
        glutInit(sys.argv)
479

    
480
        # Select type of Display mode:   
481
        #  Double buffer 
482
        #  RGBA color
483
        # Alpha components supported 
484
        # Depth buffer
485
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
486
        
487
        # get a 640 x 480 window 
488
        glutInitWindowSize(640, 480)
489
        
490
        # the window starts at the upper left corner of the screen 
491
        glutInitWindowPosition(0, 0)
492
        
493
        # Okay, like the C version we retain the window id to use when closing, but for those of you new
494
        # to Python, remember this assignment would make the variable local and not global
495
        # if it weren't for the global declaration at the start of main.
496
        window = glutCreateWindow("Lesson 45: NeHe & Paul Frazee's VBO Tut")
497

    
498
                # Register the drawing function with glut, BUT in Python land, at least using PyOpenGL, we need to
499
        # set the function pointer and invoke a function to actually register the callback, otherwise it
500
        # would be very much like the C version of the code.        
501
        glutDisplayFunc(DrawGLScene)
502
        
503
        # Uncomment this line to get full screen.
504
        #glutFullScreen()
505

    
506
        # When we are doing nothing, redraw the scene.
507
        glutIdleFunc(DrawGLScene)
508
        
509
        # Register the function called when our window is resized.
510
        glutReshapeFunc(ReSizeGLScene)
511
        
512
        # Register the function called when the keyboard is pressed.  
513
        # The call setup glutSpecialFunc () is needed to receive 
514
        # "keyboard function or directional keys." 
515
        glutKeyboardFunc(keyPressed)
516
        glutSpecialFunc(keyPressed)
517

    
518
        # We've told Glut the type of window we want, and we've told glut about
519
        # various functions that we want invoked (idle, resizing, keyboard events).
520
        # Glut has done the hard work of building up thw windows DC context and 
521
        # tying in a rendering context, so we are ready to start making immediate mode
522
        # GL calls.
523
        # Call to perform inital GL setup (the clear colors, enabling modes, and most releveant -
524
        # consturct the displays lists for the bitmap font.
525
        InitGL(640, 480)
526

    
527
        # Start Event Processing Engine        
528
        glutMainLoop()
529

    
530
# Print message to console, and kick off the main to get it rolling.
531
if __name__ == "__main__":
532
        print "Hit ESC key to quit."
533
        main()
534

    
535