From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / gears.c
index f97232bf4c624df34332a53ef32e3aa6a21a3890..0c292a2eae8ef04f1d6369c88b71875cad22ec21 100644 (file)
-/* -*- Mode: C; tab-width: 4 -*- */
-/* gears --- 3D gear wheels */
-
-#if !defined( lint ) && !defined( SABER )
-static const char sccsid[] = "@(#)gears.c      4.07 97/11/24 xlockmore";
-
-#endif
-
-/*-
- * Permission to use, copy, modify, and distribute this software and its
- * documentation for any purpose and without fee is hereby granted,
- * provided that the above copyright notice appear in all copies and that
- * both that copyright notice and this permission notice appear in
- * supporting documentation.
+/* gears, Copyright (c) 2007-2014 Jamie Zawinski <jwz@jwz.org>
  *
- * This file is provided AS IS with no warranties of any kind.  The author
- * shall have no liability with respect to the infringement of copyrights,
- * trade secrets or any patents by this file or any part thereof.  In no
- * event will the author be liable for any lost revenue or profits or
- * other special, indirect and consequential damages.
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
  *
- * Revision History:
- * 09-Feb-01: "Planetary" gear system added by jwz@jwz.org.
- * 10-May-97: Compatible with xscreensaver
- * 22-Mar-97: Added support for -mono mode, and monochrome X servers.
- *              Ed Mackey, emackey@netaxs.com
- * 13-Mar-97: Memory leak fix by Tom Schmidt <tschmidt@micron.com>
- * 1996: "written" by Danny Sung <dannys@ucla.edu>
- *       Based on 3-D gear wheels by Brian Paul which is in the public domain.
+ * Originally written by Brian Paul in 1996 or earlier;
+ * rewritten by jwz in Nov 2007.
  */
 
-/*-
- * PURIFY 3.0a on SunOS4 reports an unitialized memory read on each of
- * the glCallList() functions below when using MesaGL 2.1.  This has
- * been fixed in MesaGL 2.2 and later releases.
- */
-
-/*-
- * due to a Bug/feature in VMS X11/Intrinsic.h has to be placed before xlock.
- * otherwise caddr_t is not defined correctly
- */
-
-#include <X11/Intrinsic.h>
-
-#ifdef STANDALONE
-# define PROGCLASS                                     "Gears"
-# define HACK_INIT                                     init_gears
-# define HACK_DRAW                                     draw_gears
-# define HACK_RESHAPE                          reshape_gears
-# define gears_opts                                    xlockmore_opts
-# define DEFAULTS      "*count:                1       \n"                     \
-                                       "*cycles:               2       \n"                     \
-                                       "*delay:                20000   \n"                     \
-                                       "*planetary:    False   \n"                     \
-                                       "*showFPS:      False   \n"                     \
-                                       "*wireframe:    False   \n"
-# include "xlockmore.h"                                /* from the xscreensaver distribution */
-#else  /* !STANDALONE */
-# include "xlock.h"                                    /* from the xlockmore distribution */
-#endif /* !STANDALONE */
-
-#ifdef USE_GL
+#define DEFAULTS       "*delay:        30000       \n" \
+                       "*count:        0           \n" \
+                       "*showFPS:      False       \n" \
+                       "*wireframe:    False       \n" \
+                       "*suppressRotationAnimation: True\n" \
 
+# define refresh_gears 0
+# define release_gears 0
 #undef countof
 #define countof(x) (sizeof((x))/sizeof((*x)))
 
-#define DEF_PLANETARY "False"
+#include "xlockmore.h"
+#include "involute.h"
+#include "normals.h"
+#include "tube.h"
+#include "rotator.h"
+#include "gltrackball.h"
+#include <ctype.h>
+
+#ifdef USE_GL /* whole file */
+
+#undef BELLRAND
+#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
 
