Computer Graphics - Tietokonegrafiikka 52493S
3D Scenegraph Viewer
5.3.2002
Jani Lindgren
Tommi Rinne


Index

1. Trackball
  1.1 Rotation
  1.2 Zooming
  1.3 Panning

2. Display Lists

3. Lighting, Normals and Materials (Default)
  3.1 Implementation of Initial Lighting (ex_viewer.py)
  3.2 Implementation of Box (Class Box)
  3.3 Implementation of Custom Material (Class Material)
  3.4 Implementation of Default Material (Class NoMaterial)
  3.5 Implementation of Viewer (Class Viewpoint)

4. Transformations and Primitives
  4.1 Implementation of Rotation, Translation and Scaling (Class Transform)
  4.2 Box
  4.3 Implementation of Cone Primitive (Class Cone)
  4.4 Implementation of Cylinder Primitive (Class Cylinder)
  4.5 Implementation of Sphere Primitive (Class Sphere)

5. Keyboard Functions
  5.1 Help On Keyboard Commands
  5.2 Main Light
  5.3 Alpha Blending
  5.4 Backface Culling
  5.5 Depth Buffering
  5.6 Rendering Modes

6. Polygon Meshes and Texturing
  6.1 Implementation of Polygon Meshes (Class IndexedFaceSets)
  6.2 Implementation of Textured Surfaces (Class ImageTexture)
  6.3 Implementation of Texture Transformations (Class TextureTransform)
  6.4 Implementation of Non-Textured Surfaces (Class NoTexture)
  6.5 Implementation of Default Texture Transformation (Class NoTextureTransformation)

7. Lighting and Normals (continued)
  7.1 Computation of Normals for Polygon Meshes (Class IndexedFaceSet)
  7.2 Implementation of Directional Light Sources (Class DiractionalLight)
  7.3 Implementation of Point Light Sources (Class PointLight)
  7.4 Implementation of Spot Light Sources (Class SpotLight)

8. VRML Scene
  8.1 Building the Scene
  8.2 Lightning
  8.3 Texture Mapping

Appendix A. Source Code for VRML Viewer
Appendix B. Source Code for Trackball
Appendix C. Source Code for VRML Parser/Renderer
Appendix D. VRML Scene Code
Appendix E. References


1. Trackball

The first task was to implement a mouse driven trackball with rotation, zooming and panning.


Starting view

Rotated view

Zoomed view

Panned view

1.1 Rotation

Rotation is implemented by rotating geometry from one vector to another. The skeleton of the trackball module already contain the necessary code for calculating the two vectors. This is accomplished by projecting the previous and current mouse coordinates onto a sphere. Before anything else is done with these vectors, they are normalized. The axis of rotation is the cross product between the two vectors. This vector is also normalized. To solve the angle between the two vectors, the dot product is computed (cosine of the angle). Sine is computed by taking a square root of the cosine raised to the second power subtracted from one. Now that we have the axis of rotation and the sine and cosine of the angle of rotation, we can construct a matrix for the rotation. This matrix was taken directly from book Real-Time Rendering [1](see References). (Link to code).

1.2 Zooming

Zooming is accomplished by moving the viewer back and forth according to the movement of the mouse. The third (index=2) element of the global variable T is changed according to the movement of the mouse. (Link to code).

1.3 Panning

Panning is accomplished by moving the viewer left and right according to the movement of the mouse. The first (index=0) element of the global variable T is changed according to the movement of the mouse. (Link to code).


2. Display Lists

Main program: Display lists are used to speed up rendering. This is achieved by enclosing the parsing and rendering between calls to glNewList() and glEndList(). The parsing is no longer performed in display() but instead in init() and only once. The display list is created with the following calls:

glNewList( 1, GL_COMPILE )
vrml97.doit(filename)
glEndList()

The display() functions calls glCallList() to render the primitives stored in the display list. An integer value must be specified which identifies the display list.


3. Lighting, Normals and Materials (Default)


Slabs with lighting, materials and transparency

3.1 Implementation of Initial Lighting (ex_viewer.py)

At this stage only one light source was specified (main light). Light source is directional, parallel to, and in the direction of the -z axis. Ambient, diffuse and specular colors were all set to pure white. The three different light sources were implemented right after IndexedFaceSets and texturing because the temple was the first model to use light sources. (Link to code).

3.2 Implementation of Box (Class Box)

The code for rendering the box (which is the only primitive used in slabs.wrl) already contained the submission of normals to OpenGL. Later the sphere, cone and cylinder rendering primitives also submit normals to OpenGL. So at this stage normals for all polygons are computed. (Link to code).

3.3 Implementation of Custom Material (Class Material)

Parameters of the material specify how the surface interacts with light. A material has ambient, diffuse, specular reflectances and emission intensity. A VRML material node has the following fields: ambientItensity, diffuseColor, emissiveColor, shininess, specularColor, transparency. For diffuse and specular reflectances, the VRML fields are used directly and the same applies for emission intensity. The ambient reflectance, on the other hand, is determined by multiplying each color component of diffuseColor with ambientIntensity. Shininess must be multiplied with 128 before it is passed onto OpenGL. For each material parameter the transparency is used directly as the alpha component. To achieve transparency, alpha blending is enabled in the initialization of the viewer. The framebuffer must also contain the channel for alpha color component. It is worth noting that because the viewer does not implement depth sorting of objects and thus rendering ojects from back to front, blending is performed correctly only when objects are by chance rendered from back to front. (Link to code).

3.4 Implementation of Default Material (Class NoMaterial)

Undoes any previous material by setting the material parameters to their default values as given by the OpenGL Reference Manual [2] (see References). Ambient color is set to (0.2, 0.2, 0.2, 1.0) and diffuse color is set to (0.8, 0.8, 0.8, 1.0). Not specular and emission colors are set to (0, 0, 0, 1). Finally, shininess is set to zero. (Link to code).

3.5 Implementation of Viewer (Class Viewpoint)

A VRML viewpoint node contains the following fields: orientation and position. The orientation field is used to construct the initial viewing transformation where the viewer is assumed to located at (0, 0, 0). Both fields are stored in global variables of the trackball module: orientation is stored in R and position in T. The trackball modifies both of these variables which eventually leads to changes in the viewing transformation. (Link to code).


4. Transformations and Primitives

Next thing to implement was the transform node in VRML which specifies geometry transforms. After that we had to implement the building blocks of VRML; four basic primitives Box, Cone, Cylinder and Sphere.


Scene with basic primitives

4.1 Implementation of Rotation, Translation and Scaling (Class Transform)

A VRML transform node has the following fields: scale, translation and rotation. These fields specifies how geometry must be scaled, positioned and oriented respectively. Values of the fields are used directly in calls to glScalef(), glTranslatef(), glRotatef(). The implementation of the draw method first pushes current model view matrix, applies the transformations, then calls the draw() method of all children and finally pops the model view matrix. This way transformations of one node are not applied to sibling nodes. The transformations are applied to the geometry in the following order: scale, rotation and translation. (Link to code).

4.2 Box

Nothing changed from example code.

4.3 Implementation of Cone Primitive (Class Cone)

To draw a cone, we need to know its radius and height. Cone can be drawn with or without bottom. Cone's midpoint is defined as height/2. Midpoint is located in the origo. Cone's tip points towards positive y-axis.


Cone with detail level of 0

If given detail is 0, we draw the most basic type of cone, a four-sided pyramid consisting of four triangles. If detail is higher than 0, we recursively divide one triangle (A -> B -> tip) in two triangles (A -> AB -> tip and AB -> B -> tip) to add sides to cone. The location of point AB is calculated using bottom radius of cone and moving AB to distance of r from bottom's center point.


Side of triangle divided

Bottom is also drawn recursively. We make triangles starting from bottom's midpoint (0,-mp,0) and use points A and B as two other corners. In detail level 1 we draw triangles from -mp -> AB -> A and -mp -> B -> AB. And so on. (Link to code).

4.4 Implementation of Cylinder Primitive (Class Cylinder)

Cylinder primitive is implemented much like cone, except now we use quads to draw cylinder sides. Cylinder can be drawn with or without top and bottom parts or without sides. If you draw cone with top and bottom but without sides, you get two circles whose distance is the height of the cylinder.

We can actually draw one cylinder side by just using two coordinates A and B, and then reversing the y-axis coordinates to get C and D. Image below illustrates cylinder with detail of 0.


Cylinder with detail level of 0

Adding detail and top/bottom implementation is perfomed similary as we did with cone. (Link to code).

4.5 Implementation of Sphere Primitive (Class Sphere)

Sphere is rendered using two nested loops. The outer loop is for theta angle which represents the amount of rotation around the x-axis. The inner loops is for psi angle which represents the amount of rotation around the y-axis. Theta is assigned values from the range [0, Pi] and Psi from the range [0, 2*Pi]. At all times, the previous and current values of both angles are maintained. The step factor for both loops is Pi divided by some predefined value which specifies the amount of detail in the sphere. At the beginning, previous values are set to zero and current values to the step factor. After each round of their respective loops, both angles are incremented by the step factor. Based on the value of the two angles and the sphere radius, a coordinate (x, y, z) on the sphere surface can be computed. To compute the corresponding texture coordinates (s,t), the radius is not needed but the two angles are. The formulas for computing the coordinates are taken directly from the lecture notes. Vertices A and B have the current theta angle whereas D and C have the previous angle. Vertices C and B have the current psi angle whereas A and D have the previous angle. (Link to code).


Sphere with a single quad shown


5. Keyboard Functions

User can interact with VRML viewer not only by using a mouse, but also with special keyboard commands which are listed below:

keyaction
hToggles help text on and off
lToggles main light on/off
aEnables/disables alpha blending
bBackface culling on/off
dDepth buffering on/off
pRotates between different rendering modes (points, lines and shaded + textured)
qExits from the viewer program

Reading of keyboard was done in function kb(). (link to code).

5.1 Help On Keyboard Commands

The main program of the viewer maintains a boolean flag which indicates whether the help is to be displayed or not. The keyboard handler simply inverses the flag and the function display() renders the help text if the flag is set. Before the text is rendered, an orthographic projection matrix is specified.

5.2 Main Light

The main light can be turned on and off by pressing 'l'. The keyboard handler first queries for the state of GL_LIGHT0 and depending on the current state either disables or enables the light.

5.3 Alpha Blending

Alpha blending can enabled and disabled by pressing 'a'. The keyboard handler first queries for the state of GL_BLEND and depending on the current state either disables or enables the blending.


