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