-static int planetary;
+#define DEF_SPIN        "True"
+#define DEF_WANDER      "True"
+#define DEF_SPEED       "1.0"
+
+typedef struct {
+  GLXContext *glx_context;
+  rotator *rot;
+  trackball_state *trackball;
+  Bool button_down_p;
+  Bool planetary_p;
+
+  int ngears;
+  gear **gears;
+
+  GLuint armature_dlist;
+  int armature_polygons;
+
+  struct { GLfloat x1, y1, x2, y2; } bbox;
+
+} gears_configuration;
+
+static gears_configuration *bps = NULL;
+
+static Bool do_spin;
+static GLfloat speed;
+static Bool do_wander;
 
 static XrmOptionDescRec opts[] = {
-  {"-planetary", ".gears.planetary", XrmoptionNoArg, (caddr_t) "true" },
-  {"+planetary", ".gears.planetary", XrmoptionNoArg, (caddr_t) "false" },
+  { "-spin",   ".spin",   XrmoptionNoArg, "True"  },
+  { "+spin",   ".spin",   XrmoptionNoArg, "False" },
+  { "-speed",  ".speed",  XrmoptionSepArg, 0      },
+  { "-wander", ".wander", XrmoptionNoArg, "True"  },
+  { "+wander", ".wander", XrmoptionNoArg, "False" },
 };
 
 static argtype vars[] = {
-  {(caddr_t *) &planetary, "planetary", "Planetary", DEF_PLANETARY, t_Bool},
+  {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_Bool},
+  {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
+  {&speed,     "speed",  "Speed",  DEF_SPEED,  t_Float},
 };
 
-ModeSpecOpt gears_opts = {countof(opts), opts, countof(vars), vars, NULL};
+ENTRYPOINT ModeSpecOpt gears_opts = {countof(opts), opts, countof(vars), vars, NULL};
 
-#ifdef USE_MODULES
-ModStruct   gears_description =
-{"gears", "init_gears", "draw_gears", "release_gears",
- "draw_gears", "init_gears", NULL, &gears_opts,
- 1000, 1, 2, 1, 4, 1.0, "",
- "Shows GL's gears", 0, NULL};
 
-#endif
+/* Window management, etc
+ */
+ENTRYPOINT void
+reshape_gears (ModeInfo *mi, int width, int height)
+{
+  GLfloat h = (GLfloat) height / (GLfloat) width;
 
-typedef struct {
+  glViewport (0, 0, (GLint) width, (GLint) height);
 
-  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 */
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  gluPerspective (30.0, 1/h, 1.0, 100.0);
 
-  GLuint      gear1, gear2, gear3;
-  GLuint      gear_inner, gear_outer;
-  GLuint      armature;
-  GLfloat     angle;
-  GLXContext *glx_context;
-  Window      window;
-} gearsstruct;
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+  gluLookAt( 0.0, 0.0, 30.0,
+             0.0, 0.0, 0.0,
+             0.0, 1.0, 0.0);
+
+# ifdef HAVE_MOBILE    /* Keep it the same relative size when rotated. */
+  {
+    int o = (int) current_device_rotation();
+    if (o != 0 && o != 180 && o != -180)
+      glScalef (1/h, 1/h, 1/h);
+  }
+# endif
+
+  glClear(GL_COLOR_BUFFER_BIT);
+}
 
-static gearsstruct *gears = NULL;
 
-/*-
- * Draw a gear wheel.  You'll probably want to call this function when
- * building a display list since we do a lot of trig here.
- *
- * Input:  inner_radius - radius of hole at center
- *         outer_radius - radius at center of teeth
- *         width - width of gear
- *         teeth - number of teeth
- *         tooth_depth - depth of tooth
- *         wire - true for wireframe mode
- */
 static void
-gear(GLfloat inner_radius, GLfloat outer_radius, GLfloat width,
-     GLint teeth, GLfloat tooth_depth, Bool wire, Bool invert)
+free_gear (gear *g)
 {
-       GLint       i;
-       GLfloat     r0, r1, r2;
-       GLfloat     angle, da;
-       GLfloat     u, v, len;
+  if (g->dlist)
+    glDeleteLists (g->dlist, 1);
+  free (g);
+}
 
-    if (!invert)
-      {
-        r0 = inner_radius;
-        r1 = outer_radius - tooth_depth / 2.0;
-        r2 = outer_radius + tooth_depth / 2.0;
-        glFrontFace(GL_CCW);
-      }
-    else
-      {
-        r0 = outer_radius;
-        r2 = inner_radius + tooth_depth / 2.0;
-        r1 = outer_radius - tooth_depth / 2.0;
-        glFrontFace(GL_CW);
-      }
 
-       da = 2.0 * M_PI / teeth / 4.0;
-
-       glShadeModel(GL_FLAT);
-
-       /* This subroutine got kind of messy when I added all the checks
-        * for wireframe mode.  A much cleaner solution that I sometimes
-        * use is to have a variable hold the value GL_LINE_LOOP when
-        * in wireframe mode, or hold the value GL_POLYGON otherwise.
-        * Then I just call glBegin(that_variable), give my polygon
-        * coordinates, and glEnd().  Pretty neat eh?  Too bad I couldn't
-        * integrate that trick here.
-        *                                  --Ed.
-        */
-
-       if (!wire)
-               glNormal3f(0.0, 0.0, 1.0);
-
-       /* draw front face */
-       if (!wire)
-               glBegin(GL_QUAD_STRIP);
-       for (i = 0; i <= teeth; i++) {
-               if (wire)
-                       glBegin(GL_LINES);
-               angle = i * 2.0 * M_PI / teeth;
-               glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
-               glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
-               if (!wire) {
-                       glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
-                       glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5);
-               } else {
-                       glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5);
-                       glVertex3f(r1 * cos(angle + 4 * da), r1 * sin(angle + 4 * da), width * 0.5);
-                       glEnd();
-               }
-       }
-       if (!wire)
-               glEnd();
-
-       /* draw front sides of teeth */
-       if (!wire)
-               glBegin(GL_QUADS);
-       da = 2.0 * M_PI / teeth / 4.0;
-       for (i = 0; i < teeth; i++) {
-               angle = i * 2.0 * M_PI / teeth;
-
-               if (wire)
-                       glBegin(GL_LINE_LOOP);
-               glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
-               glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
-               glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), width * 0.5);
-               glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5);
-               if (wire)
-                       glEnd();
-       }
-       if (!wire)
-               glEnd();
-
-
-       if (!wire)
-               glNormal3f(0.0, 0.0, -1.0);
-
-       /* draw back face */
-       if (!wire)
-               glBegin(GL_QUAD_STRIP);
-       for (i = 0; i <= teeth; i++) {
-               angle = i * 2.0 * M_PI / teeth;
-               if (wire)
-                       glBegin(GL_LINES);
-               glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
-               glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
-               if (!wire) {
-                       glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5);
-                       glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
-               } else {
-                       glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5);
-                       glVertex3f(r1 * cos(angle + 4 * da), r1 * sin(angle + 4 * da), -width * 0.5);
-                       glEnd();
-               }
-       }
-       if (!wire)
-               glEnd();
-
-       /* draw back sides of teeth */
-       if (!wire)
-               glBegin(GL_QUADS);
-       da = 2.0 * M_PI / teeth / 4.0;
-       for (i = 0; i < teeth; i++) {
-               angle = i * 2.0 * M_PI / teeth;
-
-               if (wire)
-                       glBegin(GL_LINE_LOOP);
-               glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5);
-               glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -width * 0.5);
-               glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
-               glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
-               if (wire)
-                       glEnd();
-       }
-       if (!wire)
-               glEnd();
-
-
-       /* draw outward faces of teeth */
-       if (!wire)
-               glBegin(GL_QUAD_STRIP);
-       for (i = 0; i <= teeth; i++) {
-               angle = i * 2.0 * M_PI / teeth;
-
-        if(!invert) {
-          u = r2 * cos(angle + da) - r1 * cos(angle);
-          v = r2 * sin(angle + da) - r1 * sin(angle);
-        } else {
-          u = r2 * cos(angle + da + M_PI/2) - r1 * cos(angle + M_PI/2);
-          v = r2 * sin(angle + da + M_PI/2) - r1 * sin(angle + M_PI/2);
-        }
+/* Create and return a new gear sized for placement next to or on top of
+   the given parent gear (if any.)  Returns 0 if out of memory.
+   [Mostly lifted from pinion.c]
+ */
+static gear *
+new_gear (ModeInfo *mi, gear *parent)
+{
+  gears_configuration *bp = &bps[MI_SCREEN(mi)];
+  gear *g = (gear *) calloc (1, sizeof (*g));
+  static unsigned long id = 0;  /* only used in debugging output */
 
-               len = sqrt(u * u + v * v);
-               u /= len;
-               v /= len;
-               glNormal3f(v, -u, 0.0);
+  if (!g) return 0;
+  g->id = ++id;
 
-               if (wire)
-                       glBegin(GL_LINES);
-               glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
-               glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
+  /* Pick the size of the teeth.
+   */
+  if (parent) /* adjascent gears need matching teeth */
+    {
+      g->tooth_w = parent->tooth_w;
+      g->tooth_h = parent->tooth_h;
+      g->tooth_slope = -parent->tooth_slope;
+    }
+  else                 /* gears that begin trains get any size they want */
+    {
+      g->tooth_w = 0.007 * (1.0 + BELLRAND(4.0));
+      g->tooth_h = 0.005 * (1.0 + BELLRAND(8.0));
+/*
+      g->tooth_slope = ((random() % 8)
+                        ? 0
+                        : 0.5 + BELLRAND(1));
+ */
+    }
 
-               glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
-               glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
+  /* Pick the number of teeth, and thus, the radius.
+   */
+  {
+    double c;
 
-        if(!invert)
-          glNormal3f(cos(angle), sin(angle), 0.0);
-        else
-          glNormal3f(cos(angle + M_PI/2), sin(angle + M_PI/2), 0.0);
+    if (!parent || bp->ngears > 4)
+      g->nteeth = 5 + BELLRAND (20);
+    else
+      g->nteeth = parent->nteeth * (0.5 + BELLRAND(2));
 
-               glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), width * 0.5);
-               glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -width * 0.5);
+    c = g->nteeth * g->tooth_w * 2;     /* circumference = teeth + gaps */
+    g->r = c / (M_PI * 2);              /* c = 2 pi r  */
+  }
 
