http://slackware.bholcomb.com/slackware/slackware-11.0/source/xap/xscreensaver/xscree...
[xscreensaver] / hacks / glx / pinion.c
index 413fc1a94db290f295329a09ae61edfc045cc247..fa3571f1d622616eb051969810f11949194a59a9 100644 (file)
@@ -1,4 +1,4 @@
-/* pinion, Copyright (c) 2004 Jamie Zawinski <jwz@jwz.org>
+/* pinion, Copyright (c) 2004-2006 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,12 +25,17 @@ 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 <ctype.h>
 
 #ifdef USE_GL /* whole file */
 
-#include <GL/glu.h>
+#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 {
   unsigned long id;          /* unique name */
@@ -81,6 +67,7 @@ typedef struct {
   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 */
+  enum { SMALL, MEDIUM, LARGE } size;  /* Controls complexity of mesh. */
   GLfloat color[4];
   GLfloat color2[4];
 
@@ -106,25 +93,31 @@ 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 */
+
+/* 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 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? */
-
 
 static XrmOptionDescRec opts[] = {
   { "-spin",   ".spinSpeed",   XrmoptionSepArg, 0 },
@@ -144,182 +137,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);
-}
-
-
-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;
+  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 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 +176,14 @@ 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 == SMALL ? "small" : g->size == MEDIUM ? "medium"
+                  : "large"),
+                 g->tooth_h * MI_HEIGHT(mi));
     }
 
   glNewList (pp->title_list, GL_COMPILE);
@@ -359,9 +198,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 ();
 }
@@ -483,10 +324,9 @@ 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)]; */
   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();
@@ -640,6 +480,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 = SMALL;
+    else if (pix <= 3.5) g->size = MEDIUM;
+    else                 g->size = LARGE;
+  }
+
   g->base_p = !parent;
 
   return g;
@@ -667,7 +516,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 +581,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 +614,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 +640,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 +684,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 +714,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 +726,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 +841,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 +879,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 +945,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 +977,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 +1061,7 @@ scroll_gears (ModeInfo *mi)
       else
         break;
       i++;
+      if (debug_one_gear_p) break;
     }
 
   /*
@@ -1250,6 +1100,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);
@@ -1358,8 +1209,8 @@ draw_disc (ModeInfo *mi, int segments,
       if (wire_p || ra != 0)
         glVertex3f (cth * ra, sth * ra, z);
       glVertex3f (cth * rb, sth * rb, z);
-      polys++;
     }
+  polys += segments;
   glEnd();
   return polys;
 }
@@ -1489,7 +1340,7 @@ draw_gear_nubs (ModeInfo *mi, gear *g)
   Bool wire_p = MI_IS_WIREFRAME(mi);
   int polys = 0;
   int i;
-  int steps = 20;
+  int steps = (g->size != LARGE ? 5 : 20);
   double r, size, height;
   GLfloat *cc;
   int which;
@@ -1514,7 +1365,8 @@ draw_gear_nubs (ModeInfo *mi, gear *g)
       glTranslatef (cos(th) * r, sin(th) * r, 0);
 
       if (wire_p && !wire_all_p)
-        polys += draw_ring (mi, steps/2, size, 0, 0, False);
+        polys += draw_ring (mi, (g->size == LARGE ? steps/2 : steps),
+                            size, 0, 0, False);
       else
         {
           polys += draw_disc (mi, steps, 0, size, -height, True);
@@ -1571,12 +1423,14 @@ draw_gear_schematic (ModeInfo *mi, gear *g)
 static int
 draw_gear_interior (ModeInfo *mi, gear *g)
 {
+  pinion_configuration *pp = &pps[MI_SCREEN(mi)];
   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;
+  if ((wire_p && !wire_all_p) || g->size != LARGE) steps /= 2;
+  if (g->size != LARGE && steps > 16) steps = 16;
 
   /* ring 1 (facing in) is done in draw_gear_teeth */
 
@@ -1584,8 +1438,8 @@ draw_gear_interior (ModeInfo *mi, gear *g)
    */
   if (g->inner_r2)
     {
-      GLfloat ra = g->inner_r;
-      GLfloat rb = g->inner_r2;
+      GLfloat ra = g->inner_r * 1.04;  /* slightly larger than inner_r */
+      GLfloat rb = g->inner_r2;        /*  since the points don't line up */
       GLfloat za = -g->thickness2/2;
       GLfloat zb =  g->thickness2/2;
 
@@ -1641,7 +1495,7 @@ draw_gear_interior (ModeInfo *mi, gear *g)
                     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;
+      GLfloat zb = g->coax_thickness/2 + pp->plane_displacement + cap_height;
 
       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
 
@@ -1659,10 +1513,10 @@ draw_gear_interior (ModeInfo *mi, gear *g)
 }
 
 
