http://svn.poeml.de/viewvc/ppc/src-unpacked/xscreensaver/xscreensaver-4.12.tar.bz2...
[xscreensaver] / hacks / glx / molecule.c
index 0ad814edf1eb2b3408636b704897329c705368b8..39ad1969e7e8d05693c7b0c63e4c63e094199d4b 100644 (file)
 
    Good source of PDB files:
    http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
-
-   TO DO:
-
-     - I'm not sure the text labels are being done in the best way;
-       they are sometimes, but not always, occluded by spheres that
-       pass in front of them. 
-
-   GENERAL OPENGL NAIVETY:
-
-       I don't understand the *right* way to place text in front of the
-       atoms.  What I'm doing now is close, but has glitches.  I think I
-       understand glPolygonOffset(), but I think it doesn't help me.
-
-       Here's how I'd phrase the problem I'm trying to solve:
-
-       - I have a bunch of spherical objects of various sizes
-       - I want a piece of text in the scene, between each object
-         and the observer
-       - the position of this text should be apparently tangential 
-         to the surface of the sphere, so that:
-         - it is never inside the sphere;
-         - but can be occluded by other objects in the scene.
-
-       So I was trying to use glPolygonOffset() to say "pretend all
-       polygons are N units deeper than they actually are" where N was
-       somewhere around the maximal radius of the objects.  Which wasn't a
-       perfect solution, but was close.  But it turns out that can't work,
-       because the second arg to glPolygonOffset() is multiplied by some
-       minimal depth quantum which is not revealed, so I can't pass it an
-       offset in scene units -- only in multiples of the quantum.  So I
-       don't know how many quanta in radius my spheres are.
-
-       I think I need to position and render the text with glRasterPos3f()
-       so that the text is influenced by the depth buffer.  If I used 2f,
-       or an explicit constant Z value, then the text would always be in
-       front of each sphere, and text would be visible for spheres that
-       were fully occluded, which isn't what I want.
-
-       So my only guess at this point is that I need to position the text
-       exactly where I want it, tangential to the spheres -- but that
-       means I need to be able to compute that XYZ position, which is
-       dependent on the position of the observer!  Which means two things:
-       first, while generating my scene, I need to take into account the
-       position of the observer, and I don't have a clue how to do that;
-       and second, it means I can't put my whole molecule in a display
-       list, because the XYZ position of the text in the scene changes at
-       every frame, as the molecule rotates.
-
-       This just *can't* be as hard as it seems!
  */
 
 #include <X11/Intrinsic.h>
@@ -73,6 +24,8 @@
 #define HACK_INIT      init_molecule
 #define HACK_DRAW      draw_molecule
 #define HACK_RESHAPE   reshape_molecule
+#define HACK_HANDLE_EVENT molecule_handle_event
+#define EVENT_MASK     PointerMotionMask
 #define molecule_opts  xlockmore_opts
 
 #define DEF_TIMEOUT     "20"
 #include "colors.h"
 #include "sphere.h"
 #include "tube.h"
+#include "rotator.h"
+#include "gltrackball.h"
 
 #ifdef USE_GL /* whole file */
 
 #include <stdlib.h>
 #include <ctype.h>
+#include <time.h>
+#include <sys/time.h>
 #include <GL/glu.h>
 
 #define SPHERE_SLICES 16  /* how densely to render spheres */
@@ -133,6 +90,11 @@ static int scale_down;
 #define TUBE_FACES_2     3
 
 
+# ifdef __GNUC__
+  __extension__  /* don't warn about "string length is greater than the length
+                    ISO C89 compilers are required to support" when includng
+                    the following data file... */
+# endif
 const char * const builtin_pdb_data[] = {
 # include "molecules.h"
 };