-        if(!invert) {
-          u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
-          v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
-        } else {
-          u = r1 * cos(angle + 3 * da + M_PI/2) - r2 * cos(angle + 2 * da + M_PI/2);
-          v = r1 * sin(angle + 3 * da + M_PI/2) - r2 * sin(angle + 2 * da + M_PI/2);
+  g->thickness  = g->tooth_w + frand (g->r);
+  g->thickness2 = g->thickness * 0.7;
+  g->thickness3 = g->thickness;
+
+  /* Colorize
+   */
+  g->color[0] = 0.5 + frand(0.5);
+  g->color[1] = 0.5 + frand(0.5);
+  g->color[2] = 0.5 + frand(0.5);
+  g->color[3] = 1.0;
+
+  g->color2[0] = g->color[0] * 0.85;
+  g->color2[1] = g->color[1] * 0.85;
+  g->color2[2] = g->color[2] * 0.85;
+  g->color2[3] = g->color[3];
+
+
+  /* Decide on shape of gear interior:
+     - just a ring with teeth;
+     - that, plus a thinner in-set "plate" in the middle;
+     - that, plus a thin raised "lip" on the inner plate;
+     - or, a wide lip (really, a thicker third inner plate.)
+   */
+  if ((random() % 10) == 0)
+    {
+      /* inner_r can go all the way in; there's no inset disc. */
+      g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
+      g->inner_r2 = 0;
+      g->inner_r3 = 0;
+    }
+  else
+    {
+      /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
+      g->inner_r  = (g->r * 0.5)  + frand((g->r - g->tooth_h) * 0.4);
+      g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
+      g->inner_r3 = 0;
+
+      if (g->inner_r2 > (g->r * 0.2))
+        {
+          int nn = (random() % 10);
+          if (nn <= 2)
+            g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
+          else if (nn <= 7 && g->inner_r2 >= 0.1)
+            g->inner_r3 = g->inner_r2 - 0.01;
         }
+    }
 
-               glNormal3f(v, -u, 0.0);
+  /* If we have three discs, sometimes make the middle disc be spokes.
+   */
+  if (g->inner_r3 && ((random() % 5) == 0))
+    {
+      g->spokes = 2 + BELLRAND (5);
+      g->spoke_thickness = 1 + frand(7.0);
+      if (g->spokes == 2 && g->spoke_thickness < 2)
+        g->spoke_thickness += 1;
+    }
 
-               glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5);
-               glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5);
+  /* Sometimes add little nubbly bits, if there is room.
+   */
+  if (g->nteeth > 5)
+    {
+      double size = 0;
+      involute_biggest_ring (g, 0, &size, 0);
+      if (size > g->r * 0.2 && (random() % 5) == 0)
+        {
+          g->nubs = 1 + (random() % 16);
+          if (g->nubs > 8) g->nubs = 1;
+        }
+    }
 
-        if (!invert)
-          glNormal3f(cos(angle), sin(angle), 0.0);
-        else
-          glNormal3f(cos(angle + M_PI/2), sin(angle + M_PI/2), 0.0);
-
-               if (wire)
-                       glEnd();
-       }
-
-       if (!wire) {
-               glVertex3f(r1 * cos(0), r1 * sin(0), width * 0.5);
-               glVertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5);
-               glEnd();
-       }
-       if (!wire)
-               glShadeModel(GL_SMOOTH);
-
-       /* draw inside radius cylinder */
-       if (!wire)
-               glBegin(GL_QUAD_STRIP);
-       for (i = 0; i <= teeth; i++) {
-               angle = i * 2.0 * M_PI / teeth;
-               if (wire)
-                       glBegin(GL_LINES);
-
-        if (!invert)
-          glNormal3f(-cos(angle), -sin(angle), 0.0);
-        else
-          glNormal3f(cos(angle), sin(angle), 0.0);
-
-               glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
-               glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
-               if (wire) {
-                       glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
-                       glVertex3f(r0 * cos(angle + 4 * da), r0 * sin(angle + 4 * da), -width * 0.5);
-                       glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
-                       glVertex3f(r0 * cos(angle + 4 * da), r0 * sin(angle + 4 * da), width * 0.5);
-                       glEnd();
-               }
-       }
-       if (!wire)
-               glEnd();
+  if (g->inner_r3 > g->inner_r2) abort();
+  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 if (pix <= 25)  g->size = INVOLUTE_LARGE;
+    else                 g->size = INVOLUTE_HUGE;
+  }
 
-}
+  g->base_p = !parent;
 
