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