@@ -186,13 +148,9 @@ typedef struct {
 
 typedef struct {
   GLXContext *glx_context;
-
-  GLfloat rotx, roty, rotz;       /* current object rotation */
-  GLfloat dx, dy, dz;             /* current rotational velocity */
-  GLfloat ddx, ddy, ddz;          /* current rotational acceleration */
-  GLfloat d_max;                  /* max velocity */
-
-  Bool spin_x, spin_y, spin_z;
+  rotator *rot;
+  trackball_state *trackball;
+  Bool button_down_p;
 
   GLfloat molecule_size;          /* max dimension of molecule bounding box */
 
@@ -230,7 +188,7 @@ static XrmOptionDescRec opts[] = {
   { "-molecule", ".molecule", XrmoptionSepArg, 0 },
   { "-timeout",".timeout",XrmoptionSepArg, 0 },
   { "-spin",   ".spin",   XrmoptionSepArg, 0 },
-  { "+spin",   ".spin",   XrmoptionNoArg, "False" },
+  { "+spin",   ".spin",   XrmoptionNoArg, "" },
   { "-wander", ".wander", XrmoptionNoArg, "True" },
   { "+wander", ".wander", XrmoptionNoArg, "False" },
   { "-labels", ".labels", XrmoptionNoArg, "True" },
@@ -590,10 +548,12 @@ ensure_bounding_box_visible (ModeInfo *mi)
 
 static void
 print_title_string (ModeInfo *mi, const char *string,
-                    GLfloat x, GLfloat y, GLfloat line_height)
+                    GLfloat x, GLfloat y, XFontStruct *font)
 {
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
-  
+  GLfloat line_height = font->ascent + font->descent;
+  GLfloat sub_shift = (line_height * 0.3);
+
   y -= line_height;
 
   glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
@@ -610,6 +570,8 @@ print_title_string (ModeInfo *mi, const char *string,
       glPushMatrix();
       {
         int i;
+        int x2 = x;
+        Bool sub_p = False;
         glLoadIdentity();
 
         gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
@@ -621,9 +583,27 @@ print_title_string (ModeInfo *mi, const char *string,
           {
             char c = string[i];
             if (c == '\n')
-              glRasterPos2f (x, (y -= line_height));
+              {
+                glRasterPos2f (x, (y -= line_height));
+                x2 = x;
+              }
+            else if (c == '(' && (isdigit (string[i+1])))
+              {
+                sub_p = True;
+                glRasterPos2f (x2, (y -= sub_shift));
+              }
+            else if (c == ')' && sub_p)
+              {
+                sub_p = False;
+                glRasterPos2f (x2, (y += sub_shift));
+              }
             else
-              glCallList (mc->font2_dlist + (int)(c));
+              {
+                glCallList (mc->font2_dlist + (int)(c));
+                x2 += (font->per_char
+                       ? font->per_char[c - font->min_char_or_byte2].width
+                       : font->min_bounds.width);
+              }
           }
       }
       glPopMatrix();
@@ -667,21 +647,6 @@ build_molecule (ModeInfo *mi)
       glEnable(GL_CULL_FACE);
     }
 
-#if 0
-  if (do_labels && !wire)
-    {
-      /* This is so all polygons are drawn slightly farther back in the depth
-         buffer, so that when we render text directly on top of the spheres,
-         it still shows up. */
-      glEnable (GL_POLYGON_OFFSET_FILL);
-      glPolygonOffset (1.0, (do_bonds ? 10.0 : 35.0));
-    }
-  else
-    {
-      glDisable (GL_POLYGON_OFFSET_FILL);
-    }
-#endif
-
   if (!wire)
     set_atom_color (mi, 0, False);
 
@@ -715,7 +680,7 @@ build_molecule (ModeInfo *mi)
             tube (from->x, from->y, from->z,
                   to->x,   to->y,   to->z,
                   thickness, cap_size,
-                  faces, smooth, wire);
+                  faces, smooth, False, wire);
           }
       }
 
@@ -728,57 +693,13 @@ build_molecule (ModeInfo *mi)
         sphere (a->x, a->y, a->z, size, wire);
       }
 