-/* 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.)
+/* gear_teeth_geometry 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:
@@ -1679,26 +1533,59 @@ draw_gear_interior (ModeInfo *mi, gear *g)
    us -- we only compute all these normals once per gear, instead of
    once per gear per frame.
  */
+
+typedef struct {
+  int npoints;
+  XYZ *points;
+  XYZ *fnormals;  /* face normals */
+  XYZ *pnormals;  /* point normals */
+} tooth_face;
+
+
 static void
-gear_teeth_geometry (ModeInfo *mi, gear *g,
-                     int *points_per_tooth_ret,
-                     XYZ **points_ret,
-                     XYZ **normals_ret)
+tooth_normals (tooth_face *f)
 {
   int i;
 
-  int ppt = 15; /* points per tooth */
+  /* Compute the face normals for each facet. */
+  for (i = 0; i < f->npoints; i++)
+    {
+      XYZ p1, p2, p3;
+      int a = i;
+      int b = (i == f->npoints-1 ? 0 : i+1);
+      p1 = f->points[a];
+      p2 = f->points[b];
+      p3 = p1;
+      p3.z++;
+      f->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 < f->npoints; i++)
+    {
+      int a = (i == 0 ? f->npoints-1 : i-1);
+      int b = i;
+      XYZ n1 = f->fnormals[a];   /* normal of [i-1 - i] face */
+      XYZ n2 = f->fnormals[b];   /* normal of [i - i+1] face */
+      f->pnormals[i].x = (n1.x + n2.x) / 2;
+      f->pnormals[i].y = (n1.y + n2.y) / 2;
+      f->pnormals[i].z = (n1.z + n2.z) / 2;
+    }
+}
 
-  GLfloat width = M_PI * 2 / g->nteeth;
 
+static void
+gear_teeth_geometry (ModeInfo *mi, gear *g,
+                     tooth_face *orim,      /* outer rim (the teeth) */
+                     tooth_face *irim)      /* inner rim (the hole)  */
+{
+  int i;
+  int ppt = 9;   /* max 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.
 
@@ -1725,37 +1612,45 @@ gear_teeth_geometry (ModeInfo *mi, gear *g,
         r4 ......__:_____________________________:________________
    */
 
-  GLfloat R = g->r;
-
   GLfloat r[20];
   GLfloat th[20];
+  GLfloat R = g->r;
 
   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[2] = R - (r[1]-R);
+  r[3] = R - (r[0]-R);
   r[4] = g->inner_r;
 
-  th[0] = -tw * 0.45;
+  th[0] = -tw * (g->size == SMALL ? 0.5 : g->size == MEDIUM ? 0.41 : 0.45);
   th[1] = -tw * 0.30;
-  th[2] = -tw *(0.16 - fudge);
-  th[3] = -tw * 0.04;
+  th[2] = -tw * (g->nteeth >= 5 ? 0.16 : 0.12);
+  th[3] = -tw * (g->size == MEDIUM ? 0.1 : 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[5] =  -th[3];
+  th[6] =  -th[2];
+  th[7] =  -th[1];
+  th[8] =  -th[0];
   th[9] =  width / 2;
   th[10]=  th[0] + width;
 
-  if (!points || !fnormals || !pnormals)
+  orim->npoints  = 0;
+  orim->points   = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->points));
+  orim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->fnormals));
+  orim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->pnormals));
+
+  irim->npoints  = 0;
+  irim->points   = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->points));
+  irim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->fnormals));
+  irim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->pnormals));
+
+  if (!orim->points || !orim->pnormals || !orim->fnormals ||
+      !irim->points || !irim->pnormals || !irim->fnormals)
     {
       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++)
