http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.05.tar.gz
[xscreensaver] / hacks / glx / pinion.c
index 413fc1a94db290f295329a09ae61edfc045cc247..aefbe711464f687078b07d793a2bdc465706ae74 100644 (file)
@@ -1,4 +1,4 @@
-/* pinion, Copyright (c) 2004 Jamie Zawinski <jwz@jwz.org>
+/* pinion, Copyright (c) 2004-2008 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
@@ -9,34 +9,15 @@
  * implied warranty.
  */
 
-#include <X11/Intrinsic.h>
-
-extern XtAppContext app;
-
-#define PROGCLASS       "Pinion"
-#define HACK_INIT       init_pinion
-#define HACK_DRAW       draw_pinion
-#define HACK_RESHAPE    reshape_pinion
-#define HACK_HANDLE_EVENT pinion_handle_event
-#define EVENT_MASK      PointerMotionMask
-#define sws_opts        xlockmore_opts
-
-#define DEF_SPIN_SPEED   "1.0"
-#define DEF_SCROLL_SPEED "1.0"
-#define DEF_GEAR_SIZE    "1.0"
-#define DEF_MAX_RPM      "900"
-
 #define DEFAULTS        "*delay:        15000              \n" \
                         "*showFPS:      False              \n" \
                         "*wireframe:    False              \n" \
-                        "*spinSpeed:  " DEF_SPIN_SPEED   " \n" \
-                        "*scrollSpeed:" DEF_SCROLL_SPEED " \n" \
-                        "*maxRPM:     " DEF_MAX_RPM      " \n" \
-                        "*gearSize:   " DEF_GEAR_SIZE    " \n" \
                         "*titleFont:  -*-times-bold-r-normal-*-180-*\n" \
                         "*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
                         "*titleFont3: -*-times-bold-r-normal-*-80-*\n"  \
 
+# define refresh_pinion 0
+# define release_pinion 0
 #undef countof
 #define countof(x) (sizeof((x))/sizeof((*x)))
 
@@ -44,49 +25,18 @@ extern XtAppContext app;
 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
 
 #include "xlockmore.h"
+#include "normals.h"
 #include "gltrackball.h"
+#include "glxfonts.h"
+#include "involute.h"
 #include <ctype.h>
 
 #ifdef USE_GL /* whole file */
 