-  /* Second pass to draw labels, after all atoms and bonds are in place
-   */
-  if (do_labels)
-    for (i = 0; i < m->natoms; i++)
-      {
-        molecule_atom *a = &m->atoms[i];
-        int j;
-
-        if (!wire)
-          {
-            glDisable (GL_LIGHTING);
-#if 1
-            glDisable (GL_DEPTH_TEST);
-#endif
-          }
-
-        if (!wire)
-          set_atom_color (mi, a, True);
-
-        glRasterPos3f (a->x, a->y, a->z);
-
-        /* Before drawing the string, shift the origin to center
-           the text over the origin of the sphere. */
-        glBitmap (0, 0, 0, 0,
-                  -string_width (mc->xfont1, a->label) / 2,
-                  -mc->xfont1->descent,
-                  NULL);
-
-        for (j = 0; j < strlen(a->label); j++)
-          glCallList (mc->font1_dlist + (int)(a->label[j]));
-
-        /* More efficient to always call glEnable() with correct values
-           than to call glPushAttrib()/glPopAttrib(), since reading
-           attributes from GL does a round-trip and  stalls the pipeline.
-         */
-        if (!wire)
-          {
-            glEnable(GL_LIGHTING);
-#if 1
-            glEnable(GL_DEPTH_TEST);
-#endif
-          }
-      }
-
   if (do_bbox)
     draw_bounding_box (mi);
 
   if (do_titles && m->label && *m->label)
     print_title_string (mi, m->label,
                         10, mi->xgwa.height - 10,
-                        mc->xfont2->ascent + mc->xfont2->descent);
+                        mc->xfont2);
 }
 
 
@@ -955,7 +876,7 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
       else if (!strncmp (s, "CONECT ", 7))
         {
           int atoms[11];
-          int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d ",
+          int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
                           &atoms[0], &atoms[1], &atoms[2], &atoms[3], 
                           &atoms[4], &atoms[5], &atoms[6], &atoms[7], 
                           &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
@@ -1040,7 +961,7 @@ typedef struct { char *atom; int count; } atom_and_count;
 /* When listing the components of a molecule, the convention is to put the
    carbon atoms first, the hydrogen atoms second, and the other atom types
    sorted alphabetically after that (although for some molecules, the usual
-   order is different, like for NH(3), but we don't special-case those.)
+   order is different: we special-case a few of those.)
  */
 static int
 cmp_atoms (const void *aa, const void *bb)
@@ -1056,6 +977,8 @@ cmp_atoms (const void *aa, const void *bb)
   return strcmp (a->atom, b->atom);
 }
 
+static void special_case_formula (char *f);
+
 static void
 generate_molecule_formula (molecule *m)
 {
@@ -1099,6 +1022,8 @@ generate_molecule_formula (molecule *m)
       i++;
     }
 
+  special_case_formula (buf);
+
   if (!m->label) m->label = strdup("");
   s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
   strcpy (s, m->label);
@@ -1109,6 +1034,21 @@ generate_molecule_formula (molecule *m)
   m->label = s;
 }
 