Scene with alpha blending off

Scene with alpha blending on

5.4 Backface Culling

Backface culling can be enabled and disabled by pressing 'c'. The keyboard handler first queries for the state of GL_CULL_FACE and depending on the current state either disables or enables backface culling. Viewer initializes the culling mode such that only backfacing polygons are culled away from the scene.

5.5 Depth Buffering

Depth buffering can be enabled and disabled by pressing 'd'. The keyboard handler first queries for the state of GL_DEPTH_TEST and depending on the current state either disables or enables depth buffering.


Scene with depth buffering

Scene without depth buffering

5.6 Rendering Modes

Rendering mode of the primitives can be changed by pressing 'p'. Primitives are displayed as points, lines or textured. If no texture has been specified, the polygons are flat or gouraud shaded depending on how surface normals have been specified. The keyboard handler first calls next_rendering_mode() to change the rendering mode. Rendering primitives adopt to the rendering modes as follows: If the rendering mode is set to points, then a call to glBegin(GL_POINTS) is made. If the rendering mode is set to lines, then a call to glBegin(GL_LINE_LOOP) is made. If the rendering mode is set to textured, then depending on the nature of the primitive, a call to glBegin() is made with one of GL_TRIANGLES, GL_QUADS or GL_POLYGON as the parameter. After the rendering mode has been changed, the display list has to be updated. The old display list is first deleted, then a new display is started with a call to glNewList(). The VRML source file is parsed and rendered after which glEndList() is called.


Objects rendered using lines

Objects rendered using points


6. Polygon Meshes and Texturing

6.1 Implementation of Polygon Meshes (Class IndexedFaceSets)

The skeleton for the VRML parser already loads floating point values into lists and the values are stored in the fields of the IndexedFaceSet class. The draw() method contains a loop to process all the vertex indices (faces are separated with -1). It is assumed that a face can contain any number of vertices and therefore the faces are rendered as GL_POLYGONs. Each time an index of value -1 is encountered glEnd() is issued immediately followed by glBegin(). for each face the three first vertices are used to compute the face normal. First, the draw() method checks if there are texture coordinates or not. Depending on the presence texture coordinates the program execution branches to one of two loops. One loop submits the texture coordinates to OpenGL and the other does not. It is also assumed that there is an texture coordinate pair index specified for all vertices. The texture coordinates are reported to OpenGL using glTexCoord2f(). For IndexedFaceSets, two-sided lighting model is used, for other primitves, one-sided lighting model is used. (link to code).

6.2 Implementation of Textured Surfaces (Class ImageTexture)

ImageTexture class is responsible for loading a texture. The actual loading of the texture is implemented using the class from the texturing example found on the course homepage. Textures must be either in PPM or JPG format. After the texture has been loaded, the texture is scaled (enlarged) such that both the width and height are a power of two (because OpenGL expects them to be so). Then the texture is specified to OpenGL with a call to glTexImage2D(). Texture parameters are set as follows: For minification and magnification of the texture, bilinear filtering is used. Both s and t coordinates are allowed to wrap. Modulation is used when a fragment is textured. Texturing is also enables by calling glEnable(GL_TEXTURE_2D). (link to code).

6.3 Implementation of Texture Transformations (Class TextureTransform)

A VRML node contains the following fields: rotation, scale and translation. These fields specify how a texture is applied to a polygon. The transformations are applied to texture matrix stack with calls to glScalef(), glRotatef() and glTranslatef() in that order. The previous calls specify three-dimensional transformation and because textures are two-dimensional, special care has to be taken. The rotation is always assumed to be around the Z-axis and the texture coordinates remain two-dimensional after rotation. Translation along the Z-axis assumed to be zero and no scaling along the Z-axis. (link to code).

6.4 Implementation of Non-Textured Surfaces (Class NoTexture)

Undoes any previous texturing parameters by simply disabling textures by calling glDisable(GL_TEXTURE_2D). (link to code).

6.5 Implementation of Default Texture Transformation (Class NoTextureTransformation)

Undoes any previous texture transformations by first setting the matrix mode to GL_TEXTURE and then loading an identity matrix. Before returning restores the matrix mode to GL_MODELVIEW. (link to code).


7. Lighting and Normals (continued)


Sample scene temple.wrl

Sample scene fantom.wrl

7.1 Computation of Normals for Polygon Meshes (Class IndexedFaceSet)

A facet must have at least three vertices and regardless of the number of vertices only the first three vertices are used to compute the normals. Normals are computed as follows: N=(V2-V0)x(V1-V0) where V0, V1 and V2 are the first three vertices of the facet. IndexedFaceSet may also contain list of normals and normal indices as is the case with fantom.wrl. In this case normals are not computed but submitted per vertex (not per polygon). (link to code).

7.2 Implementation of Directional Light Sources (Class DiractionalLight)

A VRML DirectionalLight node contains the following fields: ambientIntensity, color, direction and intensity. Ambient intensity is computed by multiplying ambientIntensity and color. Specular intensity is computed by multiplying intensity and color. Diffuse intensity is computed by multiplying intensity and color. The last component of the light position is set zero which indicates that the light is directional instead of positional. (link to code).

7.3 Implementation of Point Light Sources (Class PointLight)

A VRML PointLight node contains the following fields: ambientIntensity, color, direction and intensity. Ambient intensity is computed by multiplying ambientIntensity and color. Specular intensity is computed by multiplying intensity and color. Diffuse intensity is computed by multiplying intensity and color. The last component of the light position is set one which indicates that the light is positional instead of directional. (link to code).

7.4 Implementation of Spot Light Sources (Class SpotLight)

A VRML SpotLight node contains the following fields: ambientIntensity, color, direction and intensity, direction, cutOffAngle and attenuation. Ambient intensity is computed by multiplying ambientIntensity and color. Specular intensity is computed by multiplying intensity and color. Diffuse intensity is computed by multiplying intensity and color. Direction specifies the direction of the spotlight. cutOffAngle specifies the cutoff angle of the spotlight. The three components of attenuation are directly passed to OpenGL as constant, linear and quadratic attenuation factors respectively. The last component of the light position is set one which indicates that the light is positional instead of directional. (link to code).


8. VRML Scene

Final task was to build a simple VRML scene which demonstrates different types of lamps as well as using basic primitives.

8.1 Building the Scene