+  return g;
+}
 
 
-static void
-tube(GLfloat radius, GLfloat width, GLint facets, Bool wire)
+/* Given a newly-created gear, place it next to its parent in the scene,
+   with its teeth meshed and the proper velocity.  Returns False if it
+   didn't work.  (Call this a bunch of times until either it works, or
+   you decide it's probably not going to.)
+   [Mostly lifted from pinion.c]
+ */
+static Bool
+place_gear (ModeInfo *mi, gear *g, gear *parent)
 {
-  GLint i;
-  GLfloat da = 2.0 * M_PI / facets / 4.0;
+  gears_configuration *bp = &bps[MI_SCREEN(mi)];
 
-  glFrontFace(GL_CCW);
+  /* Compute this gear's velocity.
+   */
+  if (! parent)
+    {
+      g->ratio = 0.8 + BELLRAND(0.4);  /* 0.8-1.2 = 8-12rpm @ 60fps */
+      g->th = 1; /* not 0 */
+    }
+  else
+    {
+      /* Gearing ratio is the ratio of the number of teeth to previous gear
+         (which is also the ratio of the circumferences.)
+       */
+      g->ratio = (double) parent->nteeth / (double) g->nteeth;
+
+      /* Set our initial rotation to match that of the previous gear,
+         multiplied by the gearing ratio.  (This is finessed later,
+         once we know the exact position of the gear relative to its
+         parent.)
+      */
+      g->th = -(parent->th * g->ratio);
+
+      if (g->nteeth & 1)    /* rotate 1/2 tooth-size if odd number of teeth */
+        {
+          double off = (180.0 / g->nteeth);
+          if (g->th > 0)
+            g->th += off;
+          else
+            g->th -= off;
+        }
 
-  /* draw bottom of tube */
+      /* ratios are cumulative for all gears in the train. */
+      g->ratio *= parent->ratio;
+    }
 
-  glShadeModel(GL_FLAT);
-  glNormal3f(0, 0, 1);
-  if (!wire)
+
+  if (parent)  /* Place the gear next to the parent. */
     {
-      glBegin(GL_TRIANGLE_FAN);
-      glVertex3f(0, 0, width * 0.5);
-      for (i = 0; i <= facets; i++) {
-        GLfloat angle = i * 2.0 * M_PI / facets;
-        glVertex3f(radius * cos(angle), radius * sin(angle), width * 0.5);
+      double r_off = parent->r + g->r;
+      int angle;
+
+      angle = (random() % 360) - 180;   /* -180 to +180 degrees */
+
+      g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
+      g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
+      g->z = parent->z;
+
+      /* avoid accidentally changing sign of "th" in the math below. */
+      g->th += (g->th > 0 ? 360 : -360);
+
+      /* Adjust the rotation of the gear so that its teeth line up with its
+         parent, based on the position of the gear and the current rotation
+         of the parent.
+       */
+      {
+        double p_c = 2 * M_PI * parent->r;  /* circumference of parent */
+        double g_c = 2 * M_PI * g->r;       /* circumference of g  */
+
+        double p_t = p_c * (angle/360.0);   /* distance travelled along
+                                               circumference of parent when
+                                               moving "angle" degrees along
+                                               parent. */
+        double g_rat = p_t / g_c;           /* if travelling that distance
+                                               along circumference of g,
+                                               ratio of g's circumference
+                                               travelled. */
+        double g_th = 360.0 * g_rat;        /* that ratio in degrees */
+
+        g->th += angle + g_th;
       }
-      glEnd();
     }
 
-  /* draw top of tube */
+  /* If the position we picked for this gear causes it to overlap
+     with any earlier gear in the train, give up.
+   */
+  {
+    int i;
 
-  glShadeModel(GL_FLAT);
-  glNormal3f(0, 0, -1);
-  glFrontFace(GL_CW);
-  if (!wire)
-    {
-      glBegin(GL_TRIANGLE_FAN);
-      glVertex3f(0, 0, -width * 0.5);
-      for (i = 0; i <= facets; i++) {
-        GLfloat angle = i * 2.0 * M_PI / facets;
-        glVertex3f(radius * cos(angle), radius * sin(angle), -width * 0.5);
+    for (i = bp->ngears-1; i >= 0; i--)
+      {
+        gear *og = bp->gears[i];
+
+        if (og == g) continue;
+        if (og == parent) continue;
+        if (g->z != og->z) continue;    /* Ignore unless on same layer */
+
+        /* Collision detection without sqrt:
+             d = sqrt(a^2 + b^2)   d^2 = a^2 + b^2
+             d < r1 + r2           d^2 < (r1 + r2)^2
+         */
+        if (((g->x - og->x) * (g->x - og->x) +
+             (g->y - og->y) * (g->y - og->y)) <
+            ((g->r + g->tooth_h + og->r + og->tooth_h) *
+             (g->r + g->tooth_h + og->r + og->tooth_h)))
+          return False;
       }
-      glEnd();
-    }
+  }
 
-  /* draw side of tube */
-  glFrontFace(GL_CW);
-  glShadeModel(GL_SMOOTH);
+  return True;
+}
 
-  if (!wire)
-    glBegin(GL_QUAD_STRIP);
 
-  for (i = 0; i <= facets; i++) {
-    GLfloat angle = i * 2.0 * M_PI / facets;
-    
-    if (wire)
-      glBegin(GL_LINES);
+/* Make a new gear, place it next to its parent in the scene,
+   with its teeth meshed and the proper velocity.  Returns the gear;
+   or 0 if it didn't work.  (Call this a bunch of times until either
+   it works, or you decide it's probably not going to.)
+   [Mostly lifted from pinion.c]
+ */
+static gear *
+place_new_gear (ModeInfo *mi, gear *parent)
+{
+  gears_configuration *bp = &bps[MI_SCREEN(mi)];
+  int loop_count = 0;
+  gear *g = 0;
 
-    glNormal3f(cos(angle), sin(angle), 0.0);
+  while (1)
+    {
+      loop_count++;
+      if (loop_count >= 100)
+        {
+          if (g)
+            free_gear (g);
+          g = 0;
+          break;
+        }
 
-    glVertex3f(radius * cos(angle), radius * sin(angle), -width * 0.5);
-    glVertex3f(radius * cos(angle), radius * sin(angle), width * 0.5);
+      g = new_gear (mi, parent);
+      if (!g) return 0;  /* out of memory? */
 
-    if (wire) {
-      glVertex3f(radius * cos(angle), radius * sin(angle), -width * 0.5);
-      glVertex3f(radius * cos(angle + 4 * da), radius * sin(angle + 4 * da), -width * 0.5);
-      glVertex3f(radius * cos(angle), radius * sin(angle), width * 0.5);
-      glVertex3f(radius * cos(angle + 4 * da), radius * sin(angle + 4 * da), width * 0.5);
-      glEnd();
+      if (place_gear (mi, g, parent))
+        break;
     }
-  }
 
-  if (!wire)
-    glEnd();
+  if (! g) return 0;
 
-  glFrontFace(GL_CCW);
+  /* We got a gear, and it is properly positioned.
+     Insert it in the scene.
+   */
+  bp->gears[bp->ngears++] = g;
+  return g;
 }
 
 
-static void
-arm(GLfloat length,
-    GLfloat width1, GLfloat height1,
-    GLfloat width2, GLfloat height2,
-    Bool wire)
+static int
+arm (GLfloat length,
+     GLfloat width1, GLfloat height1,
+     GLfloat width2, GLfloat height2,
+     Bool wire)
 {
+  int polys = 0;
   glShadeModel(GL_FLAT);
 
 #if 0  /* don't need these - they're embedded in other objects */
@@ -427,6 +428,7 @@ arm(GLfloat length,
   glVertex3f(-length/2,  width1/2, -height1/2);
   glVertex3f(-length/2,  width1/2,  height1/2);
   glVertex3f(-length/2, -width1/2,  height1/2);
+  polys++;
   glEnd();
 
   /* draw end 2 */
@@ -437,6 +439,7 @@ arm(GLfloat length,
   glVertex3f(length/2,  width2/2, -height2/2);
   glVertex3f(length/2,  width2/2,  height2/2);
   glVertex3f(length/2, -width2/2,  height2/2);
+  polys++;
   glEnd();
 #endif
 
@@ -448,6 +451,7 @@ arm(GLfloat length,
   glVertex3f(-length/2,  width1/2, -height1/2);
   glVertex3f( length/2,  width2/2, -height2/2);
   glVertex3f( length/2, -width2/2, -height2/2);
+  polys++;
   glEnd();
 
   /* draw bottom */
@@ -458,6 +462,7 @@ arm(GLfloat length,
   glVertex3f(-length/2,  width1/2, height1/2);
   glVertex3f( length/2,  width2/2, height2/2);
   glVertex3f( length/2, -width2/2, height2/2);
+  polys++;
   glEnd();
 
   /* draw left */
@@ -468,6 +473,7 @@ arm(GLfloat length,
   glVertex3f(-length/2, -width1/2,  height1/2);
   glVertex3f( length/2, -width2/2,  height2/2);
   glVertex3f( length/2, -width2/2, -height2/2);
+  polys++;
   glEnd();
 
   /* draw right */
@@ -478,587 +484,448 @@ arm(GLfloat length,
   glVertex3f(-length/2,  width1/2,  height1/2);
   glVertex3f( length/2,  width2/2,  height2/2);
   glVertex3f( length/2,  width2/2, -height2/2);
+  polys++;
   glEnd();
 
   glFrontFace(GL_CCW);
+
+  return polys;
 }
 
 
+static int
+ctube (GLfloat diameter, GLfloat width, Bool wire)
+{
+  tube (0, 0,  width/2,
+        0, 0, -width/2,
+        diameter, 0, 
+        32, True, True, wire);
+  return 0; /* #### */
+}
+
 static void
-draw(ModeInfo * mi)
+armature (ModeInfo *mi)
 {
-       gearsstruct *gp = &gears[MI_SCREEN(mi)];
-       int         wire = MI_IS_WIREFRAME(mi);
+  gears_configuration *bp = &bps[MI_SCREEN(mi)];
+  int wire = MI_IS_WIREFRAME(mi);
 
-       if (!wire) {
-               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-       } else {
-               glClear(GL_COLOR_BUFFER_BIT);
-       }
+  static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
+  GLfloat shiny = 128.0;
+  GLfloat color[4];
 
-       glPushMatrix();
+  color[0] = 0.5 + frand(0.5);
+  color[1] = 0.5 + frand(0.5);
+  color[2] = 0.5 + frand(0.5);
+  color[3] = 1.0;
 
+  bp->armature_polygons = 0;
+
+  bp->armature_dlist = glGenLists (1);
+  if (! bp->armature_dlist)
     {
-      GLfloat x = gp->rotx;
-      GLfloat y = gp->roty;
-      GLfloat z = gp->rotz;
-      if (x < 0) x = 1 - (x + 1);
-      if (y < 0) y = 1 - (y + 1);
-      if (z < 0) z = 1 - (z + 1);
-      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);
+      check_gl_error ("glGenLists");
+      abort();
     }
 
-    if (!planetary) {
-      glPushMatrix();
-      glTranslatef(-3.0, -2.0, 0.0);
-      glRotatef(gp->angle, 0.0, 0.0, 1.0);
-/* PURIFY 4.0.1 reports an unitialized memory read on the next line when using
-   * MesaGL 2.2 and -mono.  This has been fixed in MesaGL 2.3 and later. */
-      glCallList(gp->gear1);
-      glPopMatrix();
+  glNewList (bp->armature_dlist, GL_COMPILE);
 
-      glPushMatrix();
-      glTranslatef(3.1, -2.0, 0.0);
-      glRotatef(-2.0 * gp->angle - 9.0, 0.0, 0.0, 1.0);
-      glCallList(gp->gear2);
-      glPopMatrix();
+  glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
+  glMateriali  (GL_FRONT, GL_SHININESS, shiny);
+  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
+  glColor3f (color[0], color[1], color[2]);
 
-      glPushMatrix();
-      glTranslatef(-3.1, 4.2, 0.0);
-      glRotatef(-2.0 * gp->angle - 25.0, 0.0, 0.0, 1.0);
-      glCallList(gp->gear3);
-      glPopMatrix();
+  glPushMatrix();
 
-    } else { /* planetary */
+  {
+    GLfloat s = bp->gears[0]->r * 2.7;
+    s = s/5.6;
+    glScalef (s, s, s);
+  }
 
-      glScalef(0.8, 0.8, 0.8);
+  glTranslatef (0, 0, 1.4 + bp->gears[0]->thickness);
+  glRotatef (30, 0, 0, 1);
+
+  bp->armature_polygons += ctube (0.5, 10, wire);       /* center axle */
+
+  glPushMatrix();
+  glTranslatef(0.0, 4.2, -1);
+  bp->armature_polygons += ctube (0.5, 3, wire);       /* axle 1 */
+  glTranslatef(0, 0, 1.8);
+  bp->armature_polygons += ctube (0.7, 0.7, wire);
+  glPopMatrix();
+
+  glPushMatrix();
+  glRotatef(120, 0.0, 0.0, 1.0);
+  glTranslatef(0.0, 4.2, -1);
+  bp->armature_polygons += ctube (0.5, 3, wire);       /* axle 2 */
+  glTranslatef(0, 0, 1.8);
+  bp->armature_polygons += ctube (0.7, 0.7, wire);
+  glPopMatrix();
+
+  glPushMatrix();
+  glRotatef(240, 0.0, 0.0, 1.0);
+  glTranslatef(0.0, 4.2, -1);
+  bp->armature_polygons += ctube (0.5, 3, wire);       /* axle 3 */
+  glTranslatef(0, 0, 1.8);
+  bp->armature_polygons += ctube (0.7, 0.7, wire);
+  glPopMatrix();
+
+  glTranslatef(0, 0, 1.5);                           /* center disk */
+  bp->armature_polygons += ctube (1.5, 2, wire);
+
+  glPushMatrix();
+  glRotatef(270, 0, 0, 1);
+  glRotatef(-10, 0, 1, 0);
+  glTranslatef(-2.2, 0, 0);
+  bp->armature_polygons += arm (4.0, 1.0, 0.5,
+                                2.0, 1.0, wire);       /* arm 1 */
+  glPopMatrix();
+
+  glPushMatrix();
+  glRotatef(30, 0, 0, 1);
+  glRotatef(-10, 0, 1, 0);
+  glTranslatef(-2.2, 0, 0);
+  bp->armature_polygons += arm (4.0, 1.0, 0.5,
+                                2.0, 1.0, wire);       /* arm 2 */
+  glPopMatrix();
+
+  glPushMatrix();
+  glRotatef(150, 0, 0, 1);
+  glRotatef(-10, 0, 1, 0);
+  glTranslatef(-2.2, 0, 0);
+  bp->armature_polygons += arm (4.0, 1.0, 0.5,
+                                2.0, 1.0, wire);       /* arm 3 */
+  glPopMatrix();
+
+  glPopMatrix();
+
+  glEndList ();
+}
 
-      glPushMatrix();
-      glTranslatef(0.0, 4.2, 0.0);
-      glRotatef(gp->angle - 7.0, 0.0, 0.0, 1.0);
-      glCallList(gp->gear1);
-      glPopMatrix();
 
-      glPushMatrix();
-      glRotatef(120, 0.0, 0.0, 1.0);
-      glTranslatef(0.0, 4.2, 0.0);
-      glRotatef(gp->angle - 7.0, 0.0, 0.0, 1.0);
-      glCallList(gp->gear2);
-      glPopMatrix();
+static void
+planetary_gears (ModeInfo *mi)
+{
+  gears_configuration *bp = &bps[MI_SCREEN(mi)];
+  gear *g0, *g1, *g2, *g3, *g4;
+  GLfloat distance = 2.02;
+
+  bp->planetary_p = True;
+
+  g0 = new_gear (mi, 0);
+  g1 = new_gear (mi, 0);
+  g2 = new_gear (mi, 0);
+  g3 = new_gear (mi, 0);
+  g4 = new_gear (mi, 0);
+
+  if (! place_gear (mi, g0, 0)) abort();
+  if (! place_gear (mi, g1, 0)) abort();
+  if (! place_gear (mi, g2, 0)) abort();
+  if (! place_gear (mi, g3, 0)) abort();
+  if (! place_gear (mi, g4, 0)) abort();
+
+  g0->nteeth = 12 + (3 * (random() % 10));  /* must be multiple of 3 */
+  g0->tooth_w = g0->r / g0->nteeth;
+  g0->tooth_h = g0->tooth_w * 2.8;
+
+# define COPY(F) g4->F = g3->F = g2->F = g1->F = g0->F
+  COPY(r);
+  COPY(th);
+  COPY(nteeth);
+  COPY(tooth_w);
+  COPY(tooth_h);
+  COPY(tooth_slope);
+  COPY(inner_r);
+  COPY(inner_r2);
+  COPY(inner_r3);
+  COPY(thickness);
+  COPY(thickness2);
+  COPY(thickness3);
+  COPY(ratio);
+  COPY(size);
+# undef COPY
+
+  g1->x = cos (M_PI * 2 / 3) * g1->r * distance;
+  g1->y = sin (M_PI * 2 / 3) * g1->r * distance;
+
+  g2->x = cos (M_PI * 4 / 3) * g2->r * distance;
+  g2->y = sin (M_PI * 4 / 3) * g2->r * distance;
+
+  g3->x = cos (M_PI * 6 / 3) * g3->r * distance;
+  g3->y = sin (M_PI * 6 / 3) * g3->r * distance;
+
+  g4->x = 0;
+  g4->y = 0;
+  g4->th = -g3->th;
+
+  /* rotate central gear 1/2 tooth-size if odd number of teeth */
+  if (g4->nteeth & 1)
+    g4->th -= (180.0 / g4->nteeth);
+
+  g0->inverted_p  = True;
+  g0->x           = 0;
+  g0->y           = 0;
+  g0->nteeth      = g1->nteeth * 3;
+  g0->r           = g1->r * 3.05;
+  g0->inner_r     = g0->r * 0.8;
+  g0->inner_r2    = 0;
+  g0->inner_r3    = 0;
+  g0->th          = g1->th + (180 / g0->nteeth);
+  g0->ratio       = g1->ratio / 3;
+
+  g0->tooth_slope = 0;
+  g0->nubs        = 3;
+  g0->spokes      = 0;
+  g0->size        = INVOLUTE_LARGE;
+
+  bp->gears = (gear **) calloc (6, sizeof(**bp->gears));
+  bp->ngears = 0;
+
+  bp->gears[bp->ngears++] = g1;
+  bp->gears[bp->ngears++] = g2;
+  bp->gears[bp->ngears++] = g3;
+  bp->gears[bp->ngears++] = g4;
+  bp->gears[bp->ngears++] = g0;
+}
 
-      glPushMatrix();
-      glRotatef(240, 0.0, 0.0, 1.0);
-      glTranslatef(0.0, 4.2, 0.0);
-      glRotatef(gp->angle - 7.0, 0.0, 0.0, 1.0);
-      glCallList(gp->gear3);
-      glPopMatrix();
 
-      glPushMatrix();
-      glTranslatef(0.0, 0.0, 0.0);
-      glRotatef(-gp->angle, 0.0, 0.0, 1.0);
-      glCallList(gp->gear_inner);
-      glPopMatrix();
 
-      glPushMatrix();
-      glTranslatef(0.0, 0.0, 0.0);
-      glRotatef((gp->angle / 3.0) - 7.5, 0.0, 0.0, 1.0);
-      glCallList(gp->gear_outer);
-      glPopMatrix();
 
-      glPushMatrix();
-      glTranslatef(0.0, 0.0, 0.0);
-      glCallList(gp->armature);
-      glPopMatrix();
-    }
+ENTRYPOINT void 
+init_gears (ModeInfo *mi)
+{
+  gears_configuration *bp;
+  int wire = MI_IS_WIREFRAME(mi);
+  int i;
 
-       glPopMatrix();
-}
+  MI_INIT (mi, bps, NULL);
 
+  bp = &bps[MI_SCREEN(mi)];
 
+  bp->glx_context = init_GL(mi);
 
-/* new window size or exposure */
-void
-reshape_gears(ModeInfo *mi, int width, int height)
-{
-       GLfloat     h = (GLfloat) height / (GLfloat) width;
+  reshape_gears (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
 
-       glViewport(0, 0, (GLint) width, (GLint) height);
-       glMatrixMode(GL_PROJECTION);
-       glLoadIdentity();
-       glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
-       glMatrixMode(GL_MODELVIEW);
-       glLoadIdentity();
-       glTranslatef(0.0, 0.0, -40.0);
+  if (!wire)
+    {
+      GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
+      GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
+      GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
+      GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
+
+      glEnable(GL_LIGHTING);
+      glEnable(GL_LIGHT0);
+      glEnable(GL_DEPTH_TEST);
+      glEnable(GL_CULL_FACE);
+
+      glLightfv(GL_LIGHT0, GL_POSITION, pos);
+      glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
+      glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
+      glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
+    }
 
-       /* The depth buffer will be cleared, if needed, before the
-        * next frame.  Right now we just want to black the screen.
-        */
-       glClear(GL_COLOR_BUFFER_BIT);
+  if (! bp->rot)
+    {
+      double spin_speed   = 0.5;
+      double wander_speed = 0.01;
+      double spin_accel   = 0.25;
+
+      bp->rot = make_rotator (do_spin ? spin_speed : 0,
+                              do_spin ? spin_speed : 0,
+                              do_spin ? spin_speed : 0,
+                              spin_accel,
+                              do_wander ? wander_speed : 0,
+                              True
+                              );
+      bp->trackball = gltrackball_init (True);
+    }
 
-}
+  if (bp->gears)
+    {
+      for (i = 0; i < bp->ngears; i++)
+        free_gear (bp->gears[i]);
+      free (bp->gears);
+      bp->gears = 0;
+      bp->ngears = 0;
+    }
 
+  if (!(random() % 8))
+    {
+      planetary_gears (mi);
+    }
+  else
+    {
+      gear *g = 0;
+      int total_gears = MI_COUNT (mi);
 
-static void
-pinit(ModeInfo * mi)
-{
-       gearsstruct *gp = &gears[MI_SCREEN(mi)];
-       static GLfloat pos[4] =
-       {5.0, 5.0, 10.0, 1.0};
-       static GLfloat red[4] =
-       {0.8, 0.1, 0.0, 1.0};
-       static GLfloat green[4] =
-       {0.0, 0.8, 0.2, 1.0};
-       static GLfloat blue[4] =
-       {0.2, 0.2, 1.0, 1.0};
-       static GLfloat gray[4] =
-       {0.5, 0.5, 0.5, 1.0};
-       static GLfloat white[4] =
-       {1.0, 1.0, 1.0, 1.0};
-       int         wire = MI_IS_WIREFRAME(mi);
-       int         mono = MI_IS_MONO(mi);
-
-       if (!wire) {
-               glLightfv(GL_LIGHT0, GL_POSITION, pos);
-               glEnable(GL_CULL_FACE);
-               glEnable(GL_LIGHTING);
-               glEnable(GL_LIGHT0);
-               glEnable(GL_DEPTH_TEST);
-       }
-#if 0
-/*-
- * Messes up on multiscreen Pseudocolor:0 StaticGray(monochrome):1
- * 2nd time mode is run it is Grayscale on PseudoColor.
- * The code below forces monochrome on TrueColor.
- */
-       if (MI_IS_MONO(mi)) {
-               red[0] = red[1] = red[2] = 1.0;
-               green[0] = green[1] = green[2] = 1.0;
-               blue[0] = blue[1] = blue[2] = 1.0;
-       }
-#endif
+      bp->planetary_p = False;
 
-       /* make the gears */
-
-    if (! planetary) {
-
-      gp->gear1 = glGenLists(1);
-      glNewList(gp->gear1, GL_COMPILE);
-      if (wire) {
-               if (mono)
-          glColor4fv(white);
-               else
-          glColor4fv(red);
-      } else {
-               if (mono)
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
-               else
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
-      }
+      if (total_gears <= 0)
+        total_gears = 3 + fabs (BELLRAND (8) - 4);  /* 3 - 7, mostly 3. */
+      bp->gears = (gear **) calloc (total_gears+2, sizeof(**bp->gears));
+      bp->ngears = 0;
 
-      gear(1.0, 4.0, 1.0, 20, 0.7, wire, False);
-      glEndList();
-
-      gp->gear2 = glGenLists(1);
-      glNewList(gp->gear2, GL_COMPILE);
-      if (wire) {
-               if (mono)
-          glColor4fv(white);
-               else
-          glColor4fv(green);
-      } else {
-               if (mono)
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
-               else
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green);
-      }
-      gear(0.5, 2.0, 2.0, 10, 0.7, wire, False);
-      glEndList();
-
-      gp->gear3 = glGenLists(1);
-      glNewList(gp->gear3, GL_COMPILE);
-      if (wire) {
-               if (mono)
-          glColor4fv(white);
-               else
-          glColor4fv(blue);
-      } else {
-               if (mono)
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
-               else
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
-      }
-      gear(1.3, 2.0, 0.5, 10, 0.7, wire, False);
-      glEndList();
-      if (!wire)
-               glEnable(GL_NORMALIZE);
-
-    } else { /* planetary */
-
-      gp->gear1 = glGenLists(1);
-      glNewList(gp->gear1, GL_COMPILE);
-      if (wire) {
-               if (mono)
-          glColor4fv(white);
-               else
-          glColor4fv(red);
-      } else {
-               if (mono)
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
-               else
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
-      }
-      gear(1.3, 2.0, 2.0, 12, 0.7, wire, False);
-      glEndList();
-
-      gp->gear2 = glGenLists(1);
-      glNewList(gp->gear2, GL_COMPILE);
-      if (wire) {
-               if (mono)
-          glColor4fv(white);
-               else
-          glColor4fv(green);
-      } else {
-               if (mono)
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
-               else
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
-      }
-      gear(1.3, 2.0, 2.0, 12, 0.7, wire, False);
-      glEndList();
-
-      gp->gear3 = glGenLists(1);
-      glNewList(gp->gear3, GL_COMPILE);
-      if (wire) {
-               if (mono)
-          glColor4fv(white);
-               else
-          glColor4fv(blue);
-      } else {
-               if (mono)
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
-               else
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
-      }
-      gear(1.3, 2.0, 2.0, 12, 0.7, wire, False);
-      glEndList();
-      if (!wire)
-               glEnable(GL_NORMALIZE);
-
-
-      gp->gear_inner = glGenLists(1);
-      glNewList(gp->gear_inner, GL_COMPILE);
-      if (wire) {
-               if (mono)
-          glColor4fv(white);
-               else
-          glColor4fv(blue);
-      } else {
-               if (mono)
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
-               else
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
-      }
-      gear(1.0, 2.0, 2.0, 12, 0.7, wire, False);
-      glEndList();
-      if (!wire)
-               glEnable(GL_NORMALIZE);
-
-
-      gp->gear_outer = glGenLists(1);
-      glNewList(gp->gear_outer, GL_COMPILE);
-      if (wire) {
-               if (mono)
-          glColor4fv(white);
-               else
-          glColor4fv(blue);
-      } else {
-               if (mono)
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
-               else
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green);
-      }
-      gear(5.7, 7.0, 2.0, 36, 0.7, wire, True);
+      for (i = 0; i < total_gears; i++)
+        g = place_new_gear (mi, g);
+    }
 
-      /* put some nubs on the outer ring, so we can tell how it's moving */
-      glPushMatrix();
-      glTranslatef(7.0, 0, 0);
-      glRotatef(90, 0, 1, 0);
-      tube(0.5, 0.5, 10, wire);   /* nub 1 */
-      glPopMatrix();
 
-      glPushMatrix();
-      glRotatef(120, 0, 0, 1);
-      glTranslatef(7.0, 0, 0);
-      glRotatef(90, 0, 1, 0);
-      tube(0.5, 0.5, 10, wire);   /* nub 2 */
-      glPopMatrix();
-
-      glPushMatrix();
-      glRotatef(240, 0, 0, 1);
-      glTranslatef(7.0, 0, 0);
-      glRotatef(90, 0, 1, 0);
-      tube(0.5, 0.5, 10, wire);   /* nub 3 */
-      glPopMatrix();
-
-
-      glEndList();
-      if (!wire)
-               glEnable(GL_NORMALIZE);
-
-      gp->armature = glGenLists(1);
-      glNewList(gp->armature, GL_COMPILE);
-      if (wire) {
-        if (mono)
-          glColor4fv(white);
-        else
-          glColor4fv(blue);
-      } else {
-        if (mono)
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
-        else
-          glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
+  /* Center gears in scene. */
+  {
+    GLfloat minx=99999, miny=99999, maxx=-99999, maxy=-99999;
+    int i;
+    for (i = 0; i < bp->ngears; i++)
+      {
+        gear *g = bp->gears[i];
+        if (g->x - g->r < minx) minx = g->x - g->r;
+        if (g->x + g->r > maxx) maxx = g->x + g->r;
+        if (g->y - g->r < miny) miny = g->y - g->r;
+        if (g->y + g->r > maxy) maxy = g->y + g->r;
       }
+    bp->bbox.x1 = minx;
+    bp->bbox.y1 = miny;
+    bp->bbox.x2 = maxx;
+    bp->bbox.y2 = maxy;
+  }
 
-      glTranslatef(0, 0, 1.5);
-
-      tube(0.5, 10, 15, wire);       /* center axle */
+  /* Now render each gear into its display list.
+   */
+  for (i = 0; i < bp->ngears; i++)
+    {
+      gear *g = bp->gears[i];
+      g->dlist = glGenLists (1);
+      if (! g->dlist)
+        {
+          check_gl_error ("glGenLists");
+          abort();
+        }
 
-      glPushMatrix();
-      glTranslatef(0.0, 4.2, -1);
-      tube(0.5, 3, 15, wire);       /* axle 1 */
-      glTranslatef(0, 0, 1.8);
-      tube(0.7, 0.7, 15, wire);
-      glPopMatrix();
+      glNewList (g->dlist, GL_COMPILE);
+      g->polygons += draw_involute_gear (g, wire);
+      glEndList ();
+    }
+  if (bp->planetary_p)
+    armature (mi);
+}
 
-      glPushMatrix();
-      glRotatef(120, 0.0, 0.0, 1.0);
-      glTranslatef(0.0, 4.2, -1);
-      tube(0.5, 3, 15, wire);       /* axle 2 */
-      glTranslatef(0, 0, 1.8);
-      tube(0.7, 0.7, 15, wire);
-      glPopMatrix();
 
-      glPushMatrix();
-      glRotatef(240, 0.0, 0.0, 1.0);
-      glTranslatef(0.0, 4.2, -1);
-      tube(0.5, 3, 15, wire);       /* axle 3 */
-      glTranslatef(0, 0, 1.8);
-      tube(0.7, 0.7, 15, wire);
-      glPopMatrix();
+ENTRYPOINT void
+draw_gears (ModeInfo *mi)
+{
+  gears_configuration *bp = &bps[MI_SCREEN(mi)];
+  Display *dpy = MI_DISPLAY(mi);
+  Window window = MI_WINDOW(mi);
+  int i;
 
-      glTranslatef(0, 0, 1.5);      /* center disk */
-      tube(1.5, 2, 20, wire);
+  if (!bp->glx_context)
+    return;
 
-      glPushMatrix();
-      glRotatef(270, 0, 0, 1);
-      glRotatef(-10, 0, 1, 0);
-      glTranslatef(-2.2, 0, 0);
-      arm(4.0, 1.0, 0.5, 2.0, 1.0, wire);              /* arm 1 */
-      glPopMatrix();
+  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
 
-      glPushMatrix();
-      glRotatef(30, 0, 0, 1);
-      glRotatef(-10, 0, 1, 0);
-      glTranslatef(-2.2, 0, 0);
-      arm(4.0, 1.0, 0.5, 2.0, 1.0, wire);              /* arm 2 */
-      glPopMatrix();
+  glShadeModel(GL_SMOOTH);
 
-      glPushMatrix();
-      glRotatef(150, 0, 0, 1);
-      glRotatef(-10, 0, 1, 0);
-      glTranslatef(-2.2, 0, 0);
-      arm(4.0, 1.0, 0.5, 2.0, 1.0, wire);              /* arm 3 */
-      glPopMatrix();
-
-      glEndList();
-      if (!wire)
-        glEnable(GL_NORMALIZE);
-    }
-}
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_NORMALIZE);
+  glEnable(GL_CULL_FACE);
 
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
-/* lifted from lament.c */
-#define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
-#define RANDSIGN() ((random() & 1) ? 1 : -1)
+  glPushMatrix ();
 
-static void
-rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v)
-{
-  double ppos = *pos;
+  {
+    double x, y, z;
+    get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
+    glTranslatef ((x - 0.5) * 4,
+                  (y - 0.5) * 4,
+                  (z - 0.5) * 7);
 
-  /* tick position */
-  if (ppos < 0)
-    ppos = -(ppos + *v);
-  else
-    ppos += *v;
+    gltrackball_rotate (bp->trackball);
 
-  if (ppos > 1.0)
-    ppos -= 1.0;
-  else if (ppos < 0)
-    ppos += 1.0;
+    get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
 
-  if (ppos < 0) abort();
-  if (ppos > 1.0) abort();
-  *pos = (*pos > 0 ? ppos : -ppos);
+    /* add a little rotation for -no-spin mode */
+    x -= 0.14;
+    y -= 0.06;
 
-  /* accelerate */
-  *v += *dv;
+    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);
+  }
 
-  /* clamp velocity */
-  if (*v > max_v || *v < -max_v)
-    {
-      *dv = -*dv;
-    }
-  /* If it stops, start it going in the other direction. */
-  else if (*v < 0)
-    {
-      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;
-       }
-    }
+  /* Center the scene's bounding box in the window,
+     and scale it to fit. 
+   */
+  {
+    GLfloat w = bp->bbox.x2 - bp->bbox.x1;
+    GLfloat h = bp->bbox.y2 - bp->bbox.y1;
+    GLfloat s = 10.0 / (w > h ? w : h);
+    glScalef (s, s, s);
+    glTranslatef (-(bp->bbox.x1 + w/2),
+                  -(bp->bbox.y1 + h/2),
+                  0);
+  }
 
-  /* Alter direction of rotational acceleration randomly. */
-  if (! (random() % 120))
-    *dv = -*dv;
+  mi->polygon_count = 0;
 
-  /* Change acceleration very occasionally. */
-  if (! (random() % 200))
+  for (i = 0; i < bp->ngears; i++)
     {
-      if (*dv == 0)
-       *dv = 0.00001;
-      else if (random() & 1)
-       *dv *= 1.2;
-      else
-       *dv *= 0.8;
-    }
-}
-
+      gear *g = bp->gears[i];
 
-void
-init_gears(ModeInfo * mi)
-{
-       int         screen = MI_SCREEN(mi);
-
-       /*Colormap    cmap; */
-       /* Boolean     rgba, doublebuffer, cmap_installed; */
-       gearsstruct *gp;
-
-       if (gears == NULL) {
-               if ((gears = (gearsstruct *) calloc(MI_NUM_SCREENS(mi),
-                                             sizeof (gearsstruct))) == NULL)
-                       return;
-       }
-       gp = &gears[screen];
-
-       gp->window = MI_WINDOW(mi);
-
-    gp->rotx = frand(1.0) * RANDSIGN();
-    gp->roty = frand(1.0) * RANDSIGN();
-    gp->rotz = frand(1.0) * RANDSIGN();
-
-    /* bell curve from 0-1.5 degrees, avg 0.75 */
-    gp->dx = (frand(1) + frand(1) + frand(1)) / (360*2);
-    gp->dy = (frand(1) + frand(1) + frand(1)) / (360*2);
-    gp->dz = (frand(1) + frand(1) + frand(1)) / (360*2);
-
-    gp->d_max = gp->dx * 2;
-
-    gp->ddx = 0.00006 + frand(0.00003);
-    gp->ddy = 0.00006 + frand(0.00003);
-    gp->ddz = 0.00006 + frand(0.00003);
-
-    gp->ddx = 0.00001;
-    gp->ddy = 0.00001;
-    gp->ddz = 0.00001;
-
-       if ((gp->glx_context = init_GL(mi)) != NULL) {
-               reshape_gears(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
-               pinit(mi);
-       } else {
-               MI_CLEARWINDOW(mi);
-       }
-}
+      glPushMatrix();
 
-void
-draw_gears(ModeInfo * mi)
-{
-       gearsstruct *gp = &gears[MI_SCREEN(mi)];
-       Display    *display = MI_DISPLAY(mi);
-       Window      window = MI_WINDOW(mi);
-       int         angle_incr = MI_CYCLES(mi) ? MI_CYCLES(mi) : 2;
+      glTranslatef (g->x, g->y, g->z);
+      glRotatef (g->th, 0, 0, 1);
 
-    if (planetary)
-      angle_incr *= 3;
+      glCallList (g->dlist);
+      mi->polygon_count += g->polygons;
 
-       if (!gp->glx_context)
-               return;
+      glPopMatrix ();
+    }
 
-       glDrawBuffer(GL_BACK);
+  if (bp->planetary_p)
+    {
+      glCallList (bp->armature_dlist);
+      mi->polygon_count += bp->armature_polygons;
+    }
 
-       glXMakeCurrent(display, window, *(gp->glx_context));
-       draw(mi);
+  glPopMatrix ();
 
-       /* let's do something so we don't get bored */
-       gp->angle = (int) (gp->angle + angle_incr) % 360;
+  /* spin gears */
+  if (!bp->button_down_p)
+    for (i = 0; i < bp->ngears; i++)
+      {
+        gear *g = bp->gears[i];
+        double off = g->ratio * 5 * speed;
+        if (g->th > 0)
+          g->th += off;
+        else
+          g->th -= off;
+      }
 
-    rotate(&gp->rotx, &gp->dx, &gp->ddx, gp->d_max);
-    rotate(&gp->roty, &gp->dy, &gp->ddy, gp->d_max);
-    rotate(&gp->rotz, &gp->dz, &gp->ddz, gp->d_max);
+  if (mi->fps_p) do_fps (mi);
+  glFinish();
 
-    if (mi->fps_p) do_fps (mi);
-       glFinish();
-       glXSwapBuffers(display, window);
+  glXSwapBuffers(dpy, window);
 }
 
-void
-release_gears(ModeInfo * mi)
+ENTRYPOINT Bool
+gears_handle_event (ModeInfo *mi, XEvent *event)
 {
-       if (gears != NULL) {
-               int         screen;
-
-               for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
-                       gearsstruct *gp = &gears[screen];
-
-                       if (gp->glx_context) {
-                               /* Display lists MUST be freed while their glXContext is current. */
-                               glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));
-
-                               if (glIsList(gp->gear1))
-                                       glDeleteLists(gp->gear1, 1);
-                               if (glIsList(gp->gear2))
-                                       glDeleteLists(gp->gear2, 1);
-                               if (glIsList(gp->gear3))
-                                       glDeleteLists(gp->gear3, 1);
-                               if (glIsList(gp->gear_inner))
-                                       glDeleteLists(gp->gear_inner, 1);
-                               if (glIsList(gp->gear_outer))
-                                       glDeleteLists(gp->gear_outer, 1);
-
-                       }
-               }
-               (void) free((void *) gears);
-               gears = NULL;
-       }
-       FreeAllGL(mi);
-}
+  gears_configuration *bp = &bps[MI_SCREEN(mi)];
+
+  if (gltrackball_event_handler (event, bp->trackball,
+                                 MI_WIDTH (mi), MI_HEIGHT (mi),
+                                 &bp->button_down_p))
+    return True;
+  else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
+    {
+      init_gears (mi);
+      return True;
+    }
 
+  return False;
+}
 
-/*********************************************************/
+XSCREENSAVER_MODULE ("Gears", gears)
 
-#endif
+#endif /* USE_GL */