+/* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
+static void
+special_case_formula (char *f)
+{
+  if      (!strcmp(f, "H(2)Be"))   strcpy(f, "BeH(2)");
+  else if (!strcmp(f, "H(3)B"))    strcpy(f, "BH(3)");
+  else if (!strcmp(f, "H(3)N"))    strcpy(f, "NH(3)");
+  else if (!strcmp(f, "CHN"))      strcpy(f, "HCN");
+  else if (!strcmp(f, "CKN"))      strcpy(f, "KCN");
+  else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
+  else if (!strcmp(f, "Cl(3)P"))   strcpy(f, "PCl(3)");
+  else if (!strcmp(f, "Cl(5)P"))   strcpy(f, "PCl(5)");
+}
+
+
 static void
 insert_vertical_whitespace (char *string)
 {
@@ -1183,15 +1123,13 @@ reshape_molecule (ModeInfo *mi, int width, int height)
 
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
-
-  gluPerspective( 30.0, 1/h, 20.0, 40.0 );
-  gluLookAt( 0.0, 0.0, 15.0,
-             0.0, 0.0, 0.0,
-             0.0, 1.0, 0.0);
+  gluPerspective (30.0, 1/h, 20.0, 40.0);
 
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
-  glTranslatef(0.0, 0.0, -15.0);
+  gluLookAt( 0.0, 0.0, 30.0,
+             0.0, 0.0, 0.0,
+             0.0, 1.0, 0.0);
 
   glClear(GL_COLOR_BUFFER_BIT);
 }
@@ -1215,90 +1153,52 @@ gl_init (ModeInfo *mi)
 }
 
 
-/* lifted from lament.c */
-#define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
-#define RANDSIGN() ((random() & 1) ? 1 : -1)
-
 static void
-rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v)
+startup_blurb (ModeInfo *mi)
 {
-  double ppos = *pos;
-
-  /* tick position */
-  if (ppos < 0)
-    ppos = -(ppos + *v);
-  else
-    ppos += *v;
-
-  if (ppos > 1.0)
-    ppos -= 1.0;
-  else if (ppos < 0)
-    ppos += 1.0;
-
-  if (ppos < 0) abort();
-  if (ppos > 1.0) abort();
-  *pos = (*pos > 0 ? ppos : -ppos);
+  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
+  const char *s = "Constructing molecules...";
+  print_title_string (mi, s,
+                      mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
+                      10 + mc->xfont2->ascent + mc->xfont2->descent,
+                      mc->xfont2);
+  glFinish();
+  glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
+}
 
-  /* accelerate */
-  *v += *dv;
+Bool
+molecule_handle_event (ModeInfo *mi, XEvent *event)
+{
+  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
 
-  /* clamp velocity */
-  if (*v > max_v || *v < -max_v)
+  if (event->xany.type == ButtonPress &&
+      event->xbutton.button & Button1)
     {
-      *dv = -*dv;
+      mc->button_down_p = True;
+      gltrackball_start (mc->trackball,
+                         event->xbutton.x, event->xbutton.y,
+                         MI_WIDTH (mi), MI_HEIGHT (mi));
+      return True;
     }
-  /* If it stops, start it going in the other direction. */
-  else if (*v < 0)
+  else if (event->xany.type == ButtonRelease &&
+           event->xbutton.button & Button1)
     {
-      if (random() % 4)
-       {
-         *v = 0;
-
-         /* keep going in the same direction */
-         if (random() % 2)
-           *dv = 0;
-         else if (*dv < 0)
-           *dv = -*dv;
-       }
-      else
-       {
-         /* reverse gears */
-         *v = -*v;
-         *dv = -*dv;
-         *pos = -*pos;
-       }
+      mc->button_down_p = False;
+      return True;
     }
-
-  /* Alter direction of rotational acceleration randomly. */
-  if (! (random() % 120))
-    *dv = -*dv;
-
-  /* Change acceleration very occasionally. */
-  if (! (random() % 200))
+  else if (event->xany.type == MotionNotify &&
+           mc->button_down_p)
     {
-      if (*dv == 0)
-       *dv = 0.00001;
-      else if (random() & 1)
-       *dv *= 1.2;
-      else
-       *dv *= 0.8;
+      gltrackball_track (mc->trackball,
+                         event->xmotion.x, event->xmotion.y,
+                         MI_WIDTH (mi), MI_HEIGHT (mi));
+      return True;
     }
-}
-
 
-static void
-startup_blurb (ModeInfo *mi)
-{
-  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
-  const char *s = "Constructing molecules...";
-  print_title_string (mi, s,
-                      mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
-                      10 + mc->xfont2->ascent + mc->xfont2->descent,
-                      mc->xfont2->ascent + mc->xfont2->descent);
-  glFinish();
-  glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
+  return False;
 }
 