-#include <GL/glu.h>
-
-typedef struct {
-  unsigned long id;          /* unique name */
-  double x, y, z;            /* position */
-  double r;                  /* radius of the gear, at middle of teeth */
-  double th;                 /* rotation (degrees) */
-
-  GLint nteeth;              /* how many teeth */
-  double tooth_w, tooth_h;   /* size of teeth */
-
-  double inner_r;            /* radius of the (larger) inside hole */
-  double inner_r2;           /* radius of the (smaller) inside hole, if any */
-  double inner_r3;           /* yet another */
-
-  double thickness;          /* height of the edge */
-  double thickness2;         /* height of the (smaller) inside disc if any */
-  double thickness3;         /* yet another */
-  int spokes;                /* how many spokes inside, if any */
-  int nubs;                  /* how many little nubbly bits, if any */
-  double spoke_thickness;    /* spoke versus hole */
-  GLfloat wobble;            /* factory defect! */
-  int motion_blur_p;        /* whether it's spinning too fast to draw */
-  int polygons;              /* how many polys in this gear */
-
-  double ratio;              /* gearing ratio with previous gears */
-  double rpm;                /* approximate revolutions per minute */
-
-  Bool base_p;               /* whether this gear begins a new train */
-  int coax_p;                /* whether this is one of a pair of bound gears.
-                                1 for first, 2 for second. */
-  double coax_thickness;     /* thickness of the other gear in the pair */
-  GLfloat color[4];
-  GLfloat color2[4];
-
-  GLuint dlist;
-} gear;
-
+#define DEF_SPIN_SPEED   "1.0"
+#define DEF_SCROLL_SPEED "1.0"
+#define DEF_GEAR_SIZE    "1.0"
+#define DEF_MAX_RPM      "900"
 
 typedef struct {
   GLXContext *glx_context;
@@ -106,24 +56,29 @@ typedef struct {
   XFontStruct *xfont1, *xfont2, *xfont3;
   GLuint font1_dlist, font2_dlist, font3_dlist;
   GLuint title_list;
+  int draw_tick;
+
+  GLfloat plane_displacement;        /* distance between coaxial gears */
+
+  int debug_size_failures;           /* for debugging messages */
+  int debug_position_failures;
+  unsigned long current_length;      /* gear count in current train */
+  unsigned long current_blur_length; /* how long have we been blurring? */
 
 } pinion_configuration;
 
 
 static pinion_configuration *pps = NULL;
-static GLfloat spin_speed, scroll_speed, max_rpm, gear_size;
-static GLfloat plane_displacement = 0.1;  /* distance between coaxial gears */
+
+/* command line arguments */
+static GLfloat spin_speed, scroll_speed, gear_size, max_rpm;
 
 static Bool verbose_p = False;            /* print progress on stderr */
-static Bool debug_placement_p = False;    /* extreme verbosity on stderr */
 static Bool debug_p = False;              /* render as flat schematic */
-static Bool debug_one_gear_p = False;     /* draw one big stationary gear */
-static Bool wire_all_p = False;           /* in wireframe, do not abbreviate */
 
-static int debug_size_failures;           /* for debugging messages */
-static int debug_position_failures;
-static unsigned long current_length;      /* gear count in current train */
-static unsigned long current_blur_length; /* how long have we been blurring? */
+/* internal debugging variables */
+static Bool debug_placement_p = False;    /* extreme verbosity on stderr */
+static Bool debug_one_gear_p = False;     /* draw one big stationary gear */
 
 
 static XrmOptionDescRec opts[] = {
@@ -144,182 +99,22 @@ static argtype vars[] = {
   {&verbose_p,    "verbose",     "Verbose",     "False",          t_Bool},
 };
 
-ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
-
-\f
-/* Computing normal vectors
- */
-
-typedef struct {
-  double x,y,z;
-} XYZ;
-
-/* Calculate the unit normal at p given two other points p1,p2 on the
-   surface. The normal points in the direction of p1 crossproduct p2
- */
-static XYZ
-calc_normal (XYZ p, XYZ p1, XYZ p2)
-{
-  XYZ n, pa, pb;
-  pa.x = p1.x - p.x;
-  pa.y = p1.y - p.y;
-  pa.z = p1.z - p.z;
-  pb.x = p2.x - p.x;
-  pb.y = p2.y - p.y;
-  pb.z = p2.z - p.z;
-  n.x = pa.y * pb.z - pa.z * pb.y;
-  n.y = pa.z * pb.x - pa.x * pb.z;
-  n.z = pa.x * pb.y - pa.y * pb.x;
-  return (n);
-}
-
-static void
-do_normal(GLfloat x1, GLfloat y1, GLfloat z1,
-          GLfloat x2, GLfloat y2, GLfloat z2,
-          GLfloat x3, GLfloat y3, GLfloat z3)
-{
-  XYZ p1, p2, p3, p;
-  p1.x = x1; p1.y = y1; p1.z = z1;
-  p2.x = x2; p2.y = y2; p2.z = z2;
-  p3.x = x3; p3.y = y3; p3.z = z3;
-  p = calc_normal (p1, p2, p3);
-  glNormal3f (p.x, p.y, p.z);
-}
+ENTRYPOINT ModeSpecOpt pinion_opts = {countof(opts), opts, countof(vars), vars, NULL};
 
 \f
 /* Font stuff
  */
 
-static void
-load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
-{
-  const char *font = get_string_resource (res, "Font");
-  XFontStruct *f;
-  Font id;
-  int first, last;
-
-  if (!font) font = "-*-times-bold-r-normal-*-180-*";
-
-  f = XLoadQueryFont(mi->dpy, font);
-  if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
-
-  id = f->fid;
-  first = f->min_char_or_byte2;
-  last = f->max_char_or_byte2;
-  
-  clear_gl_error ();
-  *dlistP = glGenLists ((GLuint) last+1);
-  check_gl_error ("glGenLists");
-  glXUseXFont(id, first, last-first+1, *dlistP + first);
-  check_gl_error ("glXUseXFont");
-
-  *fontP = f;
-}
-
-
 static void
 load_fonts (ModeInfo *mi)
 {
   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
-  load_font (mi, "titleFont",  &pp->xfont1, &pp->font1_dlist);
-  load_font (mi, "titleFont2", &pp->xfont2, &pp->font2_dlist);
-  load_font (mi, "titleFont3", &pp->xfont3, &pp->font3_dlist);
+  load_font (mi->dpy, "titleFont",  &pp->xfont1, &pp->font1_dlist);
+  load_font (mi->dpy, "titleFont2", &pp->xfont2, &pp->font2_dlist);
+  load_font (mi->dpy, "titleFont3", &pp->xfont3, &pp->font3_dlist);
 }
 
 
-static int
-string_width (XFontStruct *f, const char *c)
-{
-  int w = 0;
-  while (*c)
-    {
-      int cc = *((unsigned char *) c);
-      w += (f->per_char
-            ? f->per_char[cc-f->min_char_or_byte2].rbearing
-            : f->min_bounds.rbearing);
-      c++;
-    }
-  return w;
-}
-
-static void
-print_title_string (ModeInfo *mi, const char *string,
-                    GLfloat x, GLfloat y,
-                    XFontStruct *font, int font_dlist)
-{
-  GLfloat line_height = font->ascent + font->descent;
-  GLfloat sub_shift = (line_height * 0.3);
-  int cw = string_width (font, "m");
-  int tabs = cw * 7;
-
-  y -= line_height;
-
-  glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
-                GL_ENABLE_BIT);     /* for various glDisable calls */
-  glDisable (GL_LIGHTING);
-  glDisable (GL_DEPTH_TEST);
-  {
-    glMatrixMode(GL_PROJECTION);
-    glPushMatrix();
-    {
-      glLoadIdentity();
-
-      glMatrixMode(GL_MODELVIEW);
-      glPushMatrix();
-      {
-        int i;
-        int x2 = x;
-        Bool sub_p = False;
-        glLoadIdentity();
-
-        gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
-
-        glColor3f (0.8, 0.8, 0);
-
-        glRasterPos2f (x, y);
-        for (i = 0; i < strlen(string); i++)
-          {
-            char c = string[i];
-            if (c == '\n')
-              {
-                glRasterPos2f (x, (y -= line_height));
-                x2 = x;
-              }
-            else if (c == '\t')
-              {
-                x2 -= x;
-                x2 = ((x2 + tabs) / tabs) * tabs;  /* tab to tab stop */
-                x2 += x;
-                glRasterPos2f (x2, y);
-              }
-            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 (font_dlist + (int)(c));
-                x2 += (font->per_char
-                       ? font->per_char[c - font->min_char_or_byte2].width
-                       : font->min_bounds.width);
-              }
-          }
-      }
-      glPopMatrix();
-    }
-    glMatrixMode(GL_PROJECTION);
-    glPopMatrix();
-  }
-  glPopAttrib();
-
-  glMatrixMode(GL_MODELVIEW);
-}
 
 static void rpm_string (double rpm, char *s);
 
@@ -343,8 +138,15 @@ new_label (ModeInfo *mi)
     *label = 0;
   else
     {
-      sprintf (label, "%d teeth\n", g->nteeth);
+      sprintf (label, "%d teeth\n", (int) g->nteeth);
       rpm_string (g->rpm, label + strlen(label));
+      if (debug_p)
+        sprintf (label + strlen (label), "\nPolys:  %d\nModel:  %s  (%.2f)\n",
+                 g->polygons,
+                 (g->size == INVOLUTE_SMALL ? "small" :
+                  g->size == INVOLUTE_MEDIUM ? "medium"
+                  : "large"),
+                 g->tooth_h * MI_HEIGHT(mi));
     }
 
   glNewList (pp->title_list, GL_COMPILE);
@@ -359,9 +161,11 @@ new_label (ModeInfo *mi)
       else
         f = pp->xfont3, fl = pp->font3_dlist;                  /* tiny font */
 
-      print_title_string (mi, label,
-                          10, mi->xgwa.height - 10,
-                          f, fl);
+      glColor3f (0.8, 0.8, 0);
+      print_gl_string (mi->dpy, f, fl,
+                       mi->xgwa.width, mi->xgwa.height,
+                       10, mi->xgwa.height - 10,
+                       label);
     }
   glEndList ();
 }
@@ -430,48 +234,6 @@ rpm_string (double rpm, char *s)
 }
 
 