The scene (a bicycle) consists of following primitives:

  • Front and back wheel (thin cylinders) - (link to code)
  • Fork (cylinder) - (link to code)
  • Handlebar (cylinder) - (link to code)
  • Handles for handlebar (two cylinders) - (link to code)
  • Three bars creating bicycles middle part (cylinders) - (link to code)
  • Saddle (scaled cone) - (link to code)
  • Back bar holding the saddle (cylinder) - (link to code)
  • Cover for pedals (thin cylinder) - (link to code)
  • Two pedals and steel bars to hold them pedals together (boxes and cylinders) - (link to code)
  • A ringing bell (cylinder and cone) - (link to code)

    Scene was made by drawing a sketch on paper. From sketch it was easy to get the main coordinates of the primitives. Then the objects were written to VRML file (bicycle.wrl) and placed to their exact positions with "trial and error" -method.

      
    Sketch of bicycle scene and same scene later on in VRML viewer

    If there were materials that were used is more than one primitive, a material definition was made at the beginning of VRML-file to make the code easier to modify (link to code).


    8.2 Lightning

    Scene uses two directional lights: A green one positioned towards positive x-axis and a red one positioned towards negative x-axis. Magneta spotlight illuminates the scene from negative y-axis. Cyan spotlight is located at (0, 4, 0) and is directed towards negative y-axis. (link to code).


    Red and green directional lights

    Magneta spotlight from negative y-axis

    8.3 Texture Mapping

    Texture was used to make a name sticker on the main bar of bicycle. Texture was first made using Corel Photo-Paint image processing application. Sticker was doubled in the image to get the sticker on both sides of the main bar. Vertical and horisontal space was added to make sticker to stay in the middle of the bar. (link to code).


    Texture image


    Rod of bicycle with texture


    Appendix A. Source Code for VRML Viewer (vrml97.py)


    from string import *
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.GLUT import *
    from Numeric import *
    from TBEX import *
    
    import sys, string, re, os
    
    #import tb
    
    #----------------------------------------------------------------------------
    
    numlights = 1
    detail_cone = 4
    detail_cylinder = 4
    detail_sphere = 25
    rendering_mode = 2
    
    #----------------------------------------------------------------------------
    
    def normalize(v):
      length = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])
      v[0] = v[0] / length
      v[1] = v[1] / length
      v[2] = v[2] / length
    
    def normal(a,b,c):
      p1 = [ b[0] - a[0], b[1] - a[1], b[2] - a[2] ]
      p2 = [ c[0] - a[0], c[1] - a[1], c[2] - a[2] ]
    
      normalize(p1)
      normalize(p2)
    
      p3 = [ p1[1] * p2[2] - p1[2] * p2[1], p1[2] * p2[0] - p1[0] * p2[2], p1[0] * p2[1] - p1[1] * p2[0] ]
    
      normalize(p3)
    
      glNormal3d( p3[0], p3[1], p3[2] )
    
    def next_rendering_mode():
      global rendering_mode
    
      rendering_mode = rendering_mode + 1
      if rendering_mode == 3:
        rendering_mode = 0
      print 'rendering mode is now', rendering_mode
    
    #----------------------------------------------------------------------------
    
    def draw_triangle(a,b,c):
      normal(c,b,a)
    
      if rendering_mode == 0:
        glBegin( GL_POINTS )
    
      if rendering_mode == 1:
        glBegin( GL_LINE_LOOP )
    
      if rendering_mode == 2:
        glBegin( GL_TRIANGLES )
    
      glVertex3f( c[0], c[1], c[2] )
      glVertex3f( b[0], b[1], b[2] )
      glVertex3f( a[0], a[1], a[2] )
      glEnd()
    
    def cone(r,height,side,bottom,detail):
    
    	mp = height/2	#midpoint of cone
    
    	if side == 0: height = 0
    
    	triangle_cone( [0,-mp,-r],[r,-mp,0],detail,r,mp,bottom,side)
    	triangle_cone( [r,-mp,0],[0,-mp,r],detail,r,mp,bottom,side)
    	triangle_cone( [0,-mp,r],[-r,-mp,0],detail,r,mp,bottom,side)
    	triangle_cone( [-r,-mp,0],[0,-mp,-r],detail,r,mp,bottom,side)
    
    def triangle_cone(a,b,n,r,mp,bottom,side):
      if n == 0:
    
        tip = [0,mp,0]
    
        draw_triangle(a,b,tip)
    
        if bottom == 1:
    
          c = [0,-mp,0]
    
          draw_triangle(c,b,a)
    
      else:
        ab = [ (a[0]+b[0])/2.0, 0, (a[2]+b[2])/2.0 ]	        #midpoint of a -> b
        lab = sqrt( ab[0]*ab[0] + ab[1]*ab[1] + ab[2]*ab[2] )	#ab distance to origo
    
        #
        # move midpoints, the distance to the origo should be the radius of the spehere
        #
        ab[0] = ab[0] * r / lab
        #ab[1] = ab[1] * r / lab
        ab[1] = -mp
        ab[2] = ab[2] * r / lab
    
        #
        # recursively generate more triangles
        #
    
        triangle_cone(a,ab,n-1,r,mp,bottom,side)
        triangle_cone(ab,b,n-1,r,mp,bottom,side)
    
    #----------------------------------------------------------------------------
    
    def draw_quad(a,b,c,d,s1,s2):
    
    	normal(d,c,b)
    
    	if rendering_mode == 0:
    	  glBegin( GL_POINTS )
    
    	if rendering_mode == 1:
    	  glBegin( GL_LINE_LOOP )
    
    	if rendering_mode == 2:
    	  glBegin( GL_QUADS )
    
    	glTexCoord2f( s1, 0 )
    	glVertex3f( a[0], a[1], a[2] )
    	glTexCoord2f( s2, 0 )
    	glVertex3f( b[0], b[1], b[2] )
    	glTexCoord2f( s2, 1 )
    	glVertex3f( c[0], c[1], c[2] )
    	glTexCoord2f( s1, 1 )
    	glVertex3f( d[0], d[1], d[2] )
    	glEnd()
    
    def cylinder(r,height,side,bottom,top,detail):
      glPolygonMode( GL_FRONT, GL_FILL )
      glColor3( 0.5, 0.5, 0 )
    
      mp = height/2	#midpoint of cone
    
      if side == 0:
        height = 0
    
      quad_cylinder( [0,-mp,-r],[r,-mp,0], 0, 0.25, detail,r,mp,bottom,top,side)
      quad_cylinder( [r,-mp,0],[0,-mp,r], 0.25, 0.5, detail,r,mp,bottom,top,side)
      quad_cylinder( [0,-mp,r],[-r,-mp,0],0.5, 0.75, detail,r,mp,bottom,top,side)
      quad_cylinder( [-r,-mp,0],[0,-mp,-r], 0.75, 1, detail,r,mp,bottom,top,side)
    
    def quad_cylinder(a,b,s1,s2,n,r,mp,bottom,top,side):
      if n == 0:
    
        c = [b[0],mp,b[2]]
        d = [a[0],mp,a[2]]
    
        if side == 1:
          draw_quad(a,b,c,d,s1,s2)
    
        if bottom == 1:
    
          p = [0,-mp,0]
          draw_triangle(p,b,a)
    
        if top == 1:
    
          p = [0,mp,0]
          draw_triangle(p,d,c)
          
    
      else:
        ab = [ (a[0]+b[0])/2.0, 0, (a[2]+b[2])/2.0 ]	        #midpoint of a -> b
        lab = sqrt( ab[0]*ab[0] + ab[1]*ab[1] + ab[2]*ab[2] )	#ab distance to origo
    
        #
        # move midpoints, the distance to the origo should be the radius of the spehere
        #
        ab[0] = ab[0] * r / lab
        ab[1] = -mp
        ab[2] = ab[2] * r / lab
    
        #
        # recursively generate more triangles
        #
        s12 = (s1+s2)/2.0
        quad_cylinder(a,ab,s1,s12,n-1,r,mp,bottom,top,side)
        quad_cylinder(ab,b,s12,s2,n-1,r,mp,bottom,top,side)
    
    #----------------------------------------------------------------------------
    
    pi = 3.1415926
    
    def sphere_vertex(r, t, p):
      v = [r*cos(p)*sin(t), r*sin(p)*sin(t),r*cos(t)]
      return v
    
    def sphere_texture(t, p):
      global pi
      v = [p / (2.0*pi), t / float(pi)]
      return v
    
    def sphere_quad(r, t1, t2, p1, p2):
      v1 = sphere_vertex(r, t2, p1)
      v2 = sphere_vertex(r, t2, p2)
      v3 = sphere_vertex(r, t1, p2)
      v4 = sphere_vertex(r, t1, p1)
    
      tex1 = sphere_texture(t2, p1)
      tex2 = sphere_texture(t2, p2)
      tex3 = sphere_texture(t1, p2)
      tex4 = sphere_texture(t1, p1)
    
      normal(v1, v2, v3)
    
      glTexCoord2f( tex1[0], tex1[1] )
      glVertex3f( v1[0], v1[1], v1[2] )
    
      glTexCoord2f( tex2[0], tex2[1] )
      glVertex3f( v2[0], v2[1], v2[2] )
    
      glTexCoord2f( tex3[0], tex3[1] )
      glVertex3f( v3[0], v3[1], v3[2] )
    
      glTexCoord2f( tex4[0], tex4[1] )
      glVertex3f( v4[0], v4[1], v4[2] )
    
    
    def sphere(r,d1,d2):
      global pi
    
      s_theta = pi / d1
      s_psi = pi / d2
    
      p_theta = 0.0
      c_theta = s_theta
    
      glEnable( GL_TEXTURE_2D )
    
      if rendering_mode == 0:
        glBegin( GL_POINTS )
             
      if rendering_mode == 1:
        glBegin( GL_LINE_LOOP )
    
      if rendering_mode == 2:
        glBegin( GL_QUADS )
      
      while p_theta <= pi:
    
        p_psi = 0.0
        c_psi = s_psi
    
        while p_psi <= (2.0*pi):
          sphere_quad(r, p_theta, c_theta, p_psi, c_psi)
    
          p_psi = c_psi
          c_psi = c_psi + s_psi
    
        p_theta = c_theta
        c_theta = c_theta + s_theta
    
      glEnd()
      glDisable( GL_TEXTURE_2D )
    
    #----------------------------------------------------------------------------
    
    def npow2(i):
      n = 1
      while n < i:
        n = n * 2
      return n
    
    def loadimage(fname):
      image = myImg( fname )
    
      oldwidth = image.w
      oldheight = image.h
    
      newwidth = npow2( oldwidth )
      newheight = npow2( oldheight )
    
      newbuffer = gluScaleImage( GL_RGB, oldwidth, oldheight, GL_UNSIGNED_BYTE, image.data, newwidth, newheight, GL_UNSIGNED_BYTE )
      glTexImage2D(GL_TEXTURE_2D, 0, 3, newwidth, newheight, 0, GL_RGB, GL_UNSIGNED_BYTE, newbuffer )
    
    #----------------------------------------------------------------------------
    
    class myImg:
        def __init__(self, fname):
            ending = string.split(fname, '.')[1]
            if ending == 'jpg':
                try:
                    import Image
                    img = Image.open(fname)
                    if img.mode != 'RGB':
                        img = img.convert('RGB')
                    self.data = img.tostring('raw', 'RGB', 0, -1)
                    self.w = img.size[0]
                    self.h = img.size[1]
                except ImportError:
                    try:
                        from wxPython.wx import *
                        wxImage_AddHandler(wxJPEGHandler())
                        wximg = wxImage(fname, wxBITMAP_TYPE_JPEG)
                        self.w = wximg.GetWidth()
                        self.h = wximg.GetHeight()
                        self.data = wximg.GetData()
                        self.flip()
                    except ImportError:
                        print "Don't have PIL or wxPython, cannot read *.jpg"
            elif ending == 'ppm':            
                # read ppm files
                f = open(fname, 'rb')
                f.readline(); f.readline()
                self.w, self.h = tuple(map(int, string.split(f.readline())))
                f.readline()
                self.data = f.read()
                self.flip()
        def flip(self):
            """Assume we have loaded in an RGB image, flip it vertically."""
            w = 3*self.w
            tmp = []
            for i in range(self.h):
                tmp.insert(0, self.data[i*w: (i+1)*w])
            self.data = string.join(tmp, '')        
    
    #----------------------------------------------------------------------------
    
    def expect(tok, l):
        """Make sure the first token of l is tok, consume it."""
        assert l[0] == tok
        del l[0]
    
    def MFFreaderN(value, l, n):
        """Read lists of n floats from l (either one or inside [ and ]),
        put them into value."""
        if l[0] == '[':
            i = l.index(']')
            del value[:]
            v = map(float, l[1:i])
            for j in xrange(0,i-1,n):
                value.append(v[j:j+n])
            del l[:i+1]
        else:
            value[:] = map(float, l[:n])
            del l[:n]
            
    def MFFreader(value, l):
        """Read floats from l (either one or inside [ and ]),
        put them into value."""
        if l[0] == '[':
            i = l.index(']')
            value[:] = map(float, l[1:i])
            del l[:i+1]
        else:
            value[:] = [float(l.pop(0))]
            
    class VRML_field:
        """A base class for various VRML fields.
        The children will have two methods, one
        for initialization with a default value,
        another for parsing the field from list
        of tokens."""
        pass
    
    class SFBool(VRML_field):
        def __init__(self,x=0):
            self.value = x
        def parse(self, l):
            self.value = (l.pop(0) == 'TRUE')
    
    class SFColor(VRML_field):
        def __init__(self,r=0.,g=0.,b=0.):
            self.value = [r,g,b]
        def parse(self, l):
            self.value = map(float, l[:3])
            del l[:3]
    
    class MFColor(VRML_field):
        def __init__(self,c=None):
            if c: self.value = c
            else: self.value = []
        def parse(self, l):
            MFFreaderN(self.value, l, 3)
    
    class SFFloat(VRML_field):
        def __init__(self,x=.0):
            self.value = x
        def __float__(self):
            return self.value
        def parse(self, l):
            self.value = float(l.pop(0))
    
    class MFFloat(VRML_field):
        def __init__(self,x=None):
            if x: self.value = x
            else: self.value = []
        def parse(self, l):
            MFFreader(self.value, l)
    
    class SFImage(VRML_field):
        def __init__(self):
            self.value = [0,0,0,[]]
        def parse(self, l):
            self.value[:3] = l[:3]
            del l[:3]
            n = self.value[0] * self.value[1]
            self.value[3] = map(lambda x: atoi(x,0), l[:n])
            del l[:n]
    
    class SFInt32(VRML_field):
        def __init__(self,x=0):
            self.value = x
        def parse(self, l):
            self.value = int(l.pop(0))
    
    class MFInt32(VRML_field):
        def __init__(self):
            self.value = []
        def parse(self, l):
            if l[0] == '[':
                i = l.index(']')
                try:
                    self.value[:] = map(int, l[1:i])
                except:
                    self.value[:] = map(lambda x: atoi(x,0), l[1:i])
                del l[:i+1]
            else:
                self.value = [atoi(l.pop(0),0)]
    
    class SFNode(VRML_field):
        def __init__(self, v = 0):
            self.value = v
        def parse(self, l):
            if l[0] == 'NULL':
                del l[0]
            else:
                self.value = parse_node(l)
        def draw(self):
            if self.value: self.value.draw()
    
    class MFNode(VRML_field):
        def __init__(self):
            self.value = []
        def parse(self, l):
            if l[0] == '[':
                del l[0]
                while l[0] != ']':
                    self.value.append(parse_node(l))
                del l[0]
            else:
                self.value = [parse_node(l)]
        def draw(self):
            for n in self.value: n.draw()
            
    class SFRotation(VRML_field):
        def __init__(self, r=None):
            if r: self.value = r
            else: self.value = [0.,0.,1.,0.]
        def parse(self, l):
            self.value = map(float, l[:4])
            del l[:4]
    
    class MFRotation(VRML_field):
        def __init__(self,x=None):
            if x: self.value = x
            else: self.value = []
        def parse(self, l):
            MFFreaderN(self.value, l, 4)
    
    class SFString(VRML_field):
        def __init__(self,s=''):
            self.value = s
        def parse(self, l):
            #self.value = l.pop(0)[1:-1]
            self.value = l.pop(0)
    
    class MFString(VRML_field):
        def __init__(self,ss=None):
            if ss: self.value = ss
            else:  self.value = []
        def parse(self, l):
            if l[0] == '[':
                del self.value[:]
                i = l.index(']')
                #self.value = map(lambda x:x[1:-1], l[1:i])
                self.value = l[1:i]
                del l[:i+1]
            else:
                #self.value = [l.pop(0)[1:-1]]
                self.value = [l.pop(0)]
    
    class SFTime(VRML_field):
        def __init__(self,t=-1):
            self.value = t
        def parse(self, l):
            self.value = float(l.pop(0))
    
    class MFTime(VRML_field):
        def __init__(self):
            self.value = []
        def parse(self, l):
            MFFreader(self.value, l)
    
    class SFVec2f(VRML_field):
        def __init__(self,x=.0,y=.0):
            self.value = [x,y]
        def parse(self, l):
            self.value = [float(l[0]),float(l[1])]
            del l[:2]
    
    class MFVec2f(VRML_field):
        def __init__(self,x=None):
            if x: self.value = x
            else: self.value = []
        def parse(self, l):
            MFFreaderN(self.value, l, 2)
    
    class SFVec3f(VRML_field):
        def __init__(self,x=0.,y=0.,z=0.):
            self.value = [x,y,z]
        def parse(self, l):
            self.value = [float(l[0]),float(l[1]),float(l[2])]
            del l[:3]
    
    class MFVec3f(VRML_field):
        def __init__(self,x=None):
            if x: self.value = x
            else: self.value = []
        def parse(self, l):
            MFFreaderN(self.value, l, 3)
    
    class VRML_node:
        """A base class for a VRML node.
        Every actual node inherits from this."""
        def parse(self, l):
            """N.parse(l) parses all the fields between matching
            { and } and returns a reference to the node."""
            expect('{',l)
            while self.fields.has_key(l[0]):
                key = l.pop(0)
                field = self.fields[key].parse(l)
                # foundFields used only for debugging
                try:
                    self.foundFields.append(field)
                except:
                    self.foundFields = [field]
            expect('}',l)
            return self
    
        def draw(self):
            """draw() is called after the parse tree has been created.
            The real action is here. All the data (defaults or given
            values) can be found in self.fields.
            """
            pass
    
    class Anchor(VRML_node):
        def __init__(self):
            self.fields = {
                'children'    : MFNode(),
                'description' : SFString(),
                'parameter'   : MFString(),
                'url'         : MFString(),
                'bboxCenter'  : SFVec3f(),
                'bboxSize'    : SFVec3f(-1,-1,-1),
                }
    
    class Appearance(VRML_node):
        def __init__(self):
            self.fields = {
                'material'         : SFNode(NoMaterial()),
                'texture'          : SFNode(NoTexture()),
                'textureTransform' : SFNode(NoTextureTransform())
                }
        def draw(self):
            self.fields['material'].draw()
            self.fields['texture'].draw()
            self.fields['textureTransform'].draw()
        
    class AudioClip(VRML_node):
        def __init__(self):
            self.fields = {
                'description': SFString(),
                'loop'       : SFBool(),
                'pitch'      : SFFloat(1.),
                'startTime'  : SFTime(),
                'stopTime'   : SFTime(),
                'url'        : MFString()
                }
        
    class Background(VRML_node):
        def __init__(self):
            self.fields = {
                'groundAngle' : MFFloat(),
                'groundColor' : MFColor(),
                'backUrl'     : MFString(),
                'bottomUrl'   : MFString(),
                'frontUrl'    : MFString(),
                'leftUrl'     : MFString(),
                'rightUrl'    : MFString(),
                'topUrl'      : MFString(),
                'skyAngle'    : MFFloat(),
                'skyColor'    : MFColor([0.,0.,0.])
                }
        
    class Billboard(VRML_node):
        def __init__(self):
            self.fields = {
                'axisOfRotation' : SFVec3f(),
                'children'       : MFNode(),
                'bboxCenter'     : SFVec3f(),
                'bboxSize'       : SFVec3f(-1.,-1.,-1.)
                }
        
    
    class Box(VRML_node):
        ind = [[0,4,6,2], [1,3,7,5], # z
               [0,2,3,1], [4,5,7,6], # x
               [0,1,5,4], [2,6,7,3]] # y
        nrm = [[0,0,1], [0,0,-1],
               [1,0,0], [-1,0,0],
               [0,1,0], [0,-1,0]]
        def __init__(self):
            self.fields = {
                'size' : SFVec3f(2,2,2)
                }
        def draw(self):
            x,y,z = tuple(self.fields['size'].value)
            x*=.5; y*=.5; z*=.5
            c = [[ x, y, z], [ x, y,-z], [ x,-y, z], [ x,-y,-z],
                 [-x, y, z], [-x, y,-z], [-x,-y, z], [-x,-y,-z]]
            t = [[0,0],[1,0],[1,1],[0,1]]
    
    	if rendering_mode == 0:
    	  glBegin( GL_POINTS )
    
    	if rendering_mode == 1:
    	  glBegin( GL_LINE_LOOP )
    
    	if rendering_mode == 2:
    	  glBegin( GL_QUADS )
    
            for i in range(6):
                glNormal3fv(Box.nrm[i])
                ind = Box.ind[i]
                for j in range(4):
                    glTexCoord2f(t[j][0], t[j][1])
                    glVertex3fv(c[ind[j]])
            glEnd()
        
    class Collision(VRML_node):
        def __init__(self):
            self.fields = {
                'children'   : MFNode(),
                'collide'    : SFBool(1),
                'bboxCenter' : SFVec3f(),
                'bboxSize'   : SFVec3f(-1.,-1.,-1.),
                'proxy'      : SFNode()
                }
        
    class Color(VRML_node):
        def __init__(self):
            self.fields = {
                'color' : MFColor()
                }
        
    class ColorInterpolator(VRML_node):
        def __init__(self):
            self.fields = {
                'key'      : MFFloat(),
                'keyValue' : MFColor()
                }    
        
    
    class Cone(VRML_node):
        def __init__(self):
            self.fields = {
                'bottomRadius' : SFFloat(1.),
                'height'       : SFFloat(2.),
                'side'         : SFBool(1),
                'bottom'       : SFBool(1)
                }
        def draw(self):
            r = self.fields['bottomRadius'].value
            height = self.fields['height'].value
            side = self.fields['side'].value
            bottom = self.fields['bottom'].value
            cone(r,height,side,bottom,detail_cone)
            
    class Coordinate(VRML_node):
        def __init__(self):
            self.fields = {
                'point' : MFVec3f(),
                }
        
    class CoordinateInterpolator(VRML_node):
        def __init__(self):
            self.fields = {
                'key'      : MFFloat(),
                'keyValue' : MFColor(),
                }
    
    
    class Cylinder(VRML_node):
        def __init__(self):
            self.fields = {
                'bottom' : SFBool(1),
                'height' : SFFloat(2.),
                'radius' : SFFloat(1.),
                'side'   : SFBool(1),
                'top'    : SFBool(1),
                }
        def draw(self):
            r = self.fields['radius'].value
            height = self.fields['height'].value
            side = self.fields['side'].value
            bottom = self.fields['bottom'].value
            top = self.fields['top'].value
            cylinder(r,height,side,bottom,top,detail_cylinder)
        
    class CylinderSensor(VRML_node):
        def __init__(self):
            self.fields = {
                'autoOffset' : SFBool(1),
                'diskAngle'  : SFFloat(.262),
                'enabled'    : SFBool(1),
                'maxAngle'   : SFFloat(-1.),
                'minAngle'   : SFFloat(),
                'offset'     : SFFloat(),
                }
    
    
    
    class DirectionalLight(VRML_node):
        def __init__(self):
            self.fields = {
                'ambientIntensity' : SFFloat(),
                'color'            : SFColor(1.,1.,1.),
                'direction'        : SFVec3f(0.,0.,-1.),
                'intensity'        : SFFloat(1.),
                'on'               : SFBool(1),
                }
        def draw(self):
            global numlights
    
            glPushMatrix()
            glLoadIdentity()
    
            light = GL_LIGHT0 + numlights
    
            if self.fields['on'].value == 1:
              glEnable( light )
    
            r,g,b = tuple(self.fields['color'].value)
    
            i = self.fields['intensity'].value
            glLight( light, GL_DIFFUSE, (i*r,i*g,i*b,1) )
            glLight( light, GL_SPECULAR, (i*r,i*g,i*b,1) )
    
            a = self.fields['ambientIntensity'].value
            glLight( light, GL_AMBIENT, (a*r,a*g,a*b,1) )
    
            x,y,z = tuple(self.fields['direction'].value)
            glLight( light, GL_POSITION, (x,y,z,0) ) # the zero makes the light directional not positional
    
            numlights = numlights + 1
    
            glPopMatrix()
    
            
    class ElevationGrid(VRML_node):
        def __init__(self):
            self.fields = {
                'color'           : SFNode(),
                'normal'          : SFNode(),
                'texCoord'        : SFNode(),
                'height'          : MFFloat(),
                'ccw'             : SFBool(1),
                'colorPerVertex'  : SFBool(1),
                'creaseAngle'     : SFFloat(),
                'normalPerVertex' : SFBool(1),
                'solid'           : SFBool(1),
                'xDimension'      : SFInt32(),
                'xSpacing'        : SFFloat(1.),
                'zDimension'      : SFInt32(),
                'zSpacing'        : SFFloat(1.),
                }
        
    class Extrusion(VRML_node):
        def __init__(self):
            self.fields = {
                'beginCap'     : SFBool(1),
                'ccw'          : SFBool(1),
                'convex'       : SFBool(1),
                'creaseAngle'  : SFFloat([[1,1],[1,-1],[-1,-1],
                                          [-1,1],[1,1]]),
                'crossSection' : MFVec2f(),
                'endCap'       : SFBool(1),
                'orientation'  : MFRotation([0,0,1,0]),
                'scale'        : MFVec2f([1,1]),
                'solid'        : SFBool(1),
                'spine'        : MFVec3f([[0,0,0],[0,1,0]]),
                }
        
    class Fog(VRML_node):
        def __init__(self):
            self.fields = {
                'color'           : SFColor(1.,1.,1.),
                'fogType'         : SFString('LINEAR'),
                'visibilityRange' : SFFloat(),
                }
        
    class FontStyle(VRML_node):
        def __init__(self):
            self.fields = {
                'family'      : MFString(['SERIF']),
                'horizontal'  : SFBool(1),
                'justify'     : MFString(['BEGIN']),
                'language'    : SFString(),
                'leftToRight' : SFBool(1),
                'size'        : SFFloat(1.),
                'spacing'     : SFFloat(1.),
                'style'       : SFString('PLAIN'),
                'topToBottom' : SFBool(1),
                }
        
    class Group(VRML_node):
        def __init__(self):
            self.fields = {
                'children'   : MFNode(),
                'bboxCenter' : SFVec3f(),
                'bboxSize'   : SFVec3f(-1.,-1.,-1.),
                }
        def draw(self):
            # draw children
            self.fields['children'].draw()
        
    
    class ImageTexture(VRML_node):
        def __init__(self):
            self.fields = {
                'url'     : MFString(),
                'repeatS' : SFBool(1),
                'repeatT' : SFBool(1),
                }
        def draw(self):
            fname = self.fields['url'].value[0]
    
            loadimage( fname )
    
            glEnable( GL_TEXTURE_2D )
    
            if self.fields['repeatS'].value == 1:
              glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
            else:
              glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
    
            if self.fields['repeatS'].value == 1:
              glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
            else:
              glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
    
            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
    
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    
    
    
    class IndexedFaceSet(VRML_node):
        def __init__(self):
            self.fields = {
                'color'           : SFNode(),
                'coord'           : SFNode(),
                'normal'          : SFNode(),
                'texCoord'        : SFNode(),
                'ccw'             : SFBool(1),
                'colorIndex'      : MFInt32(),
                'colorPerVertex'  : SFBool(1),
                'convex'          : SFBool(1),
                'coordIndex'      : MFInt32(),
                'creaseAngle'     : SFFloat(),
                'normalIndex'     : MFInt32(),
                'normalPerVertex' : SFBool(1),
                'solid'           : SFBool(1),
                'texCoordIndex'   : MFInt32(),
                }
        def draw(self):
            index = 0
            vertexarray = self.fields['coord'].value.fields['point'].value
            indexarray = self.fields['coordIndex'].value
            indexcount = len(indexarray)
    
            glLightModel( GL_LIGHT_MODEL_TWO_SIDE, 1.0 )
    
    	if rendering_mode == 0:
    	  glBegin( GL_POINTS )
              
    	if rendering_mode == 1:
    	  glBegin( GL_LINE_LOOP )
    
    	if rendering_mode == 2:
    	  glBegin( GL_POLYGON )
    
            for index in range(indexcount):
              if indexarray[index] == -1:
                glEnd()
    
    	    if rendering_mode == 0:
    	      glBegin( GL_POINTS )
              
    	    if rendering_mode == 1:
    	      glBegin( GL_LINE_LOOP )
    
    	    if rendering_mode == 2:
    	      glBegin( GL_POLYGON )
    
                normalcoord = self.fields['normal'].value
                if normalcoord == 0:
                  if index + 3 < indexcount:
                    normal(vertexarray[indexarray[index+2]],vertexarray[indexarray[index+1]],vertexarray[indexarray[index+3]])
              else:
                normalcoord = self.fields['normal'].value
                if normalcoord != 0:
                  normalcoordarray = normalcoord.fields['vector'].value
                  normalindexarray = self.fields['normalIndex'].value
                  x,y,z = tuple(normalcoordarray[normalindexarray[index]])
                  glNormal3f( x, y, z )
    
                texcoord = self.fields['texCoord'].value;
                if texcoord != 0:
                  texcoordarray = texcoord.fields['point'].value
                  texindexarray = self.fields['texCoordIndex'].value
                  s,t = tuple(texcoordarray[texindexarray[index]])
                  glTexCoord2f( s, t )
    
                x,y,z = tuple(vertexarray[indexarray[index]])
                glVertex3f( x, y, z )
    
            glEnd()
            glLightModel( GL_LIGHT_MODEL_TWO_SIDE, 0.0 )
    
    class IndexedLineSet(VRML_node):
        def __init__(self):
            self.fields = {
                'color'           : SFNode(),
                'coord'           : SFNode(),
                'colorIndex'      : MFInt32(),
                'colorPerVertex'  : SFBool(1),
                'coordIndex'      : MFInt32(),
                }
        
    class Inline(VRML_node):
        def __init__(self):
            self.fields = {
                'url'        : MFString(),
                'bboxCenter' : SFVec3f(),
                'bboxSize'   : SFVec3f(-1.,-1.,-1.),
                'nodes'      : MFNode(),
                }
        def draw(self):
            ns = self.fields['nodes']
            if not ns.value:
                # a hack: does not protect name spaces
                # can break in many ways...
                for url in self.fields['url'].value:
                    ns.value.extend(doit(url))
            ns.draw()
        
    class LOD(VRML_node):
        def __init__(self):
            self.fields = {
                'level'  : MFNode(),
                'center' : SFVec3f(),
                'range'  : MFFloat(),
                }
        
    
    class Material(VRML_node):
        def __init__(self):
            self.fields = {
                'ambientIntensity' : SFFloat(.2),
                'diffuseColor'     : SFColor(.8,.8,.8),
                'emissiveColor'    : SFColor(),
                'shininess'        : SFFloat(.2),
                'specularColor'    : SFColor(),
                'transparency'     : SFFloat(1.0),
                }
        def draw(self):
            t = self.fields['transparency'].value
    
            r,g,b = tuple(self.fields['diffuseColor'].value)
            a = self.fields['ambientIntensity'].value
            glMaterial( GL_FRONT_AND_BACK, GL_AMBIENT, (a*r,a*g,a*b,t) )
            glMaterial( GL_FRONT_AND_BACK, GL_DIFFUSE, (r,g,b,t) )
    
            r,g,b = tuple(self.fields['emissiveColor'].value)
            glMaterial( GL_FRONT_AND_BACK, GL_EMISSION, (r,g,b,t) )
    
            r,g,b = tuple(self.fields['specularColor'].value)
            glMaterial( GL_FRONT_AND_BACK, GL_SPECULAR, (r,g,b,t) )
    
            s = self.fields['shininess'].value
            glMaterial( GL_FRONT_AND_BACK, GL_SHININESS, s*128 )
    
    class MovieTexture(VRML_node):
        def __init__(self):
            self.fields = {
                'loop'      : SFBool(),
                'speed'     : SFFloat(1.),
                'startTime' : SFTime(),
                'stopTime'  : SFTime(),
                'url'       : MFString(),
                'repeatS'   : SFBool(1),
                'repeatT'   : SFBool(1),
                }
        
    class NavigationInfo(VRML_node):
        def __init__(self):
            self.fields = {
                'avatarSize'      : MFFloat([.25,1.6,.75]),
                'headlight'       : SFBool(1),
                'speed'           : SFFloat(1.),
                'type'            : MFString(['WALK','ANY']),
                'visibilityLimit' : SFFloat(),
                }
        
    class Normal(VRML_node):
        def __init__(self):
            self.fields = {
                'vector' : MFVec3f(),
                }
        
    class NormalInterpolator(VRML_node):
        def __init__(self):
            self.fields = {
                'key'      : MFFloat(),
                'keyValue' : MFVec3f(),
                }
        
    class OrientationInterpolator(VRML_node):
        def __init__(self):
            self.fields = {
                'key'      : MFFloat(),
                'keyValue' : MFRotation(),
                }
        
    class PixelTexture(VRML_node):
        def __init__(self):
            self.fields = {
                'image'     : SFImage(),
                'repeatS'   : SFBool(1),
                'repeatT'   : SFBool(1),
                }
        
    class PlaneSensor(VRML_node):
        def __init__(self):
            self.fields = {
                'autoOffset'  : SFBool(1),
                'enabled'     : SFBool(1),
                'maxPosition' : SFVec2f(-1,-1),
                'minPosition' : SFVec2f(),
                'offset'      : SFVec3f(),
                }
        
    
    class PointLight(VRML_node):
        def __init__(self):
            self.fields = {
                'ambientIntensity' : SFFloat(),
                'attenuation'      : SFVec3f(1.,0.,0.),
                'color'            : SFColor(1.,1.,1.),
                'intensity'        : SFFloat(1.),
                'location'         : SFVec3f(),
                'on'               : SFBool(1),
                'radius'           : SFFloat(100.),
                }
        def draw(self):
            global numlights
    
            glPushMatrix()
            glLoadIdentity()
    
            light = GL_LIGHT0 + numlights
    
            if self.fields['on'].value == 1:
              glEnable( light )
    
            r,g,b = tuple(self.fields['color'].value)
    
            i = self.fields['intensity'].value
            glLight( light, GL_DIFFUSE, (i*r,i*g,i*b,1) )
            glLight( light, GL_SPECULAR, (i*r,i*g,i*b,1) )
    
            a = self.fields['ambientIntensity'].value
            glLight( light, GL_AMBIENT, (a*r,a*g,a*b,1) )
    
            x,y,z = tuple(self.fields['location'].value)
            glLight( light, GL_POSITION, (x,y,z,1) ) # the one makes the light positional not directional
    
            numlights = numlights + 1
    
            glPopMatrix()
    
    class PointSet(VRML_node):
        def __init__(self):
            self.fields = {
                'color' : SFNode(),
                'coord' : SFNode(),
                }
        
    class PositionInterpolator(VRML_node):
        def __init__(self):
            self.fields = {
                'key'      : MFFloat(),
                'keyValue' : MFRotation(),
                }
        
    class ProximitySensor(VRML_node):
        def __init__(self):
            self.fields = {
                'center'  : SFVec3f(),
                'size'    : SFVec3f(),
                'enabled' : SFBool(1),
                }
    
    class ScalarInterpolator(VRML_node):
        def __init__(self):
            self.fields = {
                'key'      : MFFloat(),
                'keyValue' : MFRotation(),
                }
        
    class Script(VRML_node):
        def __init__(self):
            self.fields = {
                'url'          : MFString(),
                'directOutput' : SFBool(),
                'mustEvaluate' : SFBool(),
                }
        def parse(self, l):
            # just skip over
            expect('{',l)
            while l[0] != '}': l.pop(0)
            expect('}',l)
            return self
        
    class Shape(VRML_node):
        def __init__(self):
            self.fields = {
                'appearance' : SFNode(),
                'geometry'   : SFNode(),
                }
        def draw(self):
            self.fields['appearance'].draw()
            self.fields['geometry'].draw()
    
    class Sound(VRML_node):
        def __init__(self):
            self.fields = {
                'direction'  : SFVec3f(0.,0.,1.),
                'intensity'  : SFFloat(1.),
                'location'   : SFVec3f(),
                'maxBack'    : SFFloat(10.),
                'maxFront'   : SFFloat(10.),
                'minBack'    : SFFloat(1.),
                'minFront'   : SFFloat(1.),
                'priority'   : SFFloat(),
                'source'     : SFNode(),
                'spatialize' : SFBool(1),
                }
        
    
    class Sphere(VRML_node):
        def __init__(self):
            self.fields = {
                'radius' : SFFloat(1.),
                }
        def draw(self):
            global detail_sphere
            r = self.fields['radius'].value
            sphere( r, detail_sphere, detail_sphere );
    
    class SphereSensor(VRML_node):
        def __init__(self):
            self.fields = {
                'autoOffset' : SFBool(1),
                'enabled'    : SFBool(1),
                'offset'     : SFRotation(),
                }
        
    
    class SpotLight(VRML_node):
        def __init__(self):
            self.fields = {
                'ambientIntensity' : SFFloat(),
                'attenuation'      : SFVec3f(1.,0.,0.),
                'beamWidth'        : SFFloat(1.570796),
                'color'            : SFColor(1.,1.,1.),
                'cutOffAngle'      : SFFloat(.785398),
                'direction'        : SFVec3f(0.,0.,-1.),
                'intensity'        : SFFloat(1.),
                'location'         : SFVec3f(),
                'on'               : SFBool(1),
                'radius'           : SFFloat(100.),
                }
        def draw(self):
            global numlights
    
            glPushMatrix()
            glLoadIdentity()
    
            light = GL_LIGHT0 + numlights
    
            if self.fields['on'].value == 1:
              glEnable( light )
    
            r,g,b = tuple(self.fields['color'].value)
    
            i = self.fields['intensity'].value
            glLight( light, GL_DIFFUSE, (i*r,i*g,i*b,1) )
            glLight( light, GL_SPECULAR, (i*r,i*g,i*b,1) )
    
            a = self.fields['ambientIntensity'].value
            glLight( light, GL_AMBIENT, (a*r,a*g,a*b,1) )
    
            x,y,z = tuple(self.fields['location'].value)
            glLight( light, GL_POSITION, (x,y,z,1) ) # the one makes the light positional not directional
    
            x,y,z = tuple(self.fields['direction'].value)
            glLight( light, GL_SPOT_DIRECTION, (x,y,z) ) # the one makes the light positional not directional
    
            a = self.fields['cutOffAngle'].value
            glLight( light, GL_SPOT_CUTOFF, a*90 )
    
            c,l,q = tuple(self.fields['attenuation'].value)
            glLight( light, GL_CONSTANT_ATTENUATION, c )
            glLight( light, GL_LINEAR_ATTENUATION, l )
            glLight( light, GL_QUADRATIC_ATTENUATION, q )
    
            numlights = numlights + 1
    
            glPopMatrix()
        
    class Switch(VRML_node):
        def __init__(self):
            self.fields = {
                'choice'      : MFNode(),
                'whichChoice' : SFInt32(-1),
                }
        
    class Text(VRML_node):
        def __init__(self):
            self.fields = {
                'string'    : MFString(),
                'fontStyle' : SFNode(),
                'length'    : MFFloat(),
                'maxExtent' : SFFloat(),
                }
        
    class TextureCoordinate(VRML_node):
        def __init__(self):
            self.fields = {
                'point' : MFVec2f(),
                }
        
    
    class TextureTransform(VRML_node):
        def __init__(self):
            self.fields = {
                'center'      : SFVec2f(),
                'rotation'    : SFFloat(),
                'scale'       : SFVec2f(1,1),
                'translation' : SFVec2f(),
                }
        def draw(self):
    	global pi
    
            glMatrixMode( GL_TEXTURE )
            glLoadIdentity()
    
            x,y = tuple( self.fields['translation'].value )
            glTranslatef( x, y, 0 )
    
            x,y = tuple( self.fields['scale'].value )
            glScalef( x, y, 0 )
    
            a = self.fields['rotation'].value
            glRotatef( (a / pi)*180 , 0, 0, 1 )
    
            glMatrixMode( GL_MODELVIEW )
        
    class TimeSensor(VRML_node):
        def __init__(self):
            self.fields = {
                'cycleInterval' : SFTime(1.),
                'enabled'       : SFBool(1),
                'loop'          : SFBool(),
                'startTime'     : SFTime(),
                'stopTime'      : SFTime(),
                }
        
    class TouchSensor(VRML_node):
        def __init__(self):
            self.fields = {
                'enabled' : SFBool(1),
                }
        
    
    class Transform(VRML_node):
        def __init__(self):
            self.fields = {
                'center'           : SFVec3f(),
                'children'         : MFNode(),
                'rotation'         : SFRotation(),
                'scale'            : SFVec3f(1,1,1),
                'scaleOrientation' : SFRotation(),
                'translation'      : SFVec3f(),
                'bboxCenter'       : SFVec3f(),
                'bboxSize'         : SFVec3f(-1,-1,-1),
                }
        def draw(self):
            glPushMatrix()
    
            x,y,z = tuple(self.fields['translation'].value)
            glTranslatef( x, y, z )
    
            x,y,z,a = tuple(self.fields['rotation'].value)
            glRotatef( 360*a/(2*3.14), x, y, z )
    
            x,y,z = tuple(self.fields['scale'].value)
            glScalef( x, y, z )
    
            self.fields['children'].draw()
            glPopMatrix()
    
    
    
    class Viewpoint(VRML_node):
        def __init__(self):
            self.fields = {
                'fieldOfView' : SFFloat(.785398),
                'jump'        : SFBool(1),
                'orientation' : SFRotation(),
                'position'    : SFVec3f(0,0,10),
                'description' : SFString(),
                }
        def draw(self):
            global T, R
            x,y,z = tuple(self.fields['position'].value)
    
            T[0] = x
            T[1] = y
            T[2] = z
    
            x,y,z,a = tuple(self.fields['orientation'].value)
    
            c = cos(a)
            s = sin(a)
            t = 1-c
    
            R[0] = t*x*x+c
            R[1] = t*x*y-s*z
            R[2] = t*x*z+s*y
            R[3] = 0.0
            R[4] = t*x*y+s*z
            R[5] = t*y*y+c
            R[6] = t*y*z-s*x
            R[7] = 0.0
            R[8] = t*x*z-s*y
            R[9] = t*y*z+s*x
            R[10] = t*z*z+c
            R[11] = 0.0
            R[12] = 0.0
            R[13] = 0.0
            R[14] = 0.0
            R[15] = 1.0
    
        
    class VisibilitySensor(VRML_node):
        def __init__(self):
            self.fields = {
                'center'  : SFVec3f(),
                'enabled' : SFBool(1),
                'size'    : SFVec3f(),
                }
    
    class WorldInfo(VRML_node):
        def __init__(self):
            self.fields = {
                'info'  : MFString(),
                'title' : SFString(),
                }
    
    class DEF(VRML_node):
        def __init__(self):
            self.fields = {}
        def parse(self, l):
            """Store a reference of the named node,
            use its name as a key in the DEFS dictionary"""
            name = l.pop(0)
            next = parse_node(l)        
            DEFS[name] = next
            return next
    
    class USE(VRML_node):
        def __init__(self):
            self.fields = {}
        def parse(self, l):
            """Return a reference to a previously stored node"""
            return DEFS[l.pop(0)]
    
    class ROUTE(VRML_node):
        def __init__(self):
            self.fields = {}
        def parse(self, l):
            """Just skip without processing"""
            del l[:3]
            return self
    
    # a few extra classes for setting up the correct OpenGL state
    # for the case an Appearance node has defined no material,
    # texture, or texture transform
    
    class NoMaterial(VRML_node):
        def draw(self):
            glMaterial( GL_FRONT, GL_AMBIENT, (0.2, 0.2, 0.2, 1.0) )
            glMaterial( GL_FRONT, GL_DIFFUSE, (0.8, 0.8, 0.8, 1.0) )
            glMaterial( GL_FRONT, GL_EMISSION, (0, 0, 0, 1) )
            glMaterial( GL_FRONT, GL_SPECULAR, (0, 0, 0, 1) )
            glMaterial( GL_FRONT, GL_SHININESS, 0 )
    
    
    class NoTexture(VRML_node):
        def draw(self):
            glDisable( GL_TEXTURE_2D )
            glEnable( GL_LIGHTING )
    
    
    class NoTextureTransform(VRML_node):
        def draw(self):
            glMatrixMode( GL_TEXTURE )
            glLoadIdentity()
            glMatrixMode( GL_MODELVIEW )
        
    
    def read_and_split(fname):
        """ read and remove comments (. matches all but newline)
        split by a quote, every other piece is string and for every
        other get rid of commas, strip and split by white space
        """
        text_no_comments = re.sub('#.*','', open(fname).read())
        tmp = split(text_no_comments, '"')
        tokens = []
        for i in range(len(tmp)):
            if i%2 == 0:
                # tokens separated by whitespace or commas
                no_commas = replace(tmp[i], ',', ' ')
                tokens.extend(split(strip(no_commas)))
            else:
                # don't modify strings (originally with quotes)
                tokens.append(tmp[i])
        return tokens
    
    def error_out():
        """Print the error type and the line where it happened,
        then exit."""
        type, value, traceback = sys.exc_info()
        while traceback.tb_next: traceback = traceback.tb_next
        print type, traceback.tb_lineno
        sys.exit()
    
    def parse_node(l):
        """Read in the first token, create an instance of the corresponding
        class, remove the tokens corresponding to the node from
        the token list (filling in the fields), return a reference
        to the class instance."""
        token = l.pop(0)
        try:
            return eval(token)().parse(l)
        except:
            print token
            print l[:10]
            error_out()
    
    # a global dictionary for storing references to nodes
    # named using DEF (for later use using USE)
    DEFS = {}
    
    def doit(fname):
        """The main function of this module, does it all."""
        parsetree = []
        tokens = read_and_split(fname)
        while tokens:
            parsetree.append(parse_node(tokens))
        for branch in parsetree:
            branch.draw()
        return parsetree
    
    # the rest is just for debugging
    def output(node):
        print node.__class__.__name__
        if not VRML_node in node.__class__.__bases__:
            return
        for n in node.foundFields:
            output(n)
        if node.fields.has_key('children'):
            for ch in node.fields['children'].value:
                output(ch)
    
    if __name__ == '__main__':
        parsetree = []
        tokens = read_and_split(sys.argv[1])
        while tokens:
            parsetree.append(parse(tokens))
    
        output(parsetree[0])
    


    Appendix B. Source Code for Trackball (tbex.py)


    from Numeric import *
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.GLUT import *
    
    T = [0,0,10]
    
    R = [ 1.0,0.0,0.0,0.0,
          0.0,1.0,0.0,0.0,
          0.0,0.0,1.0,0.0,
          0.0,0.0,0.0,1.0 ]
    
    def project(r, x, y):
        """Project an x,y pair onto a sphere of radius r if we are within
        sqrt(2) * r from center OR a hyperbolic sheet if we are farther away."""
        dd = x*x+y*y
        rr = r*r
        if dd < .5 * rr:    # on sphere
            return sqrt(rr - dd)
        else:               # on hyperbola
            return .5 * rr / sqrt(dd)
    
    
    def rotation(w, h, x, y, ox, oy):
        """Given screen size, new and old mouse coordinates,
        calculate the new rotation for the trackball."""
    
        # center of the screen
        c = [w/2, h/2]
    
        # radius is the half the smaller screen extent
        if w < h: r = c[0]
        else:     r = c[1]
    
        # mouse point in screen centered coords
        #x,y   = x - c[0],  y - c[1]
        #ox,oy = ox - c[0], oy - c[1]
        x,y   = x - c[0],  h-1-y - c[1]
        ox,oy = ox - c[0], h-1-oy - c[1]
    
        if x == ox:
          if y == oy:
            return
    
        # get 3d points by projecting from screen
        p1 = [ox, oy, -project(r, ox, oy)]
        p2 = [x, y, -project(r, x, y)]
    
        # the origin is at the center of the sphere
        # calculate the angle between p1 and p2
    
        # my code here (normalize p1 and p2, dot product)
        l1 = sqrt( p1[0] * p1[0] + p1[1] * p1[1] + p1[2] * p1[2] )
        p1[0] = p1[0] / l1
        p1[1] = p1[1] / l1
        p1[2] = p1[2] / l1
    
        l2 = sqrt( p2[0] * p2[0] + p2[1] * p2[1] + p2[2] * p2[2] )
        p2[0] = p2[0] / l2
        p2[1] = p2[1] / l2
        p2[2] = p2[2] / l2
    
        a_cos = p1[0] * p2[0] + p1[1] * p2[1] + p1[2] * p2[2]
        a_sin = sqrt( 1 - a_cos * a_cos )
        
        # calculate the rotation axis for rotating p1 to p2
        # hint: the axis is perpendicular to both p1 and p2
    
        #p3 = [ p1[1] * p2[2] - p1[2] * p2[1], p1[0] * p2[2] - p1[2] * p2[0], p1[0] * p2[1] - p1[1] * p2[0] ]
        p3 = [ p1[1] * p2[2] - p1[2] * p2[1], p1[2] * p2[0] - p1[0] * p2[2], p1[0] * p2[1] - p1[1] * p2[0] ]
    
        l = sqrt( p3[0] * p3[0] + p3[1] * p3[1] + p3[2] * p3[2] )
        p3[0] = p3[0] / l
        p3[1] = p3[1] / l
        p3[2] = p3[2] / l
    
        # my code here, compound the new rotation with the
        # old one, stored in R
        
        global R
    
        M = [ R[0], R[1], R[2], R[3], R[4], R[5], R[6], R[7], R[8], R[9], R[10], R[11], R[12], R[13], R[14], R[15] ]
    
        C = [ a_cos + (1.0 - a_cos) * p3[0] * p3[0],
              (1.0 - a_cos) * p3[0] * p3[1] + p3[2] * a_sin,
              (1.0 - a_cos) * p3[0] * p3[2] - p3[1] * a_sin,
              0.0,
              (1.0 - a_cos) * p3[0] * p3[1] - p3[2] * a_sin,
              a_cos + (1.0 - a_cos ) * p3[1] * p3[1],
              (1.0 - a_cos) * p3[1] * p3[2] + p3[0] * a_sin,
              0.0,
              (1.0 - a_cos) * p3[0] * p3[2] + p3[1] * a_sin,
              (1.0 - a_cos) * p3[1] * p3[2] - p3[0] * a_sin,
              a_cos + (1.0 - a_cos) * p3[2] * p3[2],
              0.0,
              0.0,
              0.0,
              0.0,
              1.0 ]
    
        for i in range(4):
          for j in range(4):
            sum = 0.0
            for k in range(4):
              sum = sum + C[k*4+i]*M[j*4+k]
            R[j*4+i] = sum
    
    
    def zoom(y, oy):
        """Given old and new y coordinate, modify the
        translation of the trackball for zoom effect."""
    
        # a good way to implement the zooming is the modify the
        # camera's distance from the origin by a factor of
        # e.g., 2^((y - oy) / 80.0)
    
        T[2] += (y - oy) / 200.0
    
    
    def pan(x,y,ox,oy):
        # how far in the viewplane are pixels apart? scale panning accordingly
    
        T[0] += (x - ox) / 200.0
    
    


    Appendix C. Source Code for VRML Parser/Renderer (ex_viewer.py)


    #!/usr/bin/env python
    
    """\
    A simple VRML viewer
    """
    
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.GLUT import *
    from TBEX import *
    from VRML97 import *
    import sys, time
    #import sys, time, ex_vrml97
    
    filename=''
    W = 0
    H = 0
    display_help = 0
    
    def frustum(l,r,b,t,n,f):
      mat = glGetDoublev(GL_PROJECTION_MATRIX)
    
      mat[0][0] = 2.0*n/float(r-l)
      mat[0][1] = 0.0
      mat[0][2] = 0.0
      mat[0][3] = 0.0
    
      mat[1][0] = 0.0
      mat[1][1] = 2.0*n/float(t-b)
      mat[1][2] = 0.0
      mat[1][3] = 0.0
    
      mat[2][0] = (r+l)/float(r-l)
      mat[2][1] = (t+b)/float(t-b)
      mat[2][2] = -(f+n)/float(f-n)
      mat[2][3] = -1.0
    
      mat[3][0] = 0.0
      mat[3][1] = 0.0
      mat[3][2] = -2.0*f*n/float(f-n)
      mat[3][3] = 0.0
    
      glMultMatrixd(mat)
    
    def text(x, y, message):
      glLoadIdentity()
      glTranslatef(x, y, 0)
      glScalef( 0.2, 0.2, 0.2 )
      glColor4( 1.0, 1.0, 1.0, 1.0 )
      for m in message:
        glutStrokeCharacter( GLUT_STROKE_ROMAN, ord( m ) )
    
    def help():
      global W, H
    
      glMaterial( GL_FRONT_AND_BACK, GL_AMBIENT, (1, 1, 1, 1) )
      glMaterial( GL_FRONT_AND_BACK, GL_DIFFUSE, (1, 1, 1, 1) )
      glMaterial( GL_FRONT_AND_BACK, GL_SPECULAR, (1, 1, 1, 1) )
      glMaterial( GL_FRONT_AND_BACK, GL_EMISSION, (1, 1, 1, 1) )
      glMaterial( GL_FRONT_AND_BACK, GL_SHININESS, 0 )
    
      glMatrixMode( GL_PROJECTION )
      glLoadIdentity()
      gluOrtho2D( 0, W, 0, H)
    
      glMatrixMode( GL_MODELVIEW )
    
      text( 10, H-30*1, 'Help on Keyboard Commands' )
      text( 10, H-30*2, 'H - Toggles help text on/off' )
      text( 10, H-30*3, 'A - Enables/disables alpha blending' )
      text( 10, H-30*4, 'C - Enables/disables backface culling' )
      text( 10, H-30*5, 'D - Enables/disables depth buffering' )
      text( 10, H-30*6, 'P - Rotates between rendering modes: points, lines and shaded+textured' )
      text( 10, H-30*7, 'L - Toggles main light on/off' )
      text( 10, H-30*8, 'Q - Quit' )
    
    
    def display():
        global T, R, display_help
    
        #print 'display'
        glClearColor( 0, 0, 0.5, 0 )
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    
        if display_help == 1:
          help()
    
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        # these values work for the box
        # the values should be different if the scene is bigger,
        # or if you have been zooming around with trackball
        #near, far = 7, 15
        #gluPerspective(45,
        #               float(W)/H, # aspect ratio
        #               near, far)
        frustum(-1.3, 1.3, -1.0, 1.0, 3.0, 100.0)
    
        # by default in VRML, camera is at 0,0,10, looking at 0,0,0
        # you need to take the translation from the trackball,
        # as well as the rotation. in which order, translation before
        # rotation or vice versa? why that order and not the other?
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslate( -T[0], -T[1], -T[2] )
        glMultMatrixd( R )
    
        glCallList( 1 )
    
        glutSwapBuffers()
        
    def reshape(w,h):
        print 'reshape'
        global W,H
        W,H = w,h
        glViewport(0,0,w,h)
    
    state = 0 # 0 rot, 1 zoom, 2 pan
    ox = 0
    oy = 0
    
    def mouse(button, release, x, y):
        global ox, oy
    
        print 'mouse', button, release, x, y
        mod = glutGetModifiers()
        global state
        if mod & GLUT_ACTIVE_CTRL:
            state = 1
        elif mod & GLUT_ACTIVE_SHIFT:
            state = 2
        else:
            state = 0
        ox = x
        oy = y
    
    def motion(x, y):
        global W,H,ox,oy
        print 'motion', x, y
        if state == 1:
            print 'zooming'
            zoom(y,oy)
            ox = x
            oy = y
    
        elif state == 2:
            print 'panning'
            pan(x,y,ox,oy)
            ox = x
            oy = y
        else:
            rotation(W,H,x,y,ox,oy)
            ox = x
            oy = y
    	print 'rotating'
        glutPostRedisplay()
    
    
    def kb(key, x, y):
        global filename, display_help
    
         # quit
        if key == 'q':
          sys.exit()
    
        # toggle alpha blending on/off
        if key == 'a':
          if glGetInteger( GL_BLEND ) == 1:
            glDisable( GL_BLEND )
          else:
            glEnable( GL_BLEND )
    
        # toggle backface culling on/off
        if key == 'c':
          if glGetInteger( GL_CULL_FACE ) == 1:
            glDisable( GL_CULL_FACE )
          else:
            glEnable( GL_CULL_FACE )
    
        # toggle depth buffering on/off
        if key == 'd':
          if glGetInteger( GL_DEPTH_TEST ) == 1:
            glDisable( GL_DEPTH_TEST )
          else:
            glEnable( GL_DEPTH_TEST )
    
        # toggle polygon drawing mode: polygon, lines, points
        if key == 'p':
          next_rendering_mode()
    
          glDeleteLists( 1, 1 )
          glNewList( 1, GL_COMPILE )
          doit(filename)
          glEndList()
    
        # toggle main light on/off
        if key == 'l':
          if glGetInteger( GL_LIGHT0 ) == 1:
            glDisable( GL_LIGHT0 )
          else:
            glEnable( GL_LIGHT0 )
    
        # help on keyboard commands
        if key == 'h':
          print 'help'
          display_help = 1 - display_help
    
        glutPostRedisplay()
    
    def init():
        global filename
    
        glNewList( 1, GL_COMPILE )
        doit(filename)
        glEndList()
    
        glDisable( GL_CULL_FACE )
        glCullFace( GL_BACK )
    
        glShadeModel( GL_SMOOTH )  
        glPolygonMode( GL_FRONT_AND_BACK, GL_FILL )
        glEnable( GL_DEPTH_TEST )
        glDepthFunc( GL_LESS )
        glEnable( GL_BLEND )
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )
    
        
        glEnable( GL_LIGHTING )
        glEnable( GL_LIGHT0 )
        glLight( GL_LIGHT0, GL_AMBIENT, (1,1,1,1) )
        glLight( GL_LIGHT0, GL_DIFFUSE, (1,1,1,1) )
        glLight( GL_LIGHT0, GL_SPECULAR, (1,1,1,1) )
    
    def main(input_file):
        global filename
        filename = input_file
        glutInit([])
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH)
        glutInitWindowSize(400,400)
        glutInitWindowPosition(300,100)
        glutCreateWindow('VRML viewer')
    
        #glutFullScreen();
    
        glutReshapeFunc(reshape)
        glutDisplayFunc(display)
        glutMouseFunc(mouse)
        glutMotionFunc(motion)
        glutKeyboardFunc(kb)
        init()
        glutMainLoop()
    
    if __name__ == '__main__':
        main(sys.argv[1])
    


    Appendix D. VRML Scene Code (bicycle.wrl)


    #VRML V2.0 utf8
    #Test scene for VRML viewer
    
    
    
    # Light definitions
    
    DirectionalLight {
        ambientIntensity .0
        intensity 1
        color 0.2 0.5 1.0
        direction 1 0 0
    }
    
    DirectionalLight {
        ambientIntensity .0
        intensity 1
        color 1.0 0.5 0.2
        direction -1 0 0
    }
    
    PointLight {
        ambientIntensity 1
        intensity .5
        color 1 0 1
        location 0 -10 0
    }
    
    
    SpotLight {
        ambientIntensity 1
        intensity 1
        color 0 1 1
        location 0 4 0
        direction 0 -1 0
    
    }
    
    
    
    # Material definitions
    
    DEF RedWheel Material {
    	diffuseColor .6 .1 .1
    }
    
    DEF YellowBar Material {
    	diffuseColor 1 1 0
    }
    
    DEF Metal Material {
    	ambientIntensity .15
    	diffuseColor .5 .5 .5
    	shininess 1
    	specularColor .5 .5 .5
    }
    
    DEF BlackRubber Material {
    	diffuseColor 0 0 0
    	shininess .2
    	specularColor .1 .1 .1
    }
    
    DEF BlueMaterial Material {
    	diffuseColor .2 .2 .9
    }
    
    
    #Primitives
    
    
    
    #front wheel
    Transform {
    	translation 1 0 0
    	rotation 1 0 0 1.55
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE RedWheel
    			}
    			geometry Cylinder {
    				height .1
    				radius .8
    			}
    		}
    	]
    }
    
    
    #back wheel
    Transform {
    	translation -2 0 0
    	rotation 1 0 0 1.55
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE RedWheel
    			}
    			geometry Cylinder {
    				height .1
    				radius .8
    			}
    		}
    	]
    }
    
    
    #fork
    Transform {
    	translation .6 .9 0
    	rotation 0 0 1 .5
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE YellowBar
    			}
    			geometry Cylinder {
    				height 2
    				radius .08
    			}
    		}
    	]
    }
    
    
    #saddle - basebar
    Transform {
    	translation -1.6 .9 0
    	rotation 0 0 1 -0.4
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE YellowBar
    			}
    			geometry Cylinder {
    				height 2
    				radius .08
    			}
    		}
    	]
    }
    
    
    #saddle
    Transform {
    	translation -1.1 1.8 0
    	rotation 0 0 1 -1.75
    	scale .1 1 .3
    
    	children [
    		Shape {
    			appearance Appearance {
    				material Material { 
    					ambientIntensity 0
    					diffuseColor .3 .2 .1
    					shininess 0
    					specularColor .2 .1 .0
    				}
    			}
    			geometry Cone {
    				height .8
    				bottomRadius 1
    			}
    		}
    	]
    }
    
    
    #middle bar
    Transform {
    	translation -.5 .9 .0
    	rotation 0 0 1 1.6
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE YellowBar
    	                        texture DEF oglTexture ImageTexture { url "b_texture.jpg" }
    	       	                textureTransform TextureTransform {
    					rotation 0
    					translation 0.4 0
    				}
    			}
    			geometry Cylinder {
    				height 2.2
    				radius .1
    			}
    		}
    	]
    }
    
    
    
    #bottom bar - back
    Transform {
    	translation -1.1 .4 0
    	rotation 0 0 1 .8
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE YellowBar
    			}
    			geometry Cylinder {
    				height 1.44
    				radius .08
    			}
    		}
    	]
    }
    
    
    #bottom bar - front
    Transform {
    	translation -.1 .4 0
    	rotation 0 0 1 -0.8
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE YellowBar
    			}
    			geometry Cylinder {
    				height 1.51
    				radius .08
    			}
    		}
    	]
    }
    
    
    
    
    #handlebar
    Transform {
    	translation .2 1.7 0
    	rotation 1 0 0 1.55
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE Metal
    			}
    			geometry Cylinder {
    				height 1.8
    				radius .06
    			}
    		}
    	]
    }
    
    
    #handlebar - right handle
    Transform {
    	translation .2 1.72 .8
    	rotation 1 0 0 1.55
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE BlackRubber
    			}
    			geometry Cylinder {
    				height .35
    				radius .08
    			}
    		}
    	]
    }
    
    
    #handlebar - left handle
    Transform {
    	translation .2 1.69 -.8
    	rotation 1 0 0 1.55
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE BlackRubber
    			}
    			geometry Cylinder {
    				height .35
    				radius .08
    			}
    		}
    	]
    }
    
    
    #bell
    Transform {
    	translation .2 1.78 .5
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE Metal
    			}
    			geometry Cylinder {
    				height .05
    				radius .1
    			}
    		}
    	]
    }
    
    
    #bell - top
    Transform {
    	translation .2 1.83 .5
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE Metal
    			}
    			geometry Cone {
    				height .05
    				bottomRadius .1
    			}
    		}
    	]
    }
    
    
    #pedal - cover
    Transform {
    	translation -.6 -.1 .1
    	rotation 1 0 0 1.55
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE BlueMaterial
    			}
    			geometry Cylinder {
    				height .02
    				radius .2
    			}
    		}
    	]
    }
    
    
    #pedal - front - rod
    Transform {
    	translation -.45 -.25 .15
    	rotation 0 0 1 0.8
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE Metal
    			}
    			geometry Cylinder {
    				height .4
    				radius .03
    			}
    		}
    	]
    }
    
    
    #pedal - front
    Transform {
    	translation -.30 -.4 .35
    	rotation 1 0 0 1.55
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE BlueMaterial
    			}
    			geometry Box {
    				size .2 .4 .1
    			}
    		}
    	]
    }
    
    
    #pedal - back - rod
    Transform {
    	translation -.75 .05 -.1
    	rotation 0 0 1 0.8
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE Metal
    			}
    			geometry Cylinder {
    				height .4
    				radius 0.03
    			}
    		}
    	]
    }
    
    
    #pedal - back
    Transform {
    	translation -.9 .2 -.3
    	rotation 1 0 0 1.55
    
    	children [
    		Shape {
    			appearance Appearance {
    				material USE BlueMaterial
    			}
    			geometry Box {
    				size .2 .4 .1
    			}
    		}
    	]
    }
    
    


    Appendix E. References


    [1] T. Möller, E. Haines: Real Time Rendering, AK Peters, Natick, Massachusetts, 1999

    [2] D. Shreiner (editor): OpenGL Reference Manual, Third Edition, Addison-Wesley, Reading, Massachusetts, 1999