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