-
-/* Which of the gear's inside rings is the biggest? 
- */
-static int
-biggest_ring (gear *g, double *posP, double *sizeP, double *heightP)
-{
-  double r0 = (g->r - g->tooth_h/2);
-  double r1 = g->inner_r;
-  double r2 = g->inner_r2;
-  double r3 = g->inner_r3;
-  double w1 = (r1 ? r0 - r1 : r0);
-  double w2 = (r2 ? r1 - r2 : 0);
-  double w3 = (r3 ? r2 - r3 : 0);
-  double h1 = g->thickness;
-  double h2 = g->thickness2;
-  double h3 = g->thickness3;
-
-  if (g->spokes) w2 = 0;
-
-  if (w1 > w2 && w1 > w3)
-    {
-      if (posP)    *posP = (r0+r1)/2;
-      if (sizeP)   *sizeP = w1;
-      if (heightP) *heightP = h1;
-      return 0;
-    }
-  else if (w2 > w1 && w2 > w3)
-    {
-      if (posP)  *posP = (r1+r2)/2;
-      if (sizeP) *sizeP = w2;
-      if (heightP) *heightP = h2;
-      return 1;
-    }
-  else
-    {
-      if (posP)  *posP = (r2+r3)/2;
-      if (sizeP) *sizeP = w3;
-      if (heightP) *heightP = h3;
-      return 1;
-    }
-}
-
 \f
 /* Layout and stuff.
  */
@@ -483,15 +245,17 @@ biggest_ring (gear *g, double *posP, double *sizeP, double *heightP)
 static gear *
 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
 {
-  /* pinion_configuration *pp = &pps[MI_SCREEN(mi)]; */
+  pinion_configuration *pp = &pps[MI_SCREEN(mi)];
   gear *g = (gear *) calloc (1, sizeof (*g));
   int loop_count = 0;
-  static unsigned long id = 0;
+  static unsigned long id = 0;  /* only used in debugging output */
 
   if (!g) return 0;
   if (coaxial_p && !parent) abort();
   g->id = ++id;
 
+  g->coax_displacement = pp->plane_displacement;
+
   while (1)
     {
       loop_count++;
@@ -547,6 +311,8 @@ new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
       if (parent->r < g->r  * 0.6) break;  /* g much larger than parent  */
     }
 
+  /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */
+
   /* Colorize
    */
   g->color[0] = 0.5 + frand(0.5);
@@ -628,7 +394,7 @@ new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
   if (g->nteeth > 5)
     {
       double size = 0;
-      biggest_ring (g, 0, &size, 0);
+      involute_biggest_ring (g, 0, &size, 0);
       if (size > g->r * 0.2 && (random() % 5) == 0)
         {
           g->nubs = 1 + (random() % 16);
@@ -640,6 +406,15 @@ new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
   if (g->inner_r2 > g->inner_r) abort();
   if (g->inner_r  > g->r) abort();
 
+  /* Decide how complex the polygon model should be.
+   */
+  {
+    double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
+    if (pix <= 2.5)      g->size = INVOLUTE_SMALL;
+    else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
+    else                 g->size = INVOLUTE_LARGE;
+  }
+
   g->base_p = !parent;
 
   return g;
@@ -667,7 +442,7 @@ place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
                  progname,
                  (g->r + g->tooth_h), gear_size,
                  pp->vp_width, pp->vp_height);
-      debug_size_failures++;
+      pp->debug_size_failures++;
       return False;
     }
 
@@ -732,7 +507,7 @@ place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
     }
   else if (coaxial_p)
     {
-      double off = plane_displacement;
+      double off = pp->plane_displacement;
 
       g->x = parent->x;
       g->y = parent->y;
@@ -765,7 +540,7 @@ place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
           if (verbose_p && debug_placement_p)
             fprintf (stderr, "%s: placement: bad depth: %.2f\n",
                      progname, g->z);
-          debug_position_failures++;
+          pp->debug_position_failures++;
           return False;
         }
     }
@@ -791,7 +566,7 @@ place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
           if (verbose_p && debug_placement_p)
             fprintf (stderr, "%s: placement: out of bounds: %s\n",
                      progname, (g->y > pp->vp_top ? "top" : "bottom"));
-          debug_position_failures++;
+          pp->debug_position_failures++;
           return False;
         }
 
@@ -835,7 +610,7 @@ place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
     {
       if (verbose_p && debug_placement_p)
         fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
-      debug_position_failures++;
+      pp->debug_position_failures++;
       return False;
     }
 
@@ -865,7 +640,7 @@ place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
             if (verbose_p && debug_placement_p)
               fprintf (stderr, "%s: placement: collision with %lu\n",
                        progname, og->id);
-            debug_position_failures++;
+            pp->debug_position_failures++;
             return False;
           }
       }
@@ -877,7 +652,7 @@ place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
   /* Make deeper gears be darker.
    */
   {
-    double depth = g->z / plane_displacement;
+    double depth = g->z / pp->plane_displacement;
     double brightness = 1 + (depth / 6);
     double limit = 0.4;
     if (brightness < limit)   brightness = limit;
@@ -992,8 +767,8 @@ push_gear (ModeInfo *mi)
   Bool last_ditch_coax_p = False;
   int loop_count = 0;
 
-  debug_size_failures = 0;
-  debug_position_failures = 0;
+  pp->debug_size_failures = 0;
+  pp->debug_position_failures = 0;
 
  AGAIN:
   loop_count++;
@@ -1030,7 +805,7 @@ push_gear (ModeInfo *mi)
      it's a safe guess that we've wandered off into the woods and aren't
      coming back.  Bail on this train.
    */
-  if (current_blur_length >= 10)
+  if (pp->current_blur_length >= 10)
     {
       if (verbose_p)
         fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
@@ -1096,7 +871,7 @@ push_gear (ModeInfo *mi)
                  "%s: placement: resetting growth zone!  "
                  "failed: %d size, %d pos\n",
                  progname,
-                 debug_size_failures, debug_position_failures);
+                 pp->debug_size_failures, pp->debug_position_failures);
       for (i = pp->ngears-1; i >= 0; i--)
         {
           gear *g = pp->gears[i];
@@ -1128,34 +903,34 @@ push_gear (ModeInfo *mi)
 
       fprintf (stderr, " %2d%%",
                (int) (g->r * 2 * 100 / pp->vp_height));
-      fprintf (stderr, "  %2d teeth", g->nteeth);
+      fprintf (stderr, "  %2d teeth", (int) g->nteeth);
       fprintf (stderr, " %3.0f rpm;", g->rpm);
 
       {
         char buf1[50], buf2[50], buf3[100];
         *buf1 = 0; *buf2 = 0; *buf3 = 0;
-        if (debug_size_failures)
-          sprintf (buf1, "%3d sz", debug_size_failures);
-        if (debug_position_failures)
-          sprintf (buf2, "%2d pos", debug_position_failures);
+        if (pp->debug_size_failures)
+          sprintf (buf1, "%3d sz", pp->debug_size_failures);
+        if (pp->debug_position_failures)
+          sprintf (buf2, "%2d pos", pp->debug_position_failures);
         if (*buf1 || *buf2)
           sprintf (buf3, " tries: %-7s%s", buf1, buf2);
         fprintf (stderr, "%-21s", buf3);
       }
 
-      if (g->base_p) fprintf (stderr, " RESET %lu", current_length);
+      if (g->base_p) fprintf (stderr, " RESET %lu", pp->current_length);
       fprintf (stderr, "\n");
     }
 
   if (g->base_p)
