413fc1a94db290f295329a09ae61edfc045cc247
[xscreensaver] / hacks / glx / pinion.c
1 /* pinion, Copyright (c) 2004 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #include <X11/Intrinsic.h>
13
14 extern XtAppContext app;
15
16 #define PROGCLASS       "Pinion"
17 #define HACK_INIT       init_pinion
18 #define HACK_DRAW       draw_pinion
19 #define HACK_RESHAPE    reshape_pinion
20 #define HACK_HANDLE_EVENT pinion_handle_event
21 #define EVENT_MASK      PointerMotionMask
22 #define sws_opts        xlockmore_opts
23
24 #define DEF_SPIN_SPEED   "1.0"
25 #define DEF_SCROLL_SPEED "1.0"
26 #define DEF_GEAR_SIZE    "1.0"
27 #define DEF_MAX_RPM      "900"
28
29 #define DEFAULTS        "*delay:        15000              \n" \
30                         "*showFPS:      False              \n" \
31                         "*wireframe:    False              \n" \
32                         "*spinSpeed:  " DEF_SPIN_SPEED   " \n" \
33                         "*scrollSpeed:" DEF_SCROLL_SPEED " \n" \
34                         "*maxRPM:     " DEF_MAX_RPM      " \n" \
35                         "*gearSize:   " DEF_GEAR_SIZE    " \n" \
36                         "*titleFont:  -*-times-bold-r-normal-*-180-*\n" \
37                         "*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
38                         "*titleFont3: -*-times-bold-r-normal-*-80-*\n"  \
39
40 #undef countof
41 #define countof(x) (sizeof((x))/sizeof((*x)))
42
43 #undef BELLRAND
44 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
45
46 #include "xlockmore.h"
47 #include "gltrackball.h"
48 #include <ctype.h>
49
50 #ifdef USE_GL /* whole file */
51
52 #include <GL/glu.h>
53
54 typedef struct {
55   unsigned long id;          /* unique name */
56   double x, y, z;            /* position */
57   double r;                  /* radius of the gear, at middle of teeth */
58   double th;                 /* rotation (degrees) */
59
60   GLint nteeth;              /* how many teeth */
61   double tooth_w, tooth_h;   /* size of teeth */
62
63   double inner_r;            /* radius of the (larger) inside hole */
64   double inner_r2;           /* radius of the (smaller) inside hole, if any */
65   double inner_r3;           /* yet another */
66
67   double thickness;          /* height of the edge */
68   double thickness2;         /* height of the (smaller) inside disc if any */
69   double thickness3;         /* yet another */
70   int spokes;                /* how many spokes inside, if any */
71   int nubs;                  /* how many little nubbly bits, if any */
72   double spoke_thickness;    /* spoke versus hole */
73   GLfloat wobble;            /* factory defect! */
74   int motion_blur_p;         /* whether it's spinning too fast to draw */
75   int polygons;              /* how many polys in this gear */
76
77   double ratio;              /* gearing ratio with previous gears */
78   double rpm;                /* approximate revolutions per minute */
79
80   Bool base_p;               /* whether this gear begins a new train */
81   int coax_p;                /* whether this is one of a pair of bound gears.
82                                 1 for first, 2 for second. */
83   double coax_thickness;     /* thickness of the other gear in the pair */
84   GLfloat color[4];
85   GLfloat color2[4];
86
87   GLuint dlist;
88 } gear;
89
90
91 typedef struct {
92   GLXContext *glx_context;
93   GLfloat vp_left, vp_right, vp_top, vp_bottom;    /* default visible area */
94   GLfloat vp_width, vp_height;
95   GLfloat render_left, render_right;  /* area in which gears are displayed */
96   GLfloat layout_left, layout_right;  /* layout region, on the right side  */
97
98   int ngears;
99   int gears_size;
100   gear **gears;
101
102   trackball_state *trackball;
103   Bool button_down_p;
104   unsigned long mouse_gear_id;
105
106   XFontStruct *xfont1, *xfont2, *xfont3;
107   GLuint font1_dlist, font2_dlist, font3_dlist;
108   GLuint title_list;
109
110 } pinion_configuration;
111
112
113 static pinion_configuration *pps = NULL;
114 static GLfloat spin_speed, scroll_speed, max_rpm, gear_size;
115 static GLfloat plane_displacement = 0.1;  /* distance between coaxial gears */
116
117 static Bool verbose_p = False;            /* print progress on stderr */
118 static Bool debug_placement_p = False;    /* extreme verbosity on stderr */
119 static Bool debug_p = False;              /* render as flat schematic */
120 static Bool debug_one_gear_p = False;     /* draw one big stationary gear */
121 static Bool wire_all_p = False;           /* in wireframe, do not abbreviate */
122
123 static int debug_size_failures;           /* for debugging messages */
124 static int debug_position_failures;
125 static unsigned long current_length;      /* gear count in current train */
126 static unsigned long current_blur_length; /* how long have we been blurring? */
127
128
129 static XrmOptionDescRec opts[] = {
130   { "-spin",   ".spinSpeed",   XrmoptionSepArg, 0 },
131   { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
132   { "-size",   ".gearSize",    XrmoptionSepArg, 0 },
133   { "-max-rpm",".maxRPM",      XrmoptionSepArg, 0 },
134   { "-debug",  ".debug",       XrmoptionNoArg, "True" },
135   { "-verbose",".verbose",     XrmoptionNoArg, "True" },
136 };
137
138 static argtype vars[] = {
139   {&spin_speed,   "spinSpeed",   "SpinSpeed",   DEF_SPIN_SPEED,   t_Float},
140   {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
141   {&gear_size,    "gearSize",    "GearSize",    DEF_GEAR_SIZE,    t_Float},
142   {&max_rpm,      "maxRPM",      "MaxRPM",      DEF_MAX_RPM,      t_Float},
143   {&debug_p,      "debug",       "Debug",       "False",          t_Bool},
144   {&verbose_p,    "verbose",     "Verbose",     "False",          t_Bool},
145 };
146
147 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
148
149 \f
150 /* Computing normal vectors
151  */
152
153 typedef struct {
154   double x,y,z;
155 } XYZ;
156
157 /* Calculate the unit normal at p given two other points p1,p2 on the
158    surface. The normal points in the direction of p1 crossproduct p2
159  */
160 static XYZ
161 calc_normal (XYZ p, XYZ p1, XYZ p2)
162 {
163   XYZ n, pa, pb;
164   pa.x = p1.x - p.x;
165   pa.y = p1.y - p.y;
166   pa.z = p1.z - p.z;
167   pb.x = p2.x - p.x;
168   pb.y = p2.y - p.y;
169   pb.z = p2.z - p.z;
170   n.x = pa.y * pb.z - pa.z * pb.y;
171   n.y = pa.z * pb.x - pa.x * pb.z;
172   n.z = pa.x * pb.y - pa.y * pb.x;
173   return (n);
174 }
175
176 static void
177 do_normal(GLfloat x1, GLfloat y1, GLfloat z1,
178           GLfloat x2, GLfloat y2, GLfloat z2,
179           GLfloat x3, GLfloat y3, GLfloat z3)
180 {
181   XYZ p1, p2, p3, p;
182   p1.x = x1; p1.y = y1; p1.z = z1;
183   p2.x = x2; p2.y = y2; p2.z = z2;
184   p3.x = x3; p3.y = y3; p3.z = z3;
185   p = calc_normal (p1, p2, p3);
186   glNormal3f (p.x, p.y, p.z);
187 }
188
189 \f
190 /* Font stuff
191  */
192
193 static void
194 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
195 {
196   const char *font = get_string_resource (res, "Font");
197   XFontStruct *f;
198   Font id;
199   int first, last;
200
201   if (!font) font = "-*-times-bold-r-normal-*-180-*";
202
203   f = XLoadQueryFont(mi->dpy, font);
204   if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
205
206   id = f->fid;
207   first = f->min_char_or_byte2;
208   last = f->max_char_or_byte2;
209   
210   clear_gl_error ();
211   *dlistP = glGenLists ((GLuint) last+1);
212   check_gl_error ("glGenLists");
213   glXUseXFont(id, first, last-first+1, *dlistP + first);
214   check_gl_error ("glXUseXFont");
215
216   *fontP = f;
217 }
218
219
220 static void
221 load_fonts (ModeInfo *mi)
222 {
223   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
224   load_font (mi, "titleFont",  &pp->xfont1, &pp->font1_dlist);
225   load_font (mi, "titleFont2", &pp->xfont2, &pp->font2_dlist);
226   load_font (mi, "titleFont3", &pp->xfont3, &pp->font3_dlist);
227 }
228
229
230 static int
231 string_width (XFontStruct *f, const char *c)
232 {
233   int w = 0;
234   while (*c)
235     {
236       int cc = *((unsigned char *) c);
237       w += (f->per_char
238             ? f->per_char[cc-f->min_char_or_byte2].rbearing
239             : f->min_bounds.rbearing);
240       c++;
241     }
242   return w;
243 }
244
245 static void
246 print_title_string (ModeInfo *mi, const char *string,
247                     GLfloat x, GLfloat y,
248                     XFontStruct *font, int font_dlist)
249 {
250   GLfloat line_height = font->ascent + font->descent;
251   GLfloat sub_shift = (line_height * 0.3);
252   int cw = string_width (font, "m");
253   int tabs = cw * 7;
254
255   y -= line_height;
256
257   glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
258                 GL_ENABLE_BIT);     /* for various glDisable calls */
259   glDisable (GL_LIGHTING);
260   glDisable (GL_DEPTH_TEST);
261   {
262     glMatrixMode(GL_PROJECTION);
263     glPushMatrix();
264     {
265       glLoadIdentity();
266
267       glMatrixMode(GL_MODELVIEW);
268       glPushMatrix();
269       {
270         int i;
271         int x2 = x;
272         Bool sub_p = False;
273         glLoadIdentity();
274
275         gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
276
277         glColor3f (0.8, 0.8, 0);
278
279         glRasterPos2f (x, y);
280         for (i = 0; i < strlen(string); i++)
281           {
282             char c = string[i];
283             if (c == '\n')
284               {
285                 glRasterPos2f (x, (y -= line_height));
286                 x2 = x;
287               }
288             else if (c == '\t')
289               {
290                 x2 -= x;
291                 x2 = ((x2 + tabs) / tabs) * tabs;  /* tab to tab stop */
292                 x2 += x;
293                 glRasterPos2f (x2, y);
294               }
295             else if (c == '[' && (isdigit (string[i+1])))
296               {
297                 sub_p = True;
298                 glRasterPos2f (x2, (y -= sub_shift));
299               }
300             else if (c == ']' && sub_p)
301               {
302                 sub_p = False;
303                 glRasterPos2f (x2, (y += sub_shift));
304               }
305             else
306               {
307                 glCallList (font_dlist + (int)(c));
308                 x2 += (font->per_char
309                        ? font->per_char[c - font->min_char_or_byte2].width
310                        : font->min_bounds.width);
311               }
312           }
313       }
314       glPopMatrix();
315     }
316     glMatrixMode(GL_PROJECTION);
317     glPopMatrix();
318   }
319   glPopAttrib();
320
321   glMatrixMode(GL_MODELVIEW);
322 }
323
324 static void rpm_string (double rpm, char *s);
325
326 static void
327 new_label (ModeInfo *mi)
328 {
329   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
330   char label[1024];
331   int i;
332   gear *g = 0;
333
334   if (pp->mouse_gear_id)
335     for (i = 0; i < pp->ngears; i++)
336       if (pp->gears[i]->id == pp->mouse_gear_id)
337         {
338           g = pp->gears[i];
339           break;
340         }
341
342   if (!g)
343     *label = 0;
344   else
345     {
346       sprintf (label, "%d teeth\n", g->nteeth);
347       rpm_string (g->rpm, label + strlen(label));
348     }
349
350   glNewList (pp->title_list, GL_COMPILE);
351   if (*label)
352     {
353       XFontStruct *f;
354       GLuint fl;
355       if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
356         f = pp->xfont1, fl = pp->font1_dlist;                  /* big font */
357       else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
358         f = pp->xfont2, fl = pp->font2_dlist;                  /* small font */
359       else
360         f = pp->xfont3, fl = pp->font3_dlist;                  /* tiny font */
361
362       print_title_string (mi, label,
363                           10, mi->xgwa.height - 10,
364                           f, fl);
365     }
366   glEndList ();
367 }
368
369 \f
370 /* Some utilities
371  */
372
373
374 /* Find the gear in the scene that is farthest to the right or left.
375  */
376 static gear *
377 farthest_gear (ModeInfo *mi, Bool left_p)
378 {
379   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
380   int i;
381   gear *rg = 0;
382   double x = (left_p ? 999999 : -999999);
383   for (i = 0; i < pp->ngears; i++)
384     {
385       gear *g = pp->gears[i];
386       double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
387       if (left_p ? (x > gx) : (x < gx))
388         {
389           rg = g;
390           x = gx;
391         }
392     }
393   return rg;
394 }
395
396
397 /* Compute the revolutions per minute of a gear.
398  */
399 static void
400 compute_rpm (ModeInfo *mi, gear *g)
401 {
402   double fps, rpf, rps;
403   fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
404
405   if (fps > 150) fps = 150;  /* let's be reasonable... */
406   if (fps < 10)  fps = 10;
407
408   rpf    = (g->ratio * spin_speed) / 360.0;   /* rotations per frame */
409   rps    = rpf * fps;                         /* rotations per second */
410   g->rpm = rps * 60;
411 }
412
413 /* Prints the RPM into a string, doing fancy float formatting.
414  */
415 static void
416 rpm_string (double rpm, char *s)
417 {
418   char buf[30];
419   int L;
420   if (rpm >= 0.1)          sprintf (buf, "%.2f", rpm);
421   else if (rpm >= 0.001)   sprintf (buf, "%.4f", rpm);
422   else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
423   else                     sprintf (buf, "%.16f",rpm);
424
425   L = strlen(buf);
426   while (buf[L-1] == '0') buf[--L] = 0;
427   if (buf[L-1] == '.') buf[--L] = 0;
428   strcpy (s, buf);
429   strcat (s, " RPM");
430 }
431
432
433
434 /* Which of the gear's inside rings is the biggest? 
435  */
436 static int
437 biggest_ring (gear *g, double *posP, double *sizeP, double *heightP)
438 {
439   double r0 = (g->r - g->tooth_h/2);
440   double r1 = g->inner_r;
441   double r2 = g->inner_r2;
442   double r3 = g->inner_r3;
443   double w1 = (r1 ? r0 - r1 : r0);
444   double w2 = (r2 ? r1 - r2 : 0);
445   double w3 = (r3 ? r2 - r3 : 0);
446   double h1 = g->thickness;
447   double h2 = g->thickness2;
448   double h3 = g->thickness3;
449
450   if (g->spokes) w2 = 0;
451
452   if (w1 > w2 && w1 > w3)
453     {
454       if (posP)    *posP = (r0+r1)/2;
455       if (sizeP)   *sizeP = w1;
456       if (heightP) *heightP = h1;
457       return 0;
458     }
459   else if (w2 > w1 && w2 > w3)
460     {
461       if (posP)  *posP = (r1+r2)/2;
462       if (sizeP) *sizeP = w2;
463       if (heightP) *heightP = h2;
464       return 1;
465     }
466   else
467     {
468       if (posP)  *posP = (r2+r3)/2;
469       if (sizeP) *sizeP = w3;
470       if (heightP) *heightP = h3;
471       return 1;
472     }
473 }
474
475 \f
476 /* Layout and stuff.
477  */
478
479
480 /* Create and return a new gear sized for placement next to or on top of
481    the given parent gear (if any.)  Returns 0 if out of memory.
482  */
483 static gear *
484 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
485 {
486   /* pinion_configuration *pp = &pps[MI_SCREEN(mi)]; */
487   gear *g = (gear *) calloc (1, sizeof (*g));
488   int loop_count = 0;
489   static unsigned long id = 0;
490
491   if (!g) return 0;
492   if (coaxial_p && !parent) abort();
493   g->id = ++id;
494
495   while (1)
496     {
497       loop_count++;
498       if (loop_count > 1000)
499         /* The only time we loop in here is when making a coaxial gear, and
500            trying to pick a radius that is either significantly larger or
501            smaller than its parent.  That shouldn't be hard, so something
502            must be really wrong if we can't do that in only a few tries.
503          */
504         abort();
505
506       /* Pick the size of the teeth.
507        */
508       if (parent && !coaxial_p) /* adjascent gears need matching teeth */
509         {
510           g->tooth_w = parent->tooth_w;
511           g->tooth_h = parent->tooth_h;
512           g->thickness  = parent->thickness;
513           g->thickness2 = parent->thickness2;
514           g->thickness3 = parent->thickness3;
515         }
516       else                 /* gears that begin trains get any size they want */
517         {
518           double scale = (1.0 + BELLRAND(4.0)) * gear_size;
519           g->tooth_w = 0.007 * scale;
520           g->tooth_h = 0.005 * scale;
521           g->thickness  = g->tooth_h * (0.1 + BELLRAND(1.5));
522           g->thickness2 = g->thickness / 4;
523           g->thickness3 = g->thickness;
524         }
525
526       /* Pick the number of teeth, and thus, the radius.
527        */
528       {
529         double c;
530
531       AGAIN:
532         g->nteeth = 3 + (random() % 97);    /* from 3 to 100 teeth */
533
534         if (g->nteeth < 7 && (random() % 5) != 0)
535           goto AGAIN;   /* Let's make very small tooth-counts more rare */
536
537         c = g->nteeth * g->tooth_w * 2;     /* circumference = teeth + gaps */
538         g->r = c / (M_PI * 2);              /* c = 2 pi r  */
539       }
540
541
542       /* Are we done now?
543        */
544       if (! coaxial_p) break;   /* yes */
545       if (g->nteeth == parent->nteeth) continue; /* ugly */
546       if (g->r  < parent->r * 0.6) break;  /* g much smaller than parent */
547       if (parent->r < g->r  * 0.6) break;  /* g much larger than parent  */
548     }
549
550   /* Colorize
551    */
552   g->color[0] = 0.5 + frand(0.5);
553   g->color[1] = 0.5 + frand(0.5);
554   g->color[2] = 0.5 + frand(0.5);
555   g->color[3] = 1.0;
556
557   g->color2[0] = g->color[0] * 0.85;
558   g->color2[1] = g->color[1] * 0.85;
559   g->color2[2] = g->color[2] * 0.85;
560   g->color2[3] = g->color[3];
561
562
563   /* Decide on shape of gear interior:
564      - just a ring with teeth;
565      - that, plus a thinner in-set "plate" in the middle;
566      - that, plus a thin raised "lip" on the inner plate;
567      - or, a wide lip (really, a thicker third inner plate.)
568    */
569   if ((random() % 10) == 0)
570     {
571       /* inner_r can go all the way in; there's no inset disc. */
572       g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
573       g->inner_r2 = 0;
574       g->inner_r3 = 0;
575     }
576   else
577     {
578       /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
579       g->inner_r  = (g->r * 0.5)  + frand((g->r - g->tooth_h) * 0.4);
580       g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
581       g->inner_r3 = 0;
582
583       if (g->inner_r2 > (g->r * 0.2))
584         {
585           int nn = (random() % 10);
586           if (nn <= 2)
587             g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
588           else if (nn <= 7 && g->inner_r2 >= 0.1)
589             g->inner_r3 = g->inner_r2 - 0.01;
590         }
591     }
592
593   /* Coaxial gears need to have the same innermost hole size (for the axle.)
594      Use whichever of the two is smaller.  (Modifies parent.)
595    */
596   if (coaxial_p)
597     {
598       double hole1 = (g->inner_r3 ? g->inner_r3 :
599                       g->inner_r2 ? g->inner_r2 :
600                       g->inner_r);
601       double hole2 = (parent->inner_r3 ? parent->inner_r3 :
602                       parent->inner_r2 ? parent->inner_r2 :
603                       parent->inner_r);
604       double hole = (hole1 < hole2 ? hole1 : hole2);
605       if (hole <= 0) abort();
606
607       if      (g->inner_r3) g->inner_r3 = hole;
608       else if (g->inner_r2) g->inner_r2 = hole;
609       else                  g->inner_r  = hole;
610
611       if      (parent->inner_r3) parent->inner_r3 = hole;
612       else if (parent->inner_r2) parent->inner_r2 = hole;
613       else                       parent->inner_r  = hole;
614     }
615
616   /* If we have three discs, sometimes make the middle disc be spokes.
617    */
618   if (g->inner_r3 && ((random() % 5) == 0))
619     {
620       g->spokes = 2 + BELLRAND (5);
621       g->spoke_thickness = 1 + frand(7.0);
622       if (g->spokes == 2 && g->spoke_thickness < 2)
623         g->spoke_thickness += 1;
624     }
625
626   /* Sometimes add little nubbly bits, if there is room.
627    */
628   if (g->nteeth > 5)
629     {
630       double size = 0;
631       biggest_ring (g, 0, &size, 0);
632       if (size > g->r * 0.2 && (random() % 5) == 0)
633         {
634           g->nubs = 1 + (random() % 16);
635           if (g->nubs > 8) g->nubs = 1;
636         }
637     }
638
639   if (g->inner_r3 > g->inner_r2) abort();
640   if (g->inner_r2 > g->inner_r) abort();
641   if (g->inner_r  > g->r) abort();
642
643   g->base_p = !parent;
644
645   return g;
646 }
647
648
649 /* Given a newly-created gear, place it next to its parent in the scene,
650    with its teeth meshed and the proper velocity.  Returns False if it
651    didn't work.  (Call this a bunch of times until either it works, or
652    you decide it's probably not going to.)
653  */
654 static Bool
655 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
656 {
657   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
658
659   /* If this gear takes up more than 1/3rd of the screen, it's no good.
660    */
661   if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
662       ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
663     {
664       if (verbose_p && debug_placement_p && 0)
665         fprintf (stderr,
666                  "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
667                  progname,
668                  (g->r + g->tooth_h), gear_size,
669                  pp->vp_width, pp->vp_height);
670       debug_size_failures++;
671       return False;
672     }
673
674   /* Compute this gear's velocity.
675    */
676   if (! parent)
677     {
678       g->ratio = 0.8 + BELLRAND(0.4);  /* 0.8-1.2 = 8-12rpm @ 60fps */
679       g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
680     }
681   else if (coaxial_p)
682     {
683       g->ratio = parent->ratio; /* bound gears have the same ratio */
684       g->th = parent->th;
685       g->rpm = parent->rpm;
686       g->wobble = parent->wobble;
687     }
688   else
689     {
690       /* Gearing ratio is the ratio of the number of teeth to previous gear
691          (which is also the ratio of the circumferences.)
692        */
693       g->ratio = (double) parent->nteeth / (double) g->nteeth;
694
695       /* Set our initial rotation to match that of the previous gear,
696          multiplied by the gearing ratio.  (This is finessed later,
697          once we know the exact position of the gear relative to its
698          parent.)
699       */
700       g->th = -(parent->th * g->ratio);
701
702       if (g->nteeth & 1)    /* rotate 1/2 tooth-size if odd number of teeth */
703         {
704           double off = (180.0 / g->nteeth);
705           if (g->th > 0)
706             g->th += off;
707           else
708             g->th -= off;
709         }
710
711       /* ratios are cumulative for all gears in the train. */
712       g->ratio *= parent->ratio;
713     }
714
715
716   /* Place the gear relative to the parent.
717    */
718
719   if (! parent)
720     {
721       gear *rg = farthest_gear (mi, False);
722       double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
723       if (right < pp->layout_left) /* place off screen */
724         right = pp->layout_left;
725
726       g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
727       g->y = 0;
728       g->z = 0;
729
730       if (debug_one_gear_p)
731         g->x = 0;
732     }
733   else if (coaxial_p)
734     {
735       double off = plane_displacement;
736
737       g->x = parent->x;
738       g->y = parent->y;
739       g->z = parent->z + (g->r > parent->r      /* small gear on top */
740                           ? -off : off);
741
742       if (parent->r > g->r)     /* mark which is top and which is bottom */
743         {
744           parent->coax_p = 1;
745           g->coax_p      = 2;
746           parent->wobble = 0;   /* looks bad when axle moves */
747         }
748       else
749         {
750           parent->coax_p = 2;
751           g->coax_p      = 1;
752           g->wobble      = 0;
753         }
754
755       g->coax_thickness      = parent->thickness;
756       parent->coax_thickness = g->thickness;
757
758       /* Don't let the train get too close to or far from the screen.
759          If we're getting too close, give up on this gear.
760          (But getting very far away is fine.)
761        */
762       if (g->z >=  off * 4 ||
763           g->z <= -off * 4)
764         {
765           if (verbose_p && debug_placement_p)
766             fprintf (stderr, "%s: placement: bad depth: %.2f\n",
767                      progname, g->z);
768           debug_position_failures++;
769           return False;
770         }
771     }
772   else                          /* position it somewhere next to the parent. */
773     {
774       double r_off = parent->r + g->r;
775       int angle;
776
777       if ((random() % 3) != 0)
778         angle = (random() % 240) - 120;   /* mostly -120 to +120 degrees */
779       else
780         angle = (random() % 360) - 180;   /* sometimes -180 to +180 degrees */
781
782       g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
783       g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
784       g->z = parent->z;
785
786       /* If the angle we picked would have positioned this gear
787          more than halfway off screen, that's no good. */
788       if (g->y > pp->vp_top ||
789           g->y < pp->vp_bottom)
790         {
791           if (verbose_p && debug_placement_p)
792             fprintf (stderr, "%s: placement: out of bounds: %s\n",
793                      progname, (g->y > pp->vp_top ? "top" : "bottom"));
794           debug_position_failures++;
795           return False;
796         }
797
798       /* avoid accidentally changing sign of "th" in the math below. */
799       g->th += (g->th > 0 ? 360 : -360);
800
801       /* Adjust the rotation of the gear so that its teeth line up with its
802          parent, based on the position of the gear and the current rotation
803          of the parent.
804        */
805       {
806         double p_c = 2 * M_PI * parent->r;  /* circumference of parent */
807         double g_c = 2 * M_PI * g->r;       /* circumference of g  */
808
809         double p_t = p_c * (angle/360.0);   /* distance travelled along
810                                                circumference of parent when
811                                                moving "angle" degrees along
812                                                parent. */
813         double g_rat = p_t / g_c;           /* if travelling that distance
814                                                along circumference of g,
815                                                ratio of g's circumference
816                                                travelled. */
817         double g_th = 360.0 * g_rat;        /* that ratio in degrees */
818
819         g->th += angle + g_th;
820       }
821     }
822
823   if (debug_one_gear_p)
824     {
825       compute_rpm (mi, g);
826       return True;
827     }
828
829   /* If the position we picked for this gear would cause it to already
830      be visible on the screen, give up.  This can happen when the train
831      is growing backwards, and we don't want to see gears flash into
832      existence.
833    */
834   if (g->x - g->r - g->tooth_h < pp->render_right)
835     {
836       if (verbose_p && debug_placement_p)
837         fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
838       debug_position_failures++;
839       return False;
840     }
841
842   /* If the position we picked for this gear causes it to overlap
843      with any earlier gear in the train, give up.
844    */
845   {
846     int i;
847
848     for (i = pp->ngears-1; i >= 0; i--)
849       {
850         gear *og = pp->gears[i];
851
852         if (og == g) continue;
853         if (og == parent) continue;
854         if (g->z != og->z) continue;    /* Ignore unless on same layer */
855
856         /* Collision detection without sqrt:
857              d = sqrt(a^2 + b^2)   d^2 = a^2 + b^2
858              d < r1 + r2           d^2 < (r1 + r2)^2
859          */
860         if (((g->x - og->x) * (g->x - og->x) +
861              (g->y - og->y) * (g->y - og->y)) <
862             ((g->r + g->tooth_h + og->r + og->tooth_h) *
863              (g->r + g->tooth_h + og->r + og->tooth_h)))
864           {
865             if (verbose_p && debug_placement_p)
866               fprintf (stderr, "%s: placement: collision with %lu\n",
867                        progname, og->id);
868             debug_position_failures++;
869             return False;
870           }
871       }
872   }
873
874   compute_rpm (mi, g);
875
876
877   /* Make deeper gears be darker.
878    */
879   {
880     double depth = g->z / plane_displacement;
881     double brightness = 1 + (depth / 6);
882     double limit = 0.4;
883     if (brightness < limit)   brightness = limit;
884     if (brightness > 1/limit) brightness = 1/limit;
885     g->color[0]  *= brightness;
886     g->color[1]  *= brightness;
887     g->color[2]  *= brightness;
888     g->color2[0] *= brightness;
889     g->color2[1] *= brightness;
890     g->color2[2] *= brightness;
891   }
892
893   /* If a single frame of animation would cause the gear to rotate by
894      more than 1/2 the size of a single tooth, then it won't look right:
895      the gear will appear to be turning at some lower harmonic of its
896      actual speed.
897    */
898   {
899     double ratio = g->ratio * spin_speed;
900     double blur_limit = 180.0 / g->nteeth;
901
902     if (ratio > blur_limit)
903       g->motion_blur_p = 1;
904
905     if (!coaxial_p)
906       {
907         /* ride until the wheels fall off... */
908         if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
909         if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
910         if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
911         if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
912         if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
913         if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
914       }
915   }
916
917   return True;
918 }
919
920 static void
921 free_gear (gear *g)
922 {
923   if (g->dlist)
924     glDeleteLists (g->dlist, 1);
925   free (g);
926 }
927
928
929 /* Make a new gear, place it next to its parent in the scene,
930    with its teeth meshed and the proper velocity.  Returns the gear;
931    or 0 if it didn't work.  (Call this a bunch of times until either
932    it works, or you decide it's probably not going to.)
933  */
934 static gear *
935 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
936 {
937   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
938   int loop_count = 0;
939   gear *g = 0;
940
941   while (1)
942     {
943       loop_count++;
944       if (loop_count >= 100)
945         {
946           if (g)
947             free_gear (g);
948           g = 0;
949           break;
950         }
951
952       g = new_gear (mi, parent, coaxial_p);
953       if (!g) return 0;  /* out of memory? */
954
955       if (place_gear (mi, g, parent, coaxial_p))
956         break;
957     }
958
959   if (! g) return 0;
960
961   /* We got a gear, and it is properly positioned.
962      Insert it in the scene.
963    */
964   if (pp->ngears + 2 >= pp->gears_size)
965     {
966       pp->gears_size += 100;
967       pp->gears = (gear **) realloc (pp->gears,
968                                      pp->gears_size * sizeof (*pp->gears));
969       if (! pp->gears)
970         {
971           fprintf (stderr, "%s: out of memory (%d gears)\n",
972                    progname, pp->gears_size);
973         }
974     }
975
976   pp->gears[pp->ngears++] = g;
977   return g;
978 }
979
980
981 static void delete_gear (ModeInfo *mi, gear *g);
982
983 static void
984 push_gear (ModeInfo *mi)
985 {
986   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
987   gear *g;
988   gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
989
990   Bool tried_coaxial_p = False;
991   Bool coaxial_p = False;
992   Bool last_ditch_coax_p = False;
993   int loop_count = 0;
994
995   debug_size_failures = 0;
996   debug_position_failures = 0;
997
998  AGAIN:
999   loop_count++;
1000   if (loop_count > 100) abort();  /* we're doomed! */
1001   
1002   g = 0;
1003
1004   /* If the gears are turning at LUDICROUS SPEED, unhook the train to
1005      reset things to a sane velocity.
1006
1007      10,000 RPM at 30 FPS = 5.5 rotations per frame.
1008       1,000 RPM at 30 FPS = 0.5 rotations per frame.
1009         600 RPM at 30 FPS =  3 frames per rotation.
1010         200 RPM at 30 FPS =  9 frames per rotation.
1011         100 RPM at 30 FPS = 18 frames per rotation.
1012          50 RPM at 30 FPS = 36 frames per rotation.
1013          10 RPM at 30 FPS =  3 sec per rotation.
1014           1 RPM at 30 FPS = 30 sec per rotation.
1015          .5 RPM at 30 FPS =  1 min per rotation.
1016          .1 RPM at 30 FPS =  5 min per rotation.
1017    */
1018   if (parent && parent->rpm > max_rpm)
1019     {
1020       if (verbose_p)
1021         {
1022           char buf[100];
1023           rpm_string (parent->rpm, buf);
1024           fprintf (stderr, "%s: ludicrous speed!  %s\n\n", progname, buf);
1025         }
1026       parent = 0;
1027     }
1028
1029   /* If the last N gears we've placed have all been motion-blurred, then
1030      it's a safe guess that we've wandered off into the woods and aren't
1031      coming back.  Bail on this train.
1032    */
1033   if (current_blur_length >= 10)
1034     {
1035       if (verbose_p)
1036         fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
1037       parent = 0;
1038     }
1039
1040
1041
1042   /* Sometimes, try to make a coaxial gear.
1043    */
1044   if (parent && !parent->coax_p && (random() % 40) == 0)
1045     {
1046       tried_coaxial_p = True;
1047       coaxial_p = True;
1048       g = place_new_gear (mi, parent, coaxial_p);
1049     }
1050
1051   /* Try to make a regular gear.
1052    */
1053   if (!g)
1054     {
1055       coaxial_p = False;
1056       g = place_new_gear (mi, parent, coaxial_p);
1057     }
1058
1059   /* If we couldn't make a regular gear, then try to make a coxial gear
1060      (unless we already tried that.)
1061    */
1062   if (!g && !tried_coaxial_p && parent && !parent->coax_p)
1063     {
1064       tried_coaxial_p = True;
1065       coaxial_p = True;
1066       g = place_new_gear (mi, parent, coaxial_p);
1067       if (g)
1068         last_ditch_coax_p = True;
1069     }
1070
1071   /* If we couldn't do that either, then the train has hit a dead end:
1072      start a new train.
1073    */
1074   if (!g)
1075     {
1076       coaxial_p = False;
1077       if (verbose_p)
1078         fprintf (stderr, "%s: dead end!\n\n", progname);
1079       parent = 0;
1080       g = place_new_gear (mi, parent, coaxial_p);
1081     }
1082
1083   if (! g)
1084     {
1085       /* Unable to make/place any gears at all!
1086          This can happen if we've backed ourself into a corner very near
1087          the top-right or bottom-right corner of the growth zone.
1088          It's time to add a gear, but there's no room to add one!
1089          In that case, let's just wipe all the gears that are in the
1090          growth zone and try again.
1091        */
1092       int i;
1093
1094       if (verbose_p && debug_placement_p)
1095         fprintf (stderr,
1096                  "%s: placement: resetting growth zone!  "
1097                  "failed: %d size, %d pos\n",
1098                  progname,
1099                  debug_size_failures, debug_position_failures);
1100       for (i = pp->ngears-1; i >= 0; i--)
1101         {
1102           gear *g = pp->gears[i];
1103           if (g->x - g->r - g->tooth_h < pp->render_left)
1104             delete_gear (mi, g);
1105         }
1106       goto AGAIN;
1107     }
1108
1109   if (g->coax_p)
1110     {
1111       if (g->x != parent->x) abort();
1112       if (g->y != parent->y) abort();
1113       if (g->z == parent->z) abort();
1114       if (g->r == parent->r) abort();
1115       if (g->th != parent->th) abort();
1116       if (g->ratio != parent->ratio) abort();
1117       if (g->rpm != parent->rpm) abort();
1118     }
1119
1120   if (verbose_p)
1121     {
1122       fprintf (stderr, "%s: %5lu ", progname, g->id);
1123
1124       fputc ((g->motion_blur_p ? '*' : ' '), stderr);
1125       fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
1126               g->coax_p ? '1' : ' '),
1127              stderr);
1128
1129       fprintf (stderr, " %2d%%",
1130                (int) (g->r * 2 * 100 / pp->vp_height));
1131       fprintf (stderr, "  %2d teeth", g->nteeth);
1132       fprintf (stderr, " %3.0f rpm;", g->rpm);
1133
1134       {
1135         char buf1[50], buf2[50], buf3[100];
1136         *buf1 = 0; *buf2 = 0; *buf3 = 0;
1137         if (debug_size_failures)
1138           sprintf (buf1, "%3d sz", debug_size_failures);
1139         if (debug_position_failures)
1140           sprintf (buf2, "%2d pos", debug_position_failures);
1141         if (*buf1 || *buf2)
1142           sprintf (buf3, " tries: %-7s%s", buf1, buf2);
1143         fprintf (stderr, "%-21s", buf3);
1144       }
1145
1146       if (g->base_p) fprintf (stderr, " RESET %lu", current_length);
1147       fprintf (stderr, "\n");
1148     }
1149
1150   if (g->base_p)
1151     current_length = 1;
1152   else
1153     current_length++;
1154
1155   if (g->motion_blur_p)
1156     current_blur_length++;
1157   else
1158     current_blur_length = 0;
1159 }
1160
1161
1162
1163 /* Remove the given gear from the scene and free it.
1164  */
1165 static void
1166 delete_gear (ModeInfo *mi, gear *g)
1167 {
1168   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1169   int i;
1170
1171   for (i = 0; i < pp->ngears; i++)      /* find this gear in the list */
1172     if (pp->gears[i] == g) break;
1173   if (pp->gears[i] != g) abort();
1174
1175   for (; i < pp->ngears-1; i++)         /* pull later entries forward */
1176     pp->gears[i] = pp->gears[i+1];
1177   pp->gears[i] = 0;
1178   pp->ngears--;
1179   free_gear (g);
1180 }
1181
1182
1183 /* Update the position of each gear in the scene.
1184  */
1185 static void
1186 scroll_gears (ModeInfo *mi)
1187 {
1188   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1189   int i;
1190
1191   for (i = 0; i < pp->ngears; i++)
1192     pp->gears[i]->x -= (scroll_speed * 0.002);
1193
1194   /* if the right edge of any gear is off screen to the left, delete it.
1195    */
1196   for (i = pp->ngears-1; i >= 0; i--)
1197     {
1198       gear *g = pp->gears[i];
1199       if (g->x + g->r + g->tooth_h < pp->render_left)
1200         delete_gear (mi, g);
1201     }
1202
1203   /* if the right edge of the last-added gear is left of the right edge
1204      of the layout area, add another gear.
1205    */
1206   i = 0;
1207   while (1)
1208     {
1209       gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
1210       if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
1211         push_gear (mi);
1212       else
1213         break;
1214       i++;
1215     }
1216
1217   /*
1218   if (i > 1 && verbose_p)
1219     fprintf (stderr, "%s: pushed %d gears\n", progname, i);
1220    */
1221 }
1222
1223
1224 /* Update the rotation of each gear in the scene.
1225  */
1226 static void
1227 spin_gears (ModeInfo *mi)
1228 {
1229   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1230   int i;
1231
1232   for (i = 0; i < pp->ngears; i++)
1233     {
1234       gear *g = pp->gears[i];
1235       double off = (g->ratio * spin_speed);
1236
1237       if (g->th > 0)
1238         g->th += off;
1239       else
1240         g->th -= off;
1241     }
1242 }
1243
1244
1245 /* Run the animation fast (without displaying anything) until the first
1246    gear is just about to come on screen.  This is to avoid a big delay
1247    with a blank screen when -scroll is low.
1248  */
1249 static void
1250 ffwd (ModeInfo *mi)
1251 {
1252   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1253   while (1)
1254     {
1255       gear *g = farthest_gear (mi, True);
1256       if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1257         break;
1258       scroll_gears (mi);
1259     }
1260 }
1261
1262
1263 \f
1264 /* Rendering the 3D objects into the scene.
1265  */
1266
1267
1268 /* Draws an uncapped tube of the given radius extending from top to bottom,
1269    with faces pointing either in or out.
1270  */
1271 static int
1272 draw_ring (ModeInfo *mi, int segments,
1273            GLfloat r, GLfloat top, GLfloat bottom, Bool in_p)
1274 {
1275   int i;
1276   int polys = 0;
1277   Bool wire_p = MI_IS_WIREFRAME(mi);
1278   GLfloat width = M_PI * 2 / segments;
1279
1280   if (top != bottom)
1281     {
1282       glFrontFace (in_p ? GL_CCW : GL_CW);
1283       glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1284       for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1285         {
1286           GLfloat th = i * width;
1287           GLfloat cth = cos(th);
1288           GLfloat sth = sin(th);
1289           if (in_p)
1290             glNormal3f (-cth, -sth, 0);
1291           else
1292             glNormal3f (cth, sth, 0);
1293           glVertex3f (cth * r, sth * r, top);
1294           glVertex3f (cth * r, sth * r, bottom);
1295         }
1296       polys += segments;
1297       glEnd();
1298     }
1299
1300   if (wire_p)
1301     {
1302       glBegin (GL_LINE_LOOP);
1303       for (i = 0; i < segments; i++)
1304         {
1305           GLfloat th = i * width;
1306           glVertex3f (cos(th) * r, sin(th) * r, top);
1307         }
1308       glEnd();
1309       glBegin (GL_LINE_LOOP);
1310       for (i = 0; i < segments; i++)
1311         {
1312           GLfloat th = i * width;
1313           glVertex3f (cos(th) * r, sin(th) * r, bottom);
1314         }
1315       glEnd();
1316     }
1317
1318   return polys;
1319 }
1320
1321
1322 /* Draws a donut-shaped disc between the given radii,
1323    with faces pointing either up or down.
1324    The first radius may be 0, in which case, a filled disc is drawn.
1325  */
1326 static int
1327 draw_disc (ModeInfo *mi, int segments,
1328            GLfloat ra, GLfloat rb, GLfloat z, Bool up_p)
1329 {
1330   int i;
1331   int polys = 0;
1332   Bool wire_p = MI_IS_WIREFRAME(mi);
1333   GLfloat width = M_PI * 2 / segments;
1334
1335   if (ra <  0) abort();
1336   if (rb <= 0) abort();
1337
1338   if (ra == 0)
1339     glFrontFace (up_p ? GL_CW : GL_CCW);
1340   else
1341     glFrontFace (up_p ? GL_CCW : GL_CW);
1342
1343   if (ra == 0)
1344     glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
1345   else
1346     glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1347
1348   glNormal3f (0, 0, (up_p ? -1 : 1));
1349
1350   if (ra == 0 && !wire_p)
1351     glVertex3f (0, 0, z);
1352
1353   for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1354     {
1355       GLfloat th = i * width;
1356       GLfloat cth = cos(th);
1357       GLfloat sth = sin(th);
1358       if (wire_p || ra != 0)
1359         glVertex3f (cth * ra, sth * ra, z);
1360       glVertex3f (cth * rb, sth * rb, z);
1361       polys++;
1362     }
1363   glEnd();
1364   return polys;
1365 }
1366
1367
1368 /* Draws N thick radial lines between the given radii,
1369    with faces pointing either up or down.
1370  */
1371 static int
1372 draw_spokes (ModeInfo *mi, int n, GLfloat thickness, int segments,
1373              GLfloat ra, GLfloat rb, GLfloat z1, GLfloat z2)
1374 {
1375   int i;
1376   int polys = 0;
1377   Bool wire_p = MI_IS_WIREFRAME(mi);
1378   GLfloat width;
1379   int segments2 = 0;
1380   int insegs, outsegs;
1381   int tick;
1382   int state;
1383
1384   if (ra <= 0 || rb <= 0) abort();
1385
1386   segments *= 3;
1387
1388   while (segments2 < segments) /* need a multiple of N >= segments */
1389     segments2 += n;            /* (yes, this is a moronic way to find that) */
1390
1391   insegs  = ((float) (segments2 / n) + 0.5) / thickness;
1392   outsegs = (segments2 / n) - insegs;
1393   if (insegs  <= 0) insegs = 1;
1394   if (outsegs <= 0) outsegs = 1;
1395
1396   segments2 = (insegs + outsegs) * n;
1397   width = M_PI * 2 / segments2;
1398
1399   tick = 0;
1400   state = 0;
1401   for (i = 0; i < segments2; i++, tick++)
1402     {
1403       GLfloat th1 = i * width;
1404       GLfloat th2 = th1 + width;
1405       GLfloat cth1 = cos(th1);
1406       GLfloat sth1 = sin(th1);
1407       GLfloat cth2 = cos(th2);
1408       GLfloat sth2 = sin(th2);
1409       GLfloat orb = rb;
1410
1411       int changed = (i == 0);
1412
1413       if (state == 0 && tick == insegs)
1414         tick = 0, state = 1, changed = 1;
1415       else if (state == 1 && tick == outsegs)
1416         tick = 0, state = 0, changed = 1;
1417
1418       if ((state == 1 ||                /* in */
1419            (state == 0 && changed)) &&
1420           (!wire_p || wire_all_p))
1421         {
1422           /* top */
1423           glFrontFace (GL_CCW);
1424           glBegin (wire_p ? GL_LINES : GL_QUADS);
1425           glNormal3f (0, 0, -1);
1426           glVertex3f (cth1 * ra, sth1 * ra, z1);
1427           glVertex3f (cth1 * rb, sth1 * rb, z1);
1428           glVertex3f (cth2 * rb, sth2 * rb, z1);
1429           glVertex3f (cth2 * ra, sth2 * ra, z1);
1430           polys++;
1431           glEnd();
1432
1433           /* bottom */
1434           glFrontFace (GL_CW);
1435           glBegin (wire_p ? GL_LINES : GL_QUADS);
1436           glNormal3f (0, 0, 1);
1437           glVertex3f (cth1 * ra, sth1 * ra, z2);
1438           glVertex3f (cth1 * rb, sth1 * rb, z2);
1439           glVertex3f (cth2 * rb, sth2 * rb, z2);
1440           glVertex3f (cth2 * ra, sth2 * ra, z2);
1441           polys++;
1442           glEnd();
1443         }
1444
1445       if (state == 1 && changed)   /* entering "in" state */
1446         {
1447           /* left */
1448           glFrontFace (GL_CW);
1449           glBegin (wire_p ? GL_LINES : GL_QUADS);
1450           do_normal (cth1 * rb, sth1 * rb, z1,
1451                      cth1 * ra, sth1 * ra, z1,
1452                      cth1 * rb, sth1 * rb, z2);
1453           glVertex3f (cth1 * ra, sth1 * ra, z1);
1454           glVertex3f (cth1 * rb, sth1 * rb, z1);
1455           glVertex3f (cth1 * rb, sth1 * rb, z2);
1456           glVertex3f (cth1 * ra, sth1 * ra, z2);
1457           polys++;
1458           glEnd();
1459         }
1460
1461       if (state == 0 && changed)   /* entering "out" state */
1462         {
1463           /* right */
1464           glFrontFace (GL_CCW);
1465           glBegin (wire_p ? GL_LINES : GL_QUADS);
1466           do_normal (cth2 * ra, sth2 * ra, z1,
1467                      cth2 * rb, sth2 * rb, z1,
1468                      cth2 * rb, sth2 * rb, z2);
1469           glVertex3f (cth2 * ra, sth2 * ra, z1);
1470           glVertex3f (cth2 * rb, sth2 * rb, z1);
1471           glVertex3f (cth2 * rb, sth2 * rb, z2);
1472           glVertex3f (cth2 * ra, sth2 * ra, z2);
1473           polys++;
1474           glEnd();
1475         }
1476
1477       rb = orb;
1478     }
1479   glEnd();
1480   return polys;
1481 }
1482
1483
1484 /* Draws some bumps (embedded cylinders) on the gear.
1485  */
1486 static int
1487 draw_gear_nubs (ModeInfo *mi, gear *g)
1488 {
1489   Bool wire_p = MI_IS_WIREFRAME(mi);
1490   int polys = 0;
1491   int i;
1492   int steps = 20;
1493   double r, size, height;
1494   GLfloat *cc;
1495   int which;
1496   GLfloat width, off;
1497
1498   if (! g->nubs) return 0;
1499
1500   which = biggest_ring (g, &r, &size, &height);
1501   size /= 5;
1502   height *= 0.7;
1503
1504   cc = (which == 1 ? g->color : g->color2);
1505   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cc);
1506
1507   width = M_PI * 2 / g->nubs;
1508   off = M_PI / (g->nteeth * 2);  /* align first nub with a tooth */
1509
1510   for (i = 0; i < g->nubs; i++)
1511     {
1512       GLfloat th = (i * width) + off;
1513       glPushMatrix();
1514       glTranslatef (cos(th) * r, sin(th) * r, 0);
1515
1516       if (wire_p && !wire_all_p)
1517         polys += draw_ring (mi, steps/2, size, 0, 0, False);
1518       else
1519         {
1520           polys += draw_disc (mi, steps, 0, size, -height, True);
1521           polys += draw_disc (mi, steps, 0, size,  height, False);
1522           polys += draw_ring (mi, steps, size, -height, height, False);
1523         }
1524       glPopMatrix();
1525     }
1526   return polys;
1527 }
1528
1529
1530
1531 /* Draws a much simpler representation of a gear.
1532  */
1533 static int
1534 draw_gear_schematic (ModeInfo *mi, gear *g)
1535 {
1536   Bool wire_p = MI_IS_WIREFRAME(mi);
1537   int polys = 0;
1538   int i;
1539   GLfloat width = M_PI * 2 / g->nteeth;
1540
1541   if (!wire_p) glDisable(GL_LIGHTING);
1542   glColor3f (g->color[0] * 0.6, g->color[1] * 0.6, g->color[2] * 0.6);
1543
1544   glBegin (GL_LINES);
1545   for (i = 0; i < g->nteeth; i++)
1546     {
1547       GLfloat th = (i * width) + (width/4);
1548       glVertex3f (0, 0, -g->thickness/2);
1549       glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1550     }
1551   polys += g->nteeth;
1552   glEnd();
1553
1554   glBegin (GL_LINE_LOOP);
1555   for (i = 0; i < g->nteeth; i++)
1556     {
1557       GLfloat th = (i * width) + (width/4);
1558       glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1559     }
1560   polys += g->nteeth;
1561   glEnd();
1562
1563   if (!wire_p) glEnable(GL_LIGHTING);
1564   return polys;
1565 }
1566
1567
1568 /* Renders all the interior (non-toothy) parts of a gear:
1569    the discs, axles, etc.
1570  */
1571 static int
1572 draw_gear_interior (ModeInfo *mi, gear *g)
1573 {
1574   Bool wire_p = MI_IS_WIREFRAME(mi);
1575   int polys = 0;
1576
1577   int steps = g->nteeth * 2;
1578   if (steps < 10) steps = 10;
1579   if (wire_p && !wire_all_p) steps /= 2;
1580
1581   /* ring 1 (facing in) is done in draw_gear_teeth */
1582
1583   /* ring 2 (facing in) and disc 2
1584    */
1585   if (g->inner_r2)
1586     {
1587       GLfloat ra = g->inner_r;
1588       GLfloat rb = g->inner_r2;
1589       GLfloat za = -g->thickness2/2;
1590       GLfloat zb =  g->thickness2/2;
1591
1592       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color2);
1593
1594       if ((g->coax_p != 1 && !g->inner_r3) ||
1595           (wire_p && wire_all_p))
1596         polys += draw_ring (mi, steps, rb, za, zb, True);  /* ring facing in */
1597
1598       if (wire_p && wire_all_p)
1599         polys += draw_ring (mi, steps, ra, za, zb, True);  /* ring facing in */
1600
1601       if (g->spokes)
1602         polys += draw_spokes (mi, g->spokes, g->spoke_thickness,
1603                               steps, ra, rb, za, zb);
1604       else if (!wire_p || wire_all_p)
1605         {
1606           polys += draw_disc (mi, steps, ra, rb, za, True);  /* top plate */
1607           polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1608         }
1609     }
1610
1611   /* ring 3 (facing in and out) and disc 3
1612    */
1613   if (g->inner_r3)
1614     {
1615       GLfloat ra = g->inner_r2;
1616       GLfloat rb = g->inner_r3;
1617       GLfloat za = -g->thickness3/2;
1618       GLfloat zb =  g->thickness3/2;
1619
1620       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1621
1622       polys += draw_ring (mi, steps, ra, za, zb, False);  /* ring facing out */
1623
1624       if (g->coax_p != 1 || (wire_p && wire_all_p))
1625         polys += draw_ring (mi, steps, rb, za, zb, True);  /* ring facing in */
1626
1627       if (!wire_p || wire_all_p)
1628         {
1629           polys += draw_disc (mi, steps, ra, rb, za, True);  /* top plate */
1630           polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1631         }
1632     }
1633
1634   /* axle tube
1635    */
1636   if (g->coax_p == 1)
1637     {
1638       GLfloat cap_height = g->coax_thickness/3;
1639
1640       GLfloat ra = (g->inner_r3 ? g->inner_r3 :
1641                     g->inner_r2 ? g->inner_r2 :
1642                     g->inner_r);
1643       GLfloat za = -(g->thickness/2 + cap_height);
1644       GLfloat zb = g->coax_thickness/2 + plane_displacement + cap_height;
1645
1646       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1647
1648       if (wire_p && !wire_all_p) steps /= 2;
1649
1650       polys += draw_ring (mi, steps, ra, za, zb, False);  /* ring facing out */
1651
1652       if (!wire_p || wire_all_p)
1653         {
1654           polys += draw_disc (mi, steps, 0,  ra, za, True);  /* top plate */
1655           polys += draw_disc (mi, steps, 0,  ra, zb, False); /* bottom plate */
1656         }
1657     }
1658   return polys;
1659 }
1660
1661
1662 /* Computes the vertices and normals of the teeth of a gear.
1663    This is the heavy lifting: there are a ton of polygons around the
1664    perimiter of a gear, and the normals are difficult (not radial
1665    or right angles.)
1666
1667    It would be nice if we could cache this, but the numbers are
1668    different for essentially every gear:
1669
1670       - Every gear has a different inner_r, so the vertices of the
1671         inner ring (and thus, the triangle fans on the top and bottom
1672         faces) are different in a non-scalable way.
1673
1674       - If the ratio between tooth_w and tooth_h changes, the normals
1675         on the outside edges of the teeth are invalid (this can happen
1676         every time we start a new train.)
1677
1678    So instead, we rely on OpenGL display lists to do the cacheing for
1679    us -- we only compute all these normals once per gear, instead of
1680    once per gear per frame.
1681  */
1682 static void
1683 gear_teeth_geometry (ModeInfo *mi, gear *g,
1684                      int *points_per_tooth_ret,
1685                      XYZ **points_ret,
1686                      XYZ **normals_ret)
1687 {
1688   int i;
1689
1690   int ppt = 15; /* points per tooth */
1691
1692   GLfloat width = M_PI * 2 / g->nteeth;
1693
1694   GLfloat rh = g->tooth_h;
1695   GLfloat tw = width;
1696   GLfloat fudge = (g->nteeth >= 5 ? 0 : 0.04);   /* reshape small ones a bit */
1697
1698   XYZ *points   = (XYZ *) calloc (ppt * g->nteeth + 1, sizeof(*points));
1699   XYZ *fnormals = (XYZ *) calloc (ppt * g->nteeth + 1, sizeof(*points));
1700   XYZ *pnormals = (XYZ *) calloc (ppt * g->nteeth + 1, sizeof(*points));
1701   int npoints = 0;
1702
1703   /* Approximate shape of an "involute" gear tooth.
1704
1705                                  (TH)
1706                  th0 th1 th2 th3 th4 th5 th6 th7 th8   th9    th10
1707                    :  :  :   :    :    :   :  :  :      :      :
1708                    :  :  :   :    :    :   :  :  :      :      :
1709         r0 ........:..:..:...___________...:..:..:......:......:..
1710                    :  :  :  /:    :    :\  :  :  :      :      :
1711                    :  :  : / :    :    : \ :  :  :      :      :
1712                    :  :  :/  :    :    :  \:  :  :      :      :
1713         r1 ........:.....@...:....:....:...@..:..:......:......:..
1714                    :  : @:   :    :    :   :@ :  :      :      :
1715     (R) ...........:...@.:...:....:....:...:.@..........:......:......
1716                    :  :@ :   :    :    :   : @:  :      :      :
1717         r2 ........:..@..:...:....:....:...:..@:........:......:..
1718                    : /:  :   :    :    :   :  :\ :      :      :
1719                    :/ :  :   :    :    :   :  : \:      :      : /
1720         r3 ......__/..:..:...:....:....:...:..:..\______________/
1721                    :  :  :   :    :    :   :  :  :      :      :
1722                    |  :  :   :    :    :   :  :  |      :      :
1723                    :  :  :   :    :    :   :  :  :      :      :
1724                    |  :  :   :    :    :   :  :  |      :      :
1725         r4 ......__:_____________________________:________________
1726    */
1727
1728   GLfloat R = g->r;
1729
1730   GLfloat r[20];
1731   GLfloat th[20];
1732
1733   r[0] = R + (rh * 0.5);
1734   r[1] = R + (rh * 0.25);
1735   r[2] = R - (rh * 0.25);
1736   r[3] = R - (rh * 0.5);
1737   r[4] = g->inner_r;
1738
1739   th[0] = -tw * 0.45;
1740   th[1] = -tw * 0.30;
1741   th[2] = -tw *(0.16 - fudge);
1742   th[3] = -tw * 0.04;
1743   th[4] =  0;
1744   th[5] =  tw * 0.04;
1745   th[6] =  tw *(0.16 - fudge);
1746   th[7] =  tw * 0.30;
1747   th[8] =  tw * 0.45;
1748   th[9] =  width / 2;
1749   th[10]=  th[0] + width;
1750
1751   if (!points || !fnormals || !pnormals)
1752     {
1753       fprintf (stderr, "%s: out of memory\n", progname);
1754       exit (1);
1755     }
1756
1757   npoints = 0;
1758
1759   /* First, compute the coordinates of every point used for every tooth.
1760    */
1761   for (i = 0; i < g->nteeth; i++)
1762     {
1763       GLfloat TH = (i * width) + (width/4);
1764
1765 #     undef PUSH
1766 #     define PUSH(PR,PTH) \
1767         points[npoints].x = cos(TH+th[(PTH)]) * r[(PR)]; \
1768         points[npoints].y = sin(TH+th[(PTH)]) * r[(PR)]; \
1769         npoints++
1770
1771       /* start1 = npoints; */
1772
1773       PUSH(3, 0);       /* tooth left 1 */
1774       PUSH(2, 1);       /* tooth left 2 */
1775       PUSH(1, 2);       /* tooth left 3 */
1776       PUSH(0, 3);       /* tooth top 1 */
1777       PUSH(0, 5);       /* tooth top 2 */
1778       PUSH(1, 6);       /* tooth right 1 */
1779       PUSH(2, 7);       /* tooth right 2 */
1780       PUSH(3, 8);       /* tooth right 3 */
1781       PUSH(3, 10);      /* gap top */
1782
1783       /* end1   = npoints; */
1784
1785       PUSH(4, 8);       /* gap interior */
1786
1787       /* start2 = npoints; */
1788
1789       PUSH(4, 10);      /* tooth interior 1 */
1790       PUSH(4, 8);       /* tooth interior 2 */
1791       PUSH(4, 4);       /* tooth bottom 1 */
1792       PUSH(4, 0);       /* tooth bottom 2 */
1793
1794       /* end2 = npoints; */
1795
1796       PUSH(3, 4);       /* midpoint */
1797
1798       /* mid = npoints-1; */
1799
1800       if (i == 0 && npoints != ppt) abort();  /* go update "ppt"! */
1801 #     undef PUSH
1802     }
1803
1804
1805   /* Now compute the face normals for each facet on the tooth rim.
1806    */
1807   for (i = 0; i < npoints; i++)
1808     {
1809       XYZ p1, p2, p3;
1810       p1 = points[i];
1811       p2 = points[i+1];
1812       p3 = p1;
1813       p3.z++;
1814       fnormals[i] = calc_normal (p1, p2, p3);
1815     }
1816
1817
1818   /* From the face normals, compute the vertex normals (by averaging
1819      the normals of adjascent faces.)
1820    */
1821   for (i = 0; i < npoints; i++)
1822     {
1823       int a = (i == 0 ? npoints-1 : i-1);
1824       int b = i;
1825
1826       /* Kludge to fix the normal on the last top point: since the
1827          faces go all the way around, this normal pointed clockwise
1828          instead of radially out. */
1829       int start1 = (i / ppt) * ppt;
1830       int end1   = start1 + 9;
1831       XYZ n1, n2;
1832
1833       if (b == end1-1)
1834         b = (start1 + ppt == npoints ? 0 : start1 + ppt);
1835
1836       n1 = fnormals[a];   /* normal of [i-1 - i] face */
1837       n2 = fnormals[b];   /* normal of [i - i+1] face */
1838       pnormals[i].x = (n1.x + n2.x) / 2;
1839       pnormals[i].y = (n1.y + n2.y) / 2;
1840       pnormals[i].z = (n1.z + n2.z) / 2;
1841     }
1842
1843   free (fnormals);
1844
1845   if (points_ret)
1846     *points_ret = points;
1847   else
1848     free (points);
1849
1850   if (normals_ret)
1851     *normals_ret = pnormals;
1852   else
1853     free (pnormals);
1854
1855   if (points_per_tooth_ret)
1856     *points_per_tooth_ret = ppt;
1857 }
1858
1859
1860 /* Renders all teeth of a gear.
1861  */
1862 static int
1863 draw_gear_teeth (ModeInfo *mi, gear *g)
1864 {
1865   Bool wire_p = MI_IS_WIREFRAME(mi);
1866   int polys = 0;
1867   int i;
1868
1869   GLfloat z1 = -g->thickness/2;
1870   GLfloat z2 =  g->thickness/2;
1871
1872   int ppt;
1873   XYZ *points, *pnormals;
1874
1875   gear_teeth_geometry (mi, g, &ppt, &points, &pnormals);
1876
1877   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1878
1879   for (i = 0; i < g->nteeth; i++)
1880     {
1881       int j;
1882       GLfloat z;
1883
1884       int start1 = i * ppt;
1885       int end1   = start1 + 9;
1886       int start2 = end1   + 1;
1887       int end2   = start2 + 4;
1888       int mid    = end2;
1889
1890       /* Outside rim of the tooth
1891        */
1892       glFrontFace (GL_CW);
1893       glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1894       for (j = start1; j < end1; j++)
1895         {
1896           glNormal3f (pnormals[j].x, pnormals[j].y, pnormals[j].z);
1897           glVertex3f (points[j].x, points[j].y, z1);
1898           glVertex3f (points[j].x, points[j].y, z2);
1899           polys++;
1900
1901 # if 0
1902           /* Show the face normal vectors */
1903           if (wire_p)
1904             {
1905               XYZ n = fnormals[j];
1906               GLfloat x = (points[j].x + points[j+1].x) / 2;
1907               GLfloat y = (points[j].y + points[j+1].y) / 2;
1908               GLfloat z  = (z1 + z2) / 2;
1909               glVertex3f (x, y, z);
1910               glVertex3f (x + n.x, y + n.y, z);
1911             }
1912
1913           /* Show the vertex normal vectors */
1914           if (wire_p)
1915             {
1916               XYZ n = pnormals[j];
1917               GLfloat x = points[j].x;
1918               GLfloat y = points[j].y;
1919               GLfloat z  = (z1 + z2) / 2;
1920               glVertex3f (x, y, z);
1921               glVertex3f (x + n.x, y + n.y, z);
1922             }
1923 # endif /* 0 */
1924         }
1925       glEnd();
1926
1927       /* Some more lines for the outside rim of the tooth...
1928        */
1929       if (wire_p)
1930         {
1931           glBegin (GL_LINE_STRIP);
1932           for (j = start1; j < end1; j++)
1933             glVertex3f (points[j].x, points[j].y, z1);
1934           glEnd();
1935           glBegin (GL_LINE_STRIP);
1936           for (j = start1; j < end1; j++)
1937             glVertex3f (points[j].x, points[j].y, z2);
1938           glEnd();
1939         }
1940
1941       /* Inside rim behind the tooth
1942        */
1943       glFrontFace (GL_CW);
1944       glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1945       for (j = start2; j < end2; j++)
1946         {
1947           glNormal3f (-points[j].x, -points[j].y, 0);
1948           glVertex3f ( points[j].x,  points[j].y, z1);
1949           glVertex3f ( points[j].x,  points[j].y, z2);
1950           polys++;
1951         }
1952       glEnd();
1953
1954       /* Some more lines for the inside rim...
1955        */
1956       if (wire_p)
1957         {
1958           glBegin (GL_LINE_STRIP);
1959           for (j = start2; j < end2; j++)
1960             glVertex3f (points[j].x, points[j].y, z1);
1961           glEnd();
1962           glBegin (GL_LINE_STRIP);
1963           for (j = start2; j < end2; j++)
1964             glVertex3f (points[j].x, points[j].y, z2);
1965           glEnd();
1966         }
1967
1968       /* All top and bottom facets.  We can skip all of these in wire mode.
1969        */
1970       if (!wire_p || wire_all_p)
1971         for (z = z1; z <= z2; z += z2-z1)
1972           {
1973             /* Flat edge of the tooth
1974              */
1975             glFrontFace (z == z1 ? GL_CW : GL_CCW);
1976             glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
1977             glNormal3f (0, 0, z);
1978             for (j = start1; j < end2; j++)
1979               {
1980                 if (j == end1-1 || j == end1 || j == start2)
1981                   continue;  /* kludge... skip these... */
1982
1983                 if (wire_p || j == start1)
1984                   glVertex3f (points[mid].x, points[mid].y, z);
1985                 glVertex3f (points[j].x, points[j].y, z);
1986                 polys++;
1987               }
1988             glVertex3f (points[start1].x, points[start1].y, z);
1989             glEnd();
1990
1991             /* Flat edge between teeth
1992              */
1993             glFrontFace (z == z1 ? GL_CW : GL_CCW);
1994             glBegin (wire_p ? GL_LINES : GL_QUADS);
1995             glNormal3f (0, 0, z);
1996             glVertex3f (points[end1-1  ].x, points[end1-1  ].y, z);
1997             glVertex3f (points[start2  ].x, points[start2  ].y, z);
1998             glVertex3f (points[start2+1].x, points[start2+1].y, z);
1999             glVertex3f (points[end1-2  ].x, points[end1-2  ].y, z);
2000             polys++;
2001             glEnd();
2002           }
2003     }
2004
2005   free (points);
2006   free (pnormals);
2007   return polys;
2008 }
2009
2010
2011 /* Render one gear, unrotated at 0,0.
2012  */
2013 static int
2014 draw_gear_1 (ModeInfo *mi, gear *g)
2015 {
2016   Bool wire_p = MI_IS_WIREFRAME(mi);
2017   int polys = 0;
2018
2019   static GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
2020   static GLfloat shiny   = 128.0;
2021
2022   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
2023   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
2024   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
2025   glColor3f (g->color[0], g->color[1], g->color[2]);
2026
2027   if (debug_p && wire_p)
2028     polys += draw_gear_schematic (mi, g);
2029   else
2030     {
2031       glPushMatrix();
2032       glRotatef (g->wobble, 1, 0, 0);
2033       polys += draw_gear_teeth (mi, g);
2034       polys += draw_gear_interior (mi, g);
2035       polys += draw_gear_nubs (mi, g);
2036       glPopMatrix();
2037     }
2038   return polys;
2039 }
2040
2041
2042 /* Render one gear in the proper position, creating the gear's
2043    display list first if necessary.
2044  */
2045 static void
2046 draw_gear (ModeInfo *mi, int which)
2047 {
2048   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2049   gear *g = pp->gears[which];
2050   GLfloat th;
2051
2052   Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
2053                     g->x - g->r - g->tooth_h <= pp->render_right);
2054
2055   if (!visible_p && !debug_p)
2056     return;
2057
2058   if (! g->dlist)
2059     {
2060       g->dlist = glGenLists (1);
2061       if (! g->dlist)
2062         {
2063           /* I don't know how many display lists a GL implementation
2064              is supposed to provide, but hopefully it's more than
2065              "a few hundred", or we'll be in trouble...
2066            */
2067           check_gl_error ("glGenLists");
2068           abort();
2069         }
2070
2071       glNewList (g->dlist, GL_COMPILE);
2072       g->polygons = draw_gear_1 (mi, g);
2073       glEndList ();
2074     }
2075
2076   glPushMatrix();
2077
2078   glTranslatef (g->x, g->y, g->z);
2079
2080   if (g->motion_blur_p && !pp->button_down_p)
2081     {
2082       /* If we're in motion-blur mode, then draw the gear so that each
2083          frame rotates it by exactly one half tooth-width, so that it
2084          looks flickery around the edges.  But, revert to the normal
2085          way when the mouse button is down lest the user see overlapping
2086          polygons.
2087        */
2088       th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
2089       g->motion_blur_p++;
2090     }
2091   else
2092     th = g->th;
2093
2094   glRotatef (th, 0, 0, 1);
2095
2096   glPushName (g->id);
2097
2098   if (! visible_p)
2099     mi->polygon_count += draw_gear_schematic (mi, g);
2100   else
2101     {
2102       glCallList (g->dlist);
2103       mi->polygon_count += g->polygons;
2104     }
2105
2106   glPopName();
2107   glPopMatrix();
2108 }
2109
2110
2111 /* Render all gears.
2112  */
2113 static void
2114 draw_gears (ModeInfo *mi)
2115 {
2116   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2117   Bool wire_p = MI_IS_WIREFRAME(mi);
2118   int i;
2119
2120   glColor4f (1, 1, 0.8, 1);
2121
2122   glInitNames();
2123
2124   for (i = 0; i < pp->ngears; i++)
2125     draw_gear (mi, i);
2126
2127   /* draw a line connecting gears that are, uh, geared. */
2128   if (debug_p)
2129     {
2130       static GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
2131       GLfloat off = 0.1;
2132       GLfloat ox=0, oy=0, oz=0;
2133
2134       if (!wire_p) glDisable(GL_LIGHTING);
2135       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
2136       glColor3f (color[0], color[1], color[2]);
2137
2138       for (i = 0; i < pp->ngears; i++)
2139         {
2140           gear *g = pp->gears[i];
2141           glBegin(GL_LINE_STRIP);
2142           glVertex3f (g->x, g->y, g->z - off);
2143           glVertex3f (g->x, g->y, g->z + off);
2144           if (i > 0 && !g->base_p)
2145             glVertex3f (ox, oy, oz + off);
2146           glEnd();
2147           ox = g->x;
2148           oy = g->y;
2149           oz = g->z;
2150         }
2151       if (!wire_p) glEnable(GL_LIGHTING);
2152     }
2153 }
2154
2155
2156 /* Mouse hit detection
2157  */
2158 void
2159 find_mouse_gear (ModeInfo *mi)
2160 {
2161   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2162   int screen_width = MI_WIDTH (mi);
2163   int screen_height = MI_HEIGHT (mi);
2164   GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
2165   int x, y;
2166   int hits;
2167
2168   pp->mouse_gear_id = 0;
2169
2170   /* Poll mouse position */
2171   {
2172     Window r, c;
2173     int rx, ry;
2174     unsigned int m;
2175     XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
2176                    &r, &c, &rx, &ry, &x, &y, &m);
2177   }
2178
2179   if (x < 0 || y < 0 || x > screen_width || y > screen_height)
2180     return;  /* out of window */
2181
2182   /* Run OpenGL hit detector */
2183   {
2184     GLint vp[4];
2185     GLuint selbuf[512];
2186
2187     glSelectBuffer (sizeof(selbuf), selbuf);  /* set up "select" mode */
2188     glRenderMode (GL_SELECT);
2189     glMatrixMode (GL_PROJECTION);
2190     glPushMatrix();
2191     glLoadIdentity();
2192     glGetIntegerv (GL_VIEWPORT, vp);         /* save old vp */
2193     gluPickMatrix (x, vp[3]-y, 5, 5, vp);
2194     gluPerspective (30.0, 1/h, 1.0, 100.0);  /* must match reshape_pinion() */
2195     glMatrixMode (GL_MODELVIEW);
2196
2197     draw_gears (mi);                         /* render into "select" buffer */
2198
2199     glMatrixMode (GL_PROJECTION);            /* restore old vp */
2200     glPopMatrix ();
2201     glMatrixMode (GL_MODELVIEW);
2202     glFlush();
2203     hits = glRenderMode (GL_RENDER);         /* done selecting */
2204
2205     if (hits > 0)
2206       {
2207         int i;
2208         GLuint name_count = 0;
2209         GLuint *p = (GLuint *) selbuf;
2210         GLuint *pnames = 0;
2211         GLuint min_z = ~0;
2212
2213         for (i = 0; i < hits; i++)
2214           {     
2215             int names = *p++;
2216             if (*p < min_z)                  /* find match closest to screen */
2217               {
2218                 name_count = names;
2219                 min_z = *p;
2220                 pnames = p+2;
2221               }
2222             p += names+2;
2223           }
2224
2225         if (name_count > 0)                  /* take first hit */
2226           pp->mouse_gear_id = pnames[0];
2227       }
2228   }
2229 }
2230
2231
2232 /* Window management, etc
2233  */
2234 void
2235 reshape_pinion (ModeInfo *mi, int width, int height)
2236 {
2237   GLfloat h = (GLfloat) height / (GLfloat) width;
2238   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2239
2240   glViewport (0, 0, (GLint) width, (GLint) height);
2241
2242   glMatrixMode(GL_PROJECTION);
2243   glLoadIdentity();
2244   gluPerspective (30.0, 1/h, 1.0, 100.0);
2245
2246   glMatrixMode(GL_MODELVIEW);
2247   glLoadIdentity();
2248   gluLookAt( 0.0, 0.0, 30.0,
2249              0.0, 0.0, 0.0,
2250              0.0, 1.0, 0.0);
2251
2252   glClear(GL_COLOR_BUFFER_BIT);
2253
2254   {
2255     GLfloat render_width, layout_width;
2256     pp->vp_height = 1.0;
2257     pp->vp_width  = 1/h;
2258
2259     pp->vp_left   = -pp->vp_width/2;
2260     pp->vp_right  =  pp->vp_width/2;
2261     pp->vp_top    =  pp->vp_height/2;
2262     pp->vp_bottom = -pp->vp_height/2;
2263
2264     render_width = pp->vp_width * 2;
2265     layout_width = pp->vp_width * 0.8 * gear_size;
2266
2267     pp->render_left  = -render_width/2;
2268     pp->render_right =  render_width/2;
2269
2270     pp->layout_left  = pp->render_right;
2271     pp->layout_right = pp->layout_left + layout_width;
2272   }
2273 }
2274
2275
2276 Bool
2277 pinion_handle_event (ModeInfo *mi, XEvent *event)
2278 {
2279   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2280
2281   if (event->xany.type == ButtonPress &&
2282       event->xbutton.button & Button1)
2283     {
2284       pp->button_down_p = True;
2285       gltrackball_start (pp->trackball,
2286                          event->xbutton.x, event->xbutton.y,
2287                          MI_WIDTH (mi), MI_HEIGHT (mi));
2288       return True;
2289     }
2290   else if (event->xany.type == ButtonRelease &&
2291            event->xbutton.button & Button1)
2292     {
2293       pp->button_down_p = False;
2294       return True;
2295     }
2296   else if (event->xany.type == MotionNotify &&
2297            pp->button_down_p)
2298     {
2299       gltrackball_track (pp->trackball,
2300                          event->xmotion.x, event->xmotion.y,
2301                          MI_WIDTH (mi), MI_HEIGHT (mi));
2302       return True;
2303     }
2304   else if (event->xany.type == KeyPress)
2305     {
2306       KeySym keysym;
2307       char c = 0;
2308       XLookupString (&event->xkey, &c, 1, &keysym, 0);
2309       if (c == ' ' && debug_one_gear_p && pp->ngears)
2310         {
2311           delete_gear (mi, pp->gears[0]);
2312           return True;
2313         }
2314     }
2315
2316   return False;
2317 }
2318
2319
2320 void 
2321 init_pinion (ModeInfo *mi)
2322 {
2323   pinion_configuration *pp;
2324   int wire = MI_IS_WIREFRAME(mi);
2325
2326   if (!pps) {
2327     pps = (pinion_configuration *)
2328       calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
2329     if (!pps) {
2330       fprintf(stderr, "%s: out of memory\n", progname);
2331       exit(1);
2332     }
2333
2334     pp = &pps[MI_SCREEN(mi)];
2335   }
2336
2337   pp = &pps[MI_SCREEN(mi)];
2338
2339   pp->glx_context = init_GL(mi);
2340
2341   load_fonts (mi);
2342   reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2343
2344   pp->title_list  = glGenLists (1);
2345
2346   pp->ngears = 0;
2347   pp->gears_size = 0;
2348   pp->gears = 0;
2349
2350   plane_displacement *= gear_size;
2351
2352   if (!wire)
2353     {
2354       GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
2355       GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
2356       GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
2357       GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
2358
2359       glEnable(GL_LIGHTING);
2360       glEnable(GL_LIGHT0);
2361       glEnable(GL_DEPTH_TEST);
2362       glEnable(GL_CULL_FACE);
2363
2364       glLightfv(GL_LIGHT0, GL_POSITION, pos);
2365       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
2366       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
2367       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
2368     }
2369
2370   pp->trackball = gltrackball_init ();
2371
2372   ffwd (mi);
2373 }
2374
2375
2376 void
2377 draw_pinion (ModeInfo *mi)
2378 {
2379   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2380   Display *dpy = MI_DISPLAY(mi);
2381   Window window = MI_WINDOW(mi);
2382   Bool wire_p = MI_IS_WIREFRAME(mi);
2383   static int tick = 0;
2384
2385   if (!pp->glx_context)
2386     return;
2387
2388   if (!pp->button_down_p)
2389     {
2390       if (!debug_one_gear_p || pp->ngears == 0)
2391         scroll_gears (mi);
2392       spin_gears (mi);
2393     }
2394
2395   glShadeModel(GL_SMOOTH);
2396
2397   glEnable(GL_DEPTH_TEST);
2398   glEnable(GL_NORMALIZE);
2399   glEnable(GL_CULL_FACE);
2400
2401   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2402
2403   glPushMatrix ();
2404   {
2405     gltrackball_rotate (pp->trackball);
2406     mi->polygon_count = 0;
2407
2408     glScalef (16, 16, 16);   /* map vp_width/height to the screen */
2409
2410     if (debug_one_gear_p)    /* zoom in */
2411       glScalef (3, 3, 3);
2412     else if (debug_p)        /* show the "visible" and "layout" areas */
2413       {
2414         GLfloat ow = pp->layout_right - pp->render_left;
2415         GLfloat rw = pp->render_right - pp->render_left;
2416         GLfloat s = (pp->vp_width / ow) * 0.85;
2417         glScalef (s, s, s);
2418         glTranslatef (-(ow - rw) / 2, 0, 0);
2419       }
2420     else
2421       {
2422         GLfloat s = 1.2;
2423         glScalef (s, s, s);           /* zoom in a little more */
2424         glRotatef (-35, 1, 0, 0);     /* tilt back */
2425         glRotatef (  8, 0, 1, 0);     /* tilt left */
2426         glTranslatef (0.02, 0.1, 0);  /* pan up */
2427       }
2428
2429     draw_gears (mi);
2430
2431     if (debug_p)
2432       {
2433         if (!wire_p) glDisable(GL_LIGHTING);
2434         glColor3f (0.6, 0, 0);
2435         glBegin(GL_LINE_LOOP);
2436         glVertex3f (pp->render_left,  pp->vp_top,    0);
2437         glVertex3f (pp->render_right, pp->vp_top,    0);
2438         glVertex3f (pp->render_right, pp->vp_bottom, 0);
2439         glVertex3f (pp->render_left,  pp->vp_bottom, 0);
2440         glEnd();
2441         glColor3f (0.4, 0, 0);
2442         glBegin(GL_LINES);
2443         glVertex3f (pp->vp_left,      pp->vp_top,    0);
2444         glVertex3f (pp->vp_left,      pp->vp_bottom, 0);
2445         glVertex3f (pp->vp_right,     pp->vp_top,    0);
2446         glVertex3f (pp->vp_right,     pp->vp_bottom, 0);
2447         glEnd();
2448         glColor3f (0, 0.4, 0);
2449         glBegin(GL_LINE_LOOP);
2450         glVertex3f (pp->layout_left,  pp->vp_top,    0);
2451         glVertex3f (pp->layout_right, pp->vp_top,    0);
2452         glVertex3f (pp->layout_right, pp->vp_bottom, 0);
2453         glVertex3f (pp->layout_left,  pp->vp_bottom, 0);
2454         glEnd();
2455         if (!wire_p) glEnable(GL_LIGHTING);
2456       }
2457
2458     if (tick++ > 10)   /* only do this every N frames */
2459       {
2460         tick = 0;
2461         find_mouse_gear (mi);
2462         new_label (mi);
2463       }
2464   }
2465   glPopMatrix ();
2466
2467   glCallList (pp->title_list);
2468
2469   if (mi->fps_p) do_fps (mi);
2470   glFinish();
2471
2472   glXSwapBuffers(dpy, window);
2473 }
2474
2475 #endif /* USE_GL */