+
 void 
 init_molecule (ModeInfo *mi)
 {
@@ -1326,28 +1226,17 @@ init_molecule (ModeInfo *mi)
 
   wire = MI_IS_WIREFRAME(mi);
 
-  mc->rotx = frand(1.0) * RANDSIGN();
-  mc->roty = frand(1.0) * RANDSIGN();
-  mc->rotz = frand(1.0) * RANDSIGN();
-
-  /* bell curve from 0-6 degrees, avg 3 */
-  mc->dx = (frand(1) + frand(1) + frand(1)) / (360/2);
-  mc->dy = (frand(1) + frand(1) + frand(1)) / (360/2);
-  mc->dz = (frand(1) + frand(1) + frand(1)) / (360/2);
-
-  mc->d_max = mc->dx * 2;
-
-  mc->ddx = 0.00006 + frand(0.00003);
-  mc->ddy = 0.00006 + frand(0.00003);
-  mc->ddz = 0.00006 + frand(0.00003);
-
   {
+    Bool spinx=False, spiny=False, spinz=False;
+    double spin_speed   = 2.0;
+    double wander_speed = 0.03;
+
     char *s = do_spin;
     while (*s)
       {
-        if      (*s == 'x' || *s == 'X') mc->spin_x = 1;
-        else if (*s == 'y' || *s == 'Y') mc->spin_y = 1;
-        else if (*s == 'z' || *s == 'Z') mc->spin_z = 1;
+        if      (*s == 'x' || *s == 'X') spinx = True;
+        else if (*s == 'y' || *s == 'Y') spiny = True;
+        else if (*s == 'z' || *s == 'Z') spinz = True;
         else
           {
             fprintf (stderr,
@@ -1357,6 +1246,14 @@ init_molecule (ModeInfo *mi)
           }
         s++;
       }
+
+    mc->rot = make_rotator (spinx ? spin_speed : 0,
+                            spiny ? spin_speed : 0,
+                            spinz ? spin_speed : 0,
+                            1.0,
+                            do_wander ? wander_speed : 0,
+                            (spinx && spiny && spinz));
+    mc->trackball = gltrackball_init ();
   }
 
   mc->molecule_dlist = glGenLists(1);
@@ -1374,6 +1271,94 @@ init_molecule (ModeInfo *mi)
 }
 
 
+/* Put the labels on the atoms.
+   This can't be a part of the display list because of the games
+   we play with the translation matrix.
+ */
+void
+draw_labels (ModeInfo *mi)
+{
+  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
+  int wire = MI_IS_WIREFRAME(mi);
+  molecule *m = &mc->molecules[mc->which];
+  int i, j;
+
+  if (!do_labels)
+    return;
+
+  if (!wire)
+    glDisable (GL_LIGHTING);   /* don't light fonts */
+
+  for (i = 0; i < m->natoms; i++)
+    {
+      molecule_atom *a = &m->atoms[i];
+      GLfloat size = atom_size (a);
+      GLfloat m[4][4];
+
+      glPushMatrix();
+
+      if (!wire)
+        set_atom_color (mi, a, True);
+
+      /* First, we translate the origin to the center of the atom.
+
+         Then we retrieve the prevailing modelview matrix (which
+         includes any rotation, wandering, and user-trackball-rolling
+         of the scene.
+
+         We set the top 3x3 cells of that matrix to be the identity
+         matrix.  This removes all rotation from the matrix, while
+         leaving the translation alone.  This has the effect of
+         leaving the prevailing coordinate system perpendicular to
+         the camera view: were we to draw a square face, it would
+         be in the plane of the screen.
+
+         Now we translate by `size' toward the viewer -- so that the
+         origin is *just in front* of the ball.
+
+         Then we draw the label text, allowing the depth buffer to
+         do its work: that way, labels on atoms will be occluded
+         properly when other atoms move in front of them.
+
+         This technique (of neutralizing rotation relative to the
+         observer, after both rotations and translations have been
+         applied) is known as "billboarding".
+       */
+
+      glTranslatef(a->x, a->y, a->z);               /* get matrix */
+      glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);  /* load rot. identity */
+      m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
+      m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
+      m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
+      glLoadIdentity();                             /* reset modelview */
+      glMultMatrixf (&m[0][0]);                     /* replace with ours */
+
+      glTranslatef (0, 0, (size * 1.1));           /* move toward camera */
+
+      glRasterPos3f (0, 0, 0);                     /* draw text here */
+
+      /* Before drawing the string, shift the origin to center
+         the text over the origin of the sphere. */
+      glBitmap (0, 0, 0, 0,
+                -string_width (mc->xfont1, a->label) / 2,
+                -mc->xfont1->descent,
+                NULL);
+
+      for (j = 0; j < strlen(a->label); j++)
+        glCallList (mc->font1_dlist + (int)(a->label[j]));
+
+      glPopMatrix();
+    }
+
+  /* More efficient to always call glEnable() with correct values
+     than to call glPushAttrib()/glPopAttrib(), since reading
+     attributes from GL does a round-trip and  stalls the pipeline.
+   */
+  if (!wire)
+    glEnable (GL_LIGHTING);
+}
+
+
 void
 draw_molecule (ModeInfo *mi)
 {
@@ -1387,7 +1372,8 @@ draw_molecule (ModeInfo *mi)
   if (!mc->glx_context)
     return;
 
-  if (last + timeout <= now)   /* randomize molecules every -timeout seconds */
+  if (last + timeout <= now && /* randomize molecules every -timeout seconds */
+      !mc->button_down_p)
     {
       if (mc->nmolecules == 1)
         {
@@ -1433,43 +1419,24 @@ draw_molecule (ModeInfo *mi)
   glScalef(1.1, 1.1, 1.1);
 
   {
-    GLfloat x, y, z;
-
-    if (do_wander)
-      {
-        static int frame = 0;
-
-#       define SINOID(SCALE,SIZE) \
-        ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)
-
-        x = SINOID(0.031, 9.0);
-        y = SINOID(0.023, 9.0);
-        z = SINOID(0.017, 9.0);
-        frame++;
-        glTranslatef(x, y, z);
-      }
-
-    if (mc->spin_x || mc->spin_y || mc->spin_z)
-      {
-        x = mc->rotx;
-        y = mc->roty;
-        z = mc->rotz;
-        if (x < 0) x = 1 - (x + 1);
-        if (y < 0) y = 1 - (y + 1);
-        if (z < 0) z = 1 - (z + 1);
-
-        if (mc->spin_x) glRotatef(x * 360, 1.0, 0.0, 0.0);
-        if (mc->spin_y) glRotatef(y * 360, 0.0, 1.0, 0.0);
-        if (mc->spin_z) glRotatef(z * 360, 0.0, 0.0, 1.0);
-
-        rotate(&mc->rotx, &mc->dx, &mc->ddx, mc->d_max);
-        rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max);
-        rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max);
-      }
+    double x, y, z;
+    get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
+    glTranslatef((x - 0.5) * 9,
+                 (y - 0.5) * 9,
+                 (z - 0.5) * 9);
+
+    gltrackball_rotate (mc->trackball);
+
+    get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
+    glRotatef (x * 360, 1.0, 0.0, 0.0);
+    glRotatef (y * 360, 0.0, 1.0, 0.0);
+    glRotatef (z * 360, 0.0, 0.0, 1.0);
   }
 
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glCallList (mc->molecule_dlist);
+  draw_labels (mi);
+
   glPopMatrix ();
 
   if (mi->fps_p) do_fps (mi);