-    current_length = 1;
+    pp->current_length = 1;
   else
-    current_length++;
+    pp->current_length++;
 
   if (g->motion_blur_p)
-    current_blur_length++;
+    pp->current_blur_length++;
   else
-    current_blur_length = 0;
+    pp->current_blur_length = 0;
 }
 
 
@@ -1212,6 +987,7 @@ scroll_gears (ModeInfo *mi)
       else
         break;
       i++;
+      if (debug_one_gear_p) break;
     }
 
   /*
@@ -1250,6 +1026,7 @@ static void
 ffwd (ModeInfo *mi)
 {
   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+  if (debug_one_gear_p) return;
   while (1)
     {
       gear *g = farthest_gear (mi, True);
@@ -1261,790 +1038,13 @@ ffwd (ModeInfo *mi)
 
 
 \f
-/* Rendering the 3D objects into the scene.
- */
-
-
-/* Draws an uncapped tube of the given radius extending from top to bottom,
-   with faces pointing either in or out.
- */
-static int
-draw_ring (ModeInfo *mi, int segments,
-           GLfloat r, GLfloat top, GLfloat bottom, Bool in_p)
-{
-  int i;
-  int polys = 0;
-  Bool wire_p = MI_IS_WIREFRAME(mi);
-  GLfloat width = M_PI * 2 / segments;
-
-  if (top != bottom)
-    {
-      glFrontFace (in_p ? GL_CCW : GL_CW);
-      glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
-      for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
-        {
-          GLfloat th = i * width;
-          GLfloat cth = cos(th);
-          GLfloat sth = sin(th);
-          if (in_p)
-            glNormal3f (-cth, -sth, 0);
-          else
-            glNormal3f (cth, sth, 0);
-          glVertex3f (cth * r, sth * r, top);
-          glVertex3f (cth * r, sth * r, bottom);
-        }
-      polys += segments;
-      glEnd();
-    }
-
-  if (wire_p)
-    {
-      glBegin (GL_LINE_LOOP);
-      for (i = 0; i < segments; i++)
-        {
-          GLfloat th = i * width;
-          glVertex3f (cos(th) * r, sin(th) * r, top);
-        }
-      glEnd();
-      glBegin (GL_LINE_LOOP);
-      for (i = 0; i < segments; i++)
-        {
-          GLfloat th = i * width;
-          glVertex3f (cos(th) * r, sin(th) * r, bottom);
-        }
-      glEnd();
-    }
-
-  return polys;
-}
-
-
-/* Draws a donut-shaped disc between the given radii,
-   with faces pointing either up or down.
-   The first radius may be 0, in which case, a filled disc is drawn.
- */
-static int
-draw_disc (ModeInfo *mi, int segments,
-           GLfloat ra, GLfloat rb, GLfloat z, Bool up_p)
-{
-  int i;
-  int polys = 0;
-  Bool wire_p = MI_IS_WIREFRAME(mi);
-  GLfloat width = M_PI * 2 / segments;
-
-  if (ra <  0) abort();
-  if (rb <= 0) abort();
-
-  if (ra == 0)
-    glFrontFace (up_p ? GL_CW : GL_CCW);
-  else
-    glFrontFace (up_p ? GL_CCW : GL_CW);
-
-  if (ra == 0)
-    glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
-  else
-    glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
-
-  glNormal3f (0, 0, (up_p ? -1 : 1));
-
-  if (ra == 0 && !wire_p)
-    glVertex3f (0, 0, z);
-
-  for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
-    {
-      GLfloat th = i * width;
-      GLfloat cth = cos(th);
-      GLfloat sth = sin(th);
-      if (wire_p || ra != 0)
-        glVertex3f (cth * ra, sth * ra, z);
-      glVertex3f (cth * rb, sth * rb, z);
-      polys++;
-    }
-  glEnd();
-  return polys;
-}
-
-
-/* Draws N thick radial lines between the given radii,
-   with faces pointing either up or down.
- */
-static int
-draw_spokes (ModeInfo *mi, int n, GLfloat thickness, int segments,
-             GLfloat ra, GLfloat rb, GLfloat z1, GLfloat z2)
-{
-  int i;
-  int polys = 0;
-  Bool wire_p = MI_IS_WIREFRAME(mi);
-  GLfloat width;
-  int segments2 = 0;
-  int insegs, outsegs;
-  int tick;
-  int state;
-
-  if (ra <= 0 || rb <= 0) abort();
-
-  segments *= 3;
-
-  while (segments2 < segments) /* need a multiple of N >= segments */
-    segments2 += n;            /* (yes, this is a moronic way to find that) */
-
-  insegs  = ((float) (segments2 / n) + 0.5) / thickness;
-  outsegs = (segments2 / n) - insegs;
-  if (insegs  <= 0) insegs = 1;
-  if (outsegs <= 0) outsegs = 1;
-
-  segments2 = (insegs + outsegs) * n;
-  width = M_PI * 2 / segments2;
-
-  tick = 0;
-  state = 0;
-  for (i = 0; i < segments2; i++, tick++)
-    {
-      GLfloat th1 = i * width;
-      GLfloat th2 = th1 + width;
-      GLfloat cth1 = cos(th1);
-      GLfloat sth1 = sin(th1);
-      GLfloat cth2 = cos(th2);
-      GLfloat sth2 = sin(th2);
-      GLfloat orb = rb;
-
-      int changed = (i == 0);
-
-      if (state == 0 && tick == insegs)
-        tick = 0, state = 1, changed = 1;
-      else if (state == 1 && tick == outsegs)
-        tick = 0, state = 0, changed = 1;
-
-      if ((state == 1 ||                /* in */
-           (state == 0 && changed)) &&
-          (!wire_p || wire_all_p))
-        {
-          /* top */
-          glFrontFace (GL_CCW);
-          glBegin (wire_p ? GL_LINES : GL_QUADS);
-          glNormal3f (0, 0, -1);
-          glVertex3f (cth1 * ra, sth1 * ra, z1);
-          glVertex3f (cth1 * rb, sth1 * rb, z1);
-          glVertex3f (cth2 * rb, sth2 * rb, z1);
-          glVertex3f (cth2 * ra, sth2 * ra, z1);
-          polys++;
-          glEnd();
-
-          /* bottom */
-          glFrontFace (GL_CW);
-          glBegin (wire_p ? GL_LINES : GL_QUADS);
-          glNormal3f (0, 0, 1);
-          glVertex3f (cth1 * ra, sth1 * ra, z2);
-          glVertex3f (cth1 * rb, sth1 * rb, z2);
-          glVertex3f (cth2 * rb, sth2 * rb, z2);
-          glVertex3f (cth2 * ra, sth2 * ra, z2);
-          polys++;
-          glEnd();
-        }
-
-      if (state == 1 && changed)   /* entering "in" state */
-        {
-          /* left */
-          glFrontFace (GL_CW);
-          glBegin (wire_p ? GL_LINES : GL_QUADS);
-          do_normal (cth1 * rb, sth1 * rb, z1,
-                     cth1 * ra, sth1 * ra, z1,
-                     cth1 * rb, sth1 * rb, z2);
-          glVertex3f (cth1 * ra, sth1 * ra, z1);
-          glVertex3f (cth1 * rb, sth1 * rb, z1);
-          glVertex3f (cth1 * rb, sth1 * rb, z2);
-          glVertex3f (cth1 * ra, sth1 * ra, z2);
-          polys++;
-          glEnd();
-        }
-
-      if (state == 0 && changed)   /* entering "out" state */
-        {
-          /* right */
-          glFrontFace (GL_CCW);
-          glBegin (wire_p ? GL_LINES : GL_QUADS);
-          do_normal (cth2 * ra, sth2 * ra, z1,
-                     cth2 * rb, sth2 * rb, z1,
-                     cth2 * rb, sth2 * rb, z2);
-          glVertex3f (cth2 * ra, sth2 * ra, z1);
-          glVertex3f (cth2 * rb, sth2 * rb, z1);
-          glVertex3f (cth2 * rb, sth2 * rb, z2);
-          glVertex3f (cth2 * ra, sth2 * ra, z2);
-          polys++;
-          glEnd();
-        }
-
-      rb = orb;
-    }
-  glEnd();
-  return polys;
-}
-
-
-/* Draws some bumps (embedded cylinders) on the gear.
- */
-static int
-draw_gear_nubs (ModeInfo *mi, gear *g)
-{
-  Bool wire_p = MI_IS_WIREFRAME(mi);
-  int polys = 0;
-  int i;
-  int steps = 20;
-  double r, size, height;
-  GLfloat *cc;
-  int which;
-  GLfloat width, off;
-
-  if (! g->nubs) return 0;
-
-  which = biggest_ring (g, &r, &size, &height);
-  size /= 5;
-  height *= 0.7;
-
-  cc = (which == 1 ? g->color : g->color2);
-  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cc);
-
-  width = M_PI * 2 / g->nubs;
-  off = M_PI / (g->nteeth * 2);  /* align first nub with a tooth */
-
-  for (i = 0; i < g->nubs; i++)
-    {
-      GLfloat th = (i * width) + off;
-      glPushMatrix();
-      glTranslatef (cos(th) * r, sin(th) * r, 0);
-
-      if (wire_p && !wire_all_p)
-        polys += draw_ring (mi, steps/2, size, 0, 0, False);
-      else
-        {
-          polys += draw_disc (mi, steps, 0, size, -height, True);
-          polys += draw_disc (mi, steps, 0, size,  height, False);
-          polys += draw_ring (mi, steps, size, -height, height, False);
-        }
-      glPopMatrix();
-    }
-  return polys;
-}
-
-
-
-/* Draws a much simpler representation of a gear.
- */
-static int
-draw_gear_schematic (ModeInfo *mi, gear *g)
-{
-  Bool wire_p = MI_IS_WIREFRAME(mi);
-  int polys = 0;
-  int i;
-  GLfloat width = M_PI * 2 / g->nteeth;
-
-  if (!wire_p) glDisable(GL_LIGHTING);
-  glColor3f (g->color[0] * 0.6, g->color[1] * 0.6, g->color[2] * 0.6);
-
-  glBegin (GL_LINES);
-  for (i = 0; i < g->nteeth; i++)
-    {
-      GLfloat th = (i * width) + (width/4);
-      glVertex3f (0, 0, -g->thickness/2);
-      glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
-    }
-  polys += g->nteeth;
-  glEnd();
-
-  glBegin (GL_LINE_LOOP);
-  for (i = 0; i < g->nteeth; i++)
-    {
-      GLfloat th = (i * width) + (width/4);
-      glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
-    }
-  polys += g->nteeth;
-  glEnd();
-
-  if (!wire_p) glEnable(GL_LIGHTING);
-  return polys;
-}
-
-
-/* Renders all the interior (non-toothy) parts of a gear:
-   the discs, axles, etc.
- */
-static int
-draw_gear_interior (ModeInfo *mi, gear *g)
-{
-  Bool wire_p = MI_IS_WIREFRAME(mi);
-  int polys = 0;
-
-  int steps = g->nteeth * 2;
-  if (steps < 10) steps = 10;
-  if (wire_p && !wire_all_p) steps /= 2;
-
-  /* ring 1 (facing in) is done in draw_gear_teeth */
-
-  /* ring 2 (facing in) and disc 2
-   */
-  if (g->inner_r2)
-    {
-      GLfloat ra = g->inner_r;
-      GLfloat rb = g->inner_r2;
-      GLfloat za = -g->thickness2/2;
-      GLfloat zb =  g->thickness2/2;
-
-      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color2);
-
-      if ((g->coax_p != 1 && !g->inner_r3) ||
-          (wire_p && wire_all_p))
-        polys += draw_ring (mi, steps, rb, za, zb, True);  /* ring facing in */
-
-      if (wire_p && wire_all_p)
-        polys += draw_ring (mi, steps, ra, za, zb, True);  /* ring facing in */
-
-      if (g->spokes)
-        polys += draw_spokes (mi, g->spokes, g->spoke_thickness,
-                              steps, ra, rb, za, zb);
-      else if (!wire_p || wire_all_p)
-        {
-          polys += draw_disc (mi, steps, ra, rb, za, True);  /* top plate */
-          polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
-        }
-    }
-
-  /* ring 3 (facing in and out) and disc 3
-   */
-  if (g->inner_r3)
-    {
-      GLfloat ra = g->inner_r2;
-      GLfloat rb = g->inner_r3;
-      GLfloat za = -g->thickness3/2;
-      GLfloat zb =  g->thickness3/2;
-
-      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
-
-      polys += draw_ring (mi, steps, ra, za, zb, False);  /* ring facing out */
-
-      if (g->coax_p != 1 || (wire_p && wire_all_p))
-        polys += draw_ring (mi, steps, rb, za, zb, True);  /* ring facing in */
-
-      if (!wire_p || wire_all_p)
-        {
-          polys += draw_disc (mi, steps, ra, rb, za, True);  /* top plate */
-          polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
-        }
-    }
-
-  /* axle tube
-   */
-  if (g->coax_p == 1)
-    {
-      GLfloat cap_height = g->coax_thickness/3;
-
-      GLfloat ra = (g->inner_r3 ? g->inner_r3 :
-                    g->inner_r2 ? g->inner_r2 :
-                    g->inner_r);
-      GLfloat za = -(g->thickness/2 + cap_height);
-      GLfloat zb = g->coax_thickness/2 + plane_displacement + cap_height;
-
-      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
-
-      if (wire_p && !wire_all_p) steps /= 2;
-
-      polys += draw_ring (mi, steps, ra, za, zb, False);  /* ring facing out */
-
-      if (!wire_p || wire_all_p)
-        {
-          polys += draw_disc (mi, steps, 0,  ra, za, True);  /* top plate */
-          polys += draw_disc (mi, steps, 0,  ra, zb, False); /* bottom plate */
-        }
-    }
-  return polys;
-}
-
-
-/* Computes the vertices and normals of the teeth of a gear.
-   This is the heavy lifting: there are a ton of polygons around the
-   perimiter of a gear, and the normals are difficult (not radial
-   or right angles.)
-
-   It would be nice if we could cache this, but the numbers are
-   different for essentially every gear:
-
-      - Every gear has a different inner_r, so the vertices of the
-        inner ring (and thus, the triangle fans on the top and bottom
-        faces) are different in a non-scalable way.
-
-      - If the ratio between tooth_w and tooth_h changes, the normals
-        on the outside edges of the teeth are invalid (this can happen
-        every time we start a new train.)
-
-   So instead, we rely on OpenGL display lists to do the cacheing for
-   us -- we only compute all these normals once per gear, instead of
-   once per gear per frame.
- */
-static void
-gear_teeth_geometry (ModeInfo *mi, gear *g,
-                     int *points_per_tooth_ret,
-                     XYZ **points_ret,
-                     XYZ **normals_ret)
-{
-  int i;
-
-  int ppt = 15; /* points per tooth */
-
-  GLfloat width = M_PI * 2 / g->nteeth;
-
-  GLfloat rh = g->tooth_h;
-  GLfloat tw = width;
-  GLfloat fudge = (g->nteeth >= 5 ? 0 : 0.04);   /* reshape small ones a bit */
-
-  XYZ *points   = (XYZ *) calloc (ppt * g->nteeth + 1, sizeof(*points));
-  XYZ *fnormals = (XYZ *) calloc (ppt * g->nteeth + 1, sizeof(*points));
-  XYZ *pnormals = (XYZ *) calloc (ppt * g->nteeth + 1, sizeof(*points));
-  int npoints = 0;
-
-  /* Approximate shape of an "involute" gear tooth.
-
-                                 (TH)
-                 th0 th1 th2 th3 th4 th5 th6 th7 th8   th9    th10
-                   :  :  :   :    :    :   :  :  :      :      :
-                   :  :  :   :    :    :   :  :  :      :      :
-        r0 ........:..:..:...___________...:..:..:......:......:..
-                   :  :  :  /:    :    :\  :  :  :      :      :
-                   :  :  : / :    :    : \ :  :  :      :      :
-                   :  :  :/  :    :    :  \:  :  :      :      :
-        r1 ........:.....@...:....:....:...@..:..:......:......:..
-                   :  : @:   :    :    :   :@ :  :      :      :
-    (R) ...........:...@.:...:....:....:...:.@..........:......:......
-                   :  :@ :   :    :    :   : @:  :      :      :
-        r2 ........:..@..:...:....:....:...:..@:........:......:..
-                   : /:  :   :    :    :   :  :\ :      :      :
-                   :/ :  :   :    :    :   :  : \:      :      : /
-        r3 ......__/..:..:...:....:....:...:..:..\______________/
-                   :  :  :   :    :    :   :  :  :      :      :
-                   |  :  :   :    :    :   :  :  |      :      :
-                   :  :  :   :    :    :   :  :  :      :      :
-                   |  :  :   :    :    :   :  :  |      :      :
-        r4 ......__:_____________________________:________________
-   */
-
-  GLfloat R = g->r;
-
-  GLfloat r[20];
-  GLfloat th[20];
-
-  r[0] = R + (rh * 0.5);
-  r[1] = R + (rh * 0.25);
-  r[2] = R - (rh * 0.25);
-  r[3] = R - (rh * 0.5);
-  r[4] = g->inner_r;
-
-  th[0] = -tw * 0.45;
-  th[1] = -tw * 0.30;
-  th[2] = -tw *(0.16 - fudge);
-  th[3] = -tw * 0.04;
-  th[4] =  0;
-  th[5] =  tw * 0.04;
-  th[6] =  tw *(0.16 - fudge);
-  th[7] =  tw * 0.30;
-  th[8] =  tw * 0.45;
-  th[9] =  width / 2;
-  th[10]=  th[0] + width;
-
-  if (!points || !fnormals || !pnormals)
-    {
-      fprintf (stderr, "%s: out of memory\n", progname);
-      exit (1);
-    }
-
-  npoints = 0;
-
-  /* First, compute the coordinates of every point used for every tooth.
-   */
-  for (i = 0; i < g->nteeth; i++)
-    {
-      GLfloat TH = (i * width) + (width/4);
-
-#     undef PUSH
-#     define PUSH(PR,PTH) \
-        points[npoints].x = cos(TH+th[(PTH)]) * r[(PR)]; \
-        points[npoints].y = sin(TH+th[(PTH)]) * r[(PR)]; \
-        npoints++
-
-      /* start1 = npoints; */
-
-      PUSH(3, 0);       /* tooth left 1 */
-      PUSH(2, 1);       /* tooth left 2 */
-      PUSH(1, 2);       /* tooth left 3 */
-      PUSH(0, 3);       /* tooth top 1 */
-      PUSH(0, 5);       /* tooth top 2 */
-      PUSH(1, 6);       /* tooth right 1 */
-      PUSH(2, 7);       /* tooth right 2 */
-      PUSH(3, 8);       /* tooth right 3 */
-      PUSH(3, 10);      /* gap top */
-
-      /* end1   = npoints; */
-
-      PUSH(4, 8);       /* gap interior */
-
-      /* start2 = npoints; */
-
-      PUSH(4, 10);      /* tooth interior 1 */
-      PUSH(4, 8);       /* tooth interior 2 */
-      PUSH(4, 4);       /* tooth bottom 1 */
-      PUSH(4, 0);       /* tooth bottom 2 */
-
-      /* end2 = npoints; */
-
-      PUSH(3, 4);       /* midpoint */
-
-      /* mid = npoints-1; */
-
-      if (i == 0 && npoints != ppt) abort();  /* go update "ppt"! */
-#     undef PUSH
-    }
-
-
-  /* Now compute the face normals for each facet on the tooth rim.
-   */
-  for (i = 0; i < npoints; i++)
-    {
-      XYZ p1, p2, p3;
-      p1 = points[i];
-      p2 = points[i+1];
-      p3 = p1;
-      p3.z++;
-      fnormals[i] = calc_normal (p1, p2, p3);
-    }
-
-
-  /* From the face normals, compute the vertex normals (by averaging
-     the normals of adjascent faces.)
-   */
-  for (i = 0; i < npoints; i++)
-    {
-      int a = (i == 0 ? npoints-1 : i-1);
-      int b = i;
-
-      /* Kludge to fix the normal on the last top point: since the
-         faces go all the way around, this normal pointed clockwise
-         instead of radially out. */
-      int start1 = (i / ppt) * ppt;
-      int end1   = start1 + 9;
-      XYZ n1, n2;
-
-      if (b == end1-1)
-        b = (start1 + ppt == npoints ? 0 : start1 + ppt);
-
-      n1 = fnormals[a];   /* normal of [i-1 - i] face */
-      n2 = fnormals[b];   /* normal of [i - i+1] face */
-      pnormals[i].x = (n1.x + n2.x) / 2;
-      pnormals[i].y = (n1.y + n2.y) / 2;
-      pnormals[i].z = (n1.z + n2.z) / 2;
-    }
-
-  free (fnormals);
-
-  if (points_ret)
-    *points_ret = points;
-  else
-    free (points);
-
-  if (normals_ret)
-    *normals_ret = pnormals;
-  else
-    free (pnormals);
-
-  if (points_per_tooth_ret)
-    *points_per_tooth_ret = ppt;
-}
-
-
-/* Renders all teeth of a gear.
- */
-static int
-draw_gear_teeth (ModeInfo *mi, gear *g)
-{
-  Bool wire_p = MI_IS_WIREFRAME(mi);
-  int polys = 0;
-  int i;
-
-  GLfloat z1 = -g->thickness/2;
-  GLfloat z2 =  g->thickness/2;
-
-  int ppt;
-  XYZ *points, *pnormals;
-
-  gear_teeth_geometry (mi, g, &ppt, &points, &pnormals);
-
-  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
-
-  for (i = 0; i < g->nteeth; i++)
-    {
-      int j;
-      GLfloat z;
-
-      int start1 = i * ppt;
-      int end1   = start1 + 9;
-      int start2 = end1   + 1;
-      int end2   = start2 + 4;
-      int mid    = end2;
-
-      /* Outside rim of the tooth
-       */
-      glFrontFace (GL_CW);
-      glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
-      for (j = start1; j < end1; j++)
-        {
-          glNormal3f (pnormals[j].x, pnormals[j].y, pnormals[j].z);
-          glVertex3f (points[j].x, points[j].y, z1);
-          glVertex3f (points[j].x, points[j].y, z2);
-          polys++;
-
-# if 0
-          /* Show the face normal vectors */
-          if (wire_p)
-            {
-              XYZ n = fnormals[j];
-              GLfloat x = (points[j].x + points[j+1].x) / 2;
-              GLfloat y = (points[j].y + points[j+1].y) / 2;
-              GLfloat z  = (z1 + z2) / 2;
-              glVertex3f (x, y, z);
-              glVertex3f (x + n.x, y + n.y, z);
-            }
-
-          /* Show the vertex normal vectors */
-          if (wire_p)
-            {
-              XYZ n = pnormals[j];
-              GLfloat x = points[j].x;
-              GLfloat y = points[j].y;
-              GLfloat z  = (z1 + z2) / 2;
-              glVertex3f (x, y, z);
-              glVertex3f (x + n.x, y + n.y, z);
-            }
-# endif /* 0 */
-        }
-      glEnd();
-
-      /* Some more lines for the outside rim of the tooth...
-       */
-      if (wire_p)
-        {
-          glBegin (GL_LINE_STRIP);
-          for (j = start1; j < end1; j++)
-            glVertex3f (points[j].x, points[j].y, z1);
-          glEnd();
-          glBegin (GL_LINE_STRIP);
-          for (j = start1; j < end1; j++)
-            glVertex3f (points[j].x, points[j].y, z2);
-          glEnd();
-        }
-
-      /* Inside rim behind the tooth
-       */
-      glFrontFace (GL_CW);
-      glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
-      for (j = start2; j < end2; j++)
-        {
-          glNormal3f (-points[j].x, -points[j].y, 0);
-          glVertex3f ( points[j].x,  points[j].y, z1);
-          glVertex3f ( points[j].x,  points[j].y, z2);
-          polys++;
-        }
-      glEnd();
-
-      /* Some more lines for the inside rim...
-       */
-      if (wire_p)
-        {
-          glBegin (GL_LINE_STRIP);
-          for (j = start2; j < end2; j++)
-            glVertex3f (points[j].x, points[j].y, z1);
-          glEnd();
-          glBegin (GL_LINE_STRIP);
-          for (j = start2; j < end2; j++)
-            glVertex3f (points[j].x, points[j].y, z2);
-          glEnd();
-        }
-
-      /* All top and bottom facets.  We can skip all of these in wire mode.
-       */
-      if (!wire_p || wire_all_p)
-        for (z = z1; z <= z2; z += z2-z1)
-          {
-            /* Flat edge of the tooth
-             */
-            glFrontFace (z == z1 ? GL_CW : GL_CCW);
-            glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
-            glNormal3f (0, 0, z);
-            for (j = start1; j < end2; j++)
-              {
-                if (j == end1-1 || j == end1 || j == start2)
-                  continue;  /* kludge... skip these... */
-
-                if (wire_p || j == start1)
-                  glVertex3f (points[mid].x, points[mid].y, z);
-                glVertex3f (points[j].x, points[j].y, z);
-                polys++;
-              }
-            glVertex3f (points[start1].x, points[start1].y, z);
-            glEnd();
-
-            /* Flat edge between teeth
-             */
-            glFrontFace (z == z1 ? GL_CW : GL_CCW);
-            glBegin (wire_p ? GL_LINES : GL_QUADS);
-            glNormal3f (0, 0, z);
-            glVertex3f (points[end1-1  ].x, points[end1-1  ].y, z);
-            glVertex3f (points[start2  ].x, points[start2  ].y, z);
-            glVertex3f (points[start2+1].x, points[start2+1].y, z);
-            glVertex3f (points[end1-2  ].x, points[end1-2  ].y, z);
-            polys++;
-            glEnd();
-          }
-    }
-
-  free (points);
-  free (pnormals);
-  return polys;
-}
-
-
-/* Render one gear, unrotated at 0,0.
- */
-static int
-draw_gear_1 (ModeInfo *mi, gear *g)
-{
-  Bool wire_p = MI_IS_WIREFRAME(mi);
-  int polys = 0;
-
-  static GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
-  static GLfloat shiny   = 128.0;
-
-  glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
-  glMateriali  (GL_FRONT, GL_SHININESS, shiny);
-  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
-  glColor3f (g->color[0], g->color[1], g->color[2]);
-
-  if (debug_p && wire_p)
-    polys += draw_gear_schematic (mi, g);
-  else
-    {
-      glPushMatrix();
-      glRotatef (g->wobble, 1, 0, 0);
-      polys += draw_gear_teeth (mi, g);
-      polys += draw_gear_interior (mi, g);
-      polys += draw_gear_nubs (mi, g);
-      glPopMatrix();
-    }
-  return polys;
-}
-
-
 /* Render one gear in the proper position, creating the gear's
    display list first if necessary.
  */
 static void
 draw_gear (ModeInfo *mi, int which)
 {
+  Bool wire_p = MI_IS_WIREFRAME(mi);
   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
   gear *g = pp->gears[which];
   GLfloat th;
@@ -2069,7 +1069,7 @@ draw_gear (ModeInfo *mi, int which)
         }
 
       glNewList (g->dlist, GL_COMPILE);