@@ -1763,97 +1658,47 @@ gear_teeth_geometry (ModeInfo *mi, gear *g,
       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"! */
+#     define PUSH(OPR,IPR,PTH) \
+        orim->points[orim->npoints].x = cos(TH+th[(PTH)]) * r[(OPR)]; \
+        orim->points[orim->npoints].y = sin(TH+th[(PTH)]) * r[(OPR)]; \
+        orim->npoints++; \
+        irim->points[irim->npoints].x = cos(TH+th[(PTH)]) * r[(IPR)]; \
+        irim->points[irim->npoints].y = sin(TH+th[(PTH)]) * r[(IPR)]; \
+        irim->npoints++
+
+      if (g->size == SMALL)
+        {
+          PUSH(3, 4, 0);       /* tooth left 1 */
+          PUSH(0, 4, 4);       /* tooth top middle */
+        }
+      else if (g->size == MEDIUM)
+        {
+          PUSH(3, 4, 0);       /* tooth left 1 */
+          PUSH(0, 4, 3);       /* tooth top left */
+          PUSH(0, 4, 5);       /* tooth top right */
+          PUSH(3, 4, 8);       /* tooth right 3 */
+        }
+      else if (g->size == LARGE)
+        {
+          PUSH(3, 4, 0);       /* tooth left 1 */
+          PUSH(2, 4, 1);       /* tooth left 2 */
+          PUSH(1, 4, 2);       /* tooth left 3 */
+          PUSH(0, 4, 3);       /* tooth top left */
+          PUSH(0, 4, 5);       /* tooth top right */
+          PUSH(1, 4, 6);       /* tooth right 1 */
+          PUSH(2, 4, 7);       /* tooth right 2 */
+          PUSH(3, 4, 8);       /* tooth right 3 */
+          PUSH(3, 4, 9);       /* gap top */
+        }
+      else
+        abort();
 #     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);
+      if (i == 0 && orim->npoints > ppt) abort();  /* go update "ppt"! */
     }
 
-
-  /* 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;
+  tooth_normals (orim);
+  tooth_normals (irim);
 }
 
 
@@ -1863,147 +1708,171 @@ static int
 draw_gear_teeth (ModeInfo *mi, gear *g)
 {
   Bool wire_p = MI_IS_WIREFRAME(mi);
+  Bool show_normals_p = False;
   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);
+  tooth_face orim, irim;
+  gear_teeth_geometry (mi, g, &orim, &irim);
 
   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
 
-  for (i = 0; i < g->nteeth; i++)
+  /* Draw the outer rim (the teeth)
+     (In wire mode, this draws just the upright lines.)
+   */
+  glFrontFace (GL_CW);
+  glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
+  for (i = 0; i < orim.npoints; i++)
     {
-      int j;
-      GLfloat z;
+      glNormal3f (orim.pnormals[i].x, orim.pnormals[i].y, orim.pnormals[i].z);
+      glVertex3f (orim.points[i].x, orim.points[i].y, z1);
+      glVertex3f (orim.points[i].x, orim.points[i].y, z2);
 
-      int start1 = i * ppt;
-      int end1   = start1 + 9;
-      int start2 = end1   + 1;
-      int end2   = start2 + 4;
-      int mid    = end2;
+      /* Show the face normal vectors */
+      if (wire_p && show_normals_p)
+        {
+          XYZ n = orim.fnormals[i];
+          int a = i;
+          int b = (i == orim.npoints-1 ? 0 : i+1);
+          GLfloat x = (orim.points[a].x + orim.points[b].x) / 2;
+          GLfloat y = (orim.points[a].y + orim.points[b].y) / 2;
+          GLfloat z  = (z1 + z2) / 2;
+          glVertex3f (x, y, z);
+          glVertex3f (x + n.x, y + n.y, z);
+        }
 
-      /* Outside rim of the tooth
-       */
-      glFrontFace (GL_CW);
-      glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
-      for (j = start1; j < end1; j++)
+      /* Show the vertex normal vectors */
+      if (wire_p && show_normals_p)
         {
-          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++;
+          XYZ n = orim.pnormals[i];
+          GLfloat x = orim.points[i].x;
+          GLfloat y = orim.points[i].y;
+          GLfloat z  = (z1 + z2) / 2;
+          glVertex3f (x, y, z);
+          glVertex3f (x + n.x, y + n.y, z);
+        }
+    }
 
-# 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);
-            }
+  if (!wire_p)  /* close the quad loop */
+    {
+      glNormal3f (orim.pnormals[0].x, orim.pnormals[0].y, orim.pnormals[0].z);
+      glVertex3f (orim.points[0].x, orim.points[0].y, z1);
+      glVertex3f (orim.points[0].x, orim.points[0].y, z2);
+    }
+  polys += orim.npoints;
+  glEnd();
 
-          /* 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 */
-        }
+  /* Draw the outer rim circles, in wire mode */
+  if (wire_p)
+    {
+      glBegin (GL_LINE_LOOP);
+      for (i = 0; i < orim.npoints; i++)
+        glVertex3f (orim.points[i].x, orim.points[i].y, z1);
+      glEnd();
+      glBegin (GL_LINE_LOOP);
+      for (i = 0; i < orim.npoints; i++)
+        glVertex3f (orim.points[i].x, orim.points[i].y, z2);
       glEnd();
+    }
 