-      g->polygons = draw_gear_1 (mi, g);
+      g->polygons = draw_involute_gear (g, (wire_p && debug_p ? 2 : wire_p));
       glEndList ();
     }
 
@@ -2096,7 +1096,7 @@ draw_gear (ModeInfo *mi, int which)
   glPushName (g->id);
 
   if (! visible_p)
-    mi->polygon_count += draw_gear_schematic (mi, g);
+    mi->polygon_count += draw_involute_schematic (g, wire_p);
   else
     {
       glCallList (g->dlist);
@@ -2127,7 +1127,7 @@ draw_gears (ModeInfo *mi)
   /* draw a line connecting gears that are, uh, geared. */
   if (debug_p)
     {
-      static GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
+      static const GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
       GLfloat off = 0.1;
       GLfloat ox=0, oy=0, oz=0;
 
@@ -2155,7 +1155,7 @@ draw_gears (ModeInfo *mi)
 
 /* Mouse hit detection
  */
-void
+static void
 find_mouse_gear (ModeInfo *mi)
 {
   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
@@ -2231,7 +1231,7 @@ find_mouse_gear (ModeInfo *mi)
 
 /* Window management, etc
  */
-void
+ENTRYPOINT void
 reshape_pinion (ModeInfo *mi, int width, int height)
 {
   GLfloat h = (GLfloat) height / (GLfloat) width;
@@ -2273,13 +1273,13 @@ reshape_pinion (ModeInfo *mi, int width, int height)
 }
 
 
-Bool
+ENTRYPOINT Bool
 pinion_handle_event (ModeInfo *mi, XEvent *event)
 {
   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
 
   if (event->xany.type == ButtonPress &&
-      event->xbutton.button & Button1)
+      event->xbutton.button == Button1)
     {
       pp->button_down_p = True;
       gltrackball_start (pp->trackball,
@@ -2288,11 +1288,21 @@ pinion_handle_event (ModeInfo *mi, XEvent *event)
       return True;
     }
   else if (event->xany.type == ButtonRelease &&
-           event->xbutton.button & Button1)
+           event->xbutton.button == Button1)
     {
       pp->button_down_p = False;
       return True;
     }
+  else if (event->xany.type == ButtonPress &&
+           (event->xbutton.button == Button4 ||
+            event->xbutton.button == Button5 ||
+            event->xbutton.button == Button6 ||
+            event->xbutton.button == Button7))
+    {
+      gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
+                              !!event->xbutton.state);
+      return True;
+    }
   else if (event->xany.type == MotionNotify &&
            pp->button_down_p)
     {
@@ -2317,7 +1327,7 @@ pinion_handle_event (ModeInfo *mi, XEvent *event)
 }
 
 
-void 
+ENTRYPOINT void 
 init_pinion (ModeInfo *mi)
 {
   pinion_configuration *pp;
@@ -2347,7 +1357,7 @@ init_pinion (ModeInfo *mi)
   pp->gears_size = 0;
   pp->gears = 0;
 
-  plane_displacement *= gear_size;
+  pp->plane_displacement = gear_size * 0.1;
 
   if (!wire)
     {
@@ -2373,18 +1383,19 @@ init_pinion (ModeInfo *mi)
 }
 
 
-void
+ENTRYPOINT void
 draw_pinion (ModeInfo *mi)
 {
   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
   Display *dpy = MI_DISPLAY(mi);
   Window window = MI_WINDOW(mi);
   Bool wire_p = MI_IS_WIREFRAME(mi);
-  static int tick = 0;
 
   if (!pp->glx_context)
     return;
 
+  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
+
   if (!pp->button_down_p)
     {
       if (!debug_one_gear_p || pp->ngears == 0)
@@ -2455,9 +1466,9 @@ draw_pinion (ModeInfo *mi)
         if (!wire_p) glEnable(GL_LIGHTING);
       }
 
-    if (tick++ > 10)   /* only do this every N frames */
+    if (pp->draw_tick++ > 10)   /* only do this every N frames */
       {
-        tick = 0;
+        pp->draw_tick = 0;
         find_mouse_gear (mi);
         new_label (mi);
       }
@@ -2472,4 +1483,6 @@ draw_pinion (ModeInfo *mi)
   glXSwapBuffers(dpy, window);
 }
 
+XSCREENSAVER_MODULE ("Pinion", pinion)
+
 #endif /* USE_GL */