-      /* Some more lines for the outside rim of the tooth...
-       */
-      if (wire_p)
+
+  /* Draw the inner rim (the hole)
+     (In wire mode, this draws just the upright lines.)
+   */
+  glFrontFace (GL_CCW);
+  glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
+  for (i = 0; i < irim.npoints; i++)
+    {
+      glNormal3f(-irim.pnormals[i].x, -irim.pnormals[i].y,-irim.pnormals[i].z);
+      glVertex3f (irim.points[i].x, irim.points[i].y, z1);
+      glVertex3f (irim.points[i].x, irim.points[i].y, z2);
+
+      /* Show the face normal vectors */
+      if (wire_p && show_normals_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();
+          XYZ n = irim.fnormals[i];
+          int a = i;
+          int b = (i == irim.npoints-1 ? 0 : i+1);
+          GLfloat x = (irim.points[a].x + irim.points[b].x) / 2;
+          GLfloat y = (irim.points[a].y + irim.points[b].y) / 2;
+          GLfloat z  = (z1 + z2) / 2;
+          glVertex3f (x, y, z);
+          glVertex3f (x - n.x, y - n.y, z);
         }
 
-      /* Inside rim behind the tooth
-       */
-      glFrontFace (GL_CW);
-      glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
-      for (j = start2; j < end2; j++)
+      /* Show the vertex normal vectors */
+      if (wire_p && show_normals_p)
         {
-          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++;
+          XYZ n = irim.pnormals[i];
+          GLfloat x = irim.points[i].x;
+          GLfloat y = irim.points[i].y;
+          GLfloat z  = (z1 + z2) / 2;
+          glVertex3f (x, y, z);
+          glVertex3f (x - n.x, y - n.y, z);
         }
+    }
+
+  if (!wire_p)  /* close the quad loop */
+    {
+      glNormal3f (-irim.pnormals[0].x,-irim.pnormals[0].y,-irim.pnormals[0].z);
+      glVertex3f (irim.points[0].x, irim.points[0].y, z1);
+      glVertex3f (irim.points[0].x, irim.points[0].y, z2);
+    }
+  polys += irim.npoints;
+  glEnd();
+
+  /* Draw the inner rim circles, in wire mode
+   */
+  if (wire_p)
+    {
+      glBegin (GL_LINE_LOOP);
+      for (i = 0; i < irim.npoints; i++)
+        glVertex3f (irim.points[i].x, irim.points[i].y, z1);
+      glEnd();
+      glBegin (GL_LINE_LOOP);
+      for (i = 0; i < irim.npoints; i++)
+        glVertex3f (irim.points[i].x, irim.points[i].y, z2);
       glEnd();
+    }
 
-      /* Some more lines for the inside rim...
-       */
-      if (wire_p)
+
+  /* Draw the side (the flat bit)
+   */
+  if (!wire_p || wire_all_p)
+    {
+      GLfloat z;
+      if (irim.npoints != orim.npoints) abort();
+      for (z = z1; z <= z2; z += z2-z1)
         {
-          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);
+          glFrontFace (z == z1 ? GL_CCW : GL_CW);
+          glNormal3f (0, 0, z);
+          glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
+          for (i = 0; i < orim.npoints; i++)
+            {
+              glVertex3f (orim.points[i].x, orim.points[i].y, z);
+              glVertex3f (irim.points[i].x, irim.points[i].y, z);
+            }
+          if (!wire_p)  /* close the quad loop */
+            {
+              glVertex3f (orim.points[0].x, orim.points[0].y, z);
+              glVertex3f (irim.points[0].x, irim.points[0].y, z);
+            }
+          polys += orim.npoints;
           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... */
+  free (irim.points);
+  free (irim.fnormals);
+  free (irim.pnormals);
 
-                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 (orim.points);
+  free (orim.fnormals);
+  free (orim.pnormals);
 
-  free (points);
-  free (pnormals);
   return polys;
 }
 
@@ -2016,8 +1885,8 @@ 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;
+  static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
+  GLfloat shiny   = 128.0;
 
   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
@@ -2127,7 +1996,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 +2024,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 +2100,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 +2142,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 +2157,19 @@ 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))
+    {
+      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 +2194,7 @@ pinion_handle_event (ModeInfo *mi, XEvent *event)
 }
 
 
-void 
+ENTRYPOINT void 
 init_pinion (ModeInfo *mi)
 {
   pinion_configuration *pp;
@@ -2347,7 +2224,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 +2250,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 +2333,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 +2350,6 @@ draw_pinion (ModeInfo *mi)
   glXSwapBuffers(dpy, window);
 }
 
+XSCREENSAVER_MODULE ("Pinion", pinion)
+
 #endif /* USE_GL */