http://www.jwz.org/xscreensaver/xscreensaver-5.12.tar.gz
[xscreensaver] / hacks / glx / pinion.c
1 /* pinion, Copyright (c) 2004-2008 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:  -*-helvetica-medium-r-normal-*-180-*\n" \
16                         "*titleFont2: -*-helvetica-medium-r-normal-*-120-*\n" \
17                         "*titleFont3: -*-helvetica-medium-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 "involute.h"
32 #include <ctype.h>
33
34 #ifdef USE_GL /* whole file */
35
36 #define DEF_SPIN_SPEED   "1.0"
37 #define DEF_SCROLL_SPEED "1.0"
38 #define DEF_GEAR_SIZE    "1.0"
39 #define DEF_MAX_RPM      "900"
40
41 typedef struct {
42   GLXContext *glx_context;
43   GLfloat vp_left, vp_right, vp_top, vp_bottom;    /* default visible area */
44   GLfloat vp_width, vp_height;
45   GLfloat render_left, render_right;  /* area in which gears are displayed */
46   GLfloat layout_left, layout_right;  /* layout region, on the right side  */
47
48   int ngears;
49   int gears_size;
50   gear **gears;
51
52   trackball_state *trackball;
53   Bool button_down_p;
54   unsigned long mouse_gear_id;
55
56   XFontStruct *xfont1, *xfont2, *xfont3;
57   GLuint font1_dlist, font2_dlist, font3_dlist;
58   GLuint title_list;
59   int draw_tick;
60
61   GLfloat plane_displacement;        /* distance between coaxial gears */
62
63   int debug_size_failures;           /* for debugging messages */
64   int debug_position_failures;
65   unsigned long current_length;      /* gear count in current train */
66   unsigned long current_blur_length; /* how long have we been blurring? */
67
68 } pinion_configuration;
69
70
71 static pinion_configuration *pps = NULL;
72
73 /* command line arguments */
74 static GLfloat spin_speed, scroll_speed, gear_size, max_rpm;
75
76 static Bool verbose_p = False;            /* print progress on stderr */
77 static Bool debug_p = False;              /* render as flat schematic */
78
79 /* internal debugging variables */
80 static Bool debug_placement_p = False;    /* extreme verbosity on stderr */
81 static Bool debug_one_gear_p = False;     /* draw one big stationary gear */
82
83
84 static XrmOptionDescRec opts[] = {
85   { "-spin",   ".spinSpeed",   XrmoptionSepArg, 0 },
86   { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
87   { "-size",   ".gearSize",    XrmoptionSepArg, 0 },
88   { "-max-rpm",".maxRPM",      XrmoptionSepArg, 0 },
89   { "-debug",  ".debug",       XrmoptionNoArg, "True" },
90   { "-verbose",".verbose",     XrmoptionNoArg, "True" },
91 };
92
93 static argtype vars[] = {
94   {&spin_speed,   "spinSpeed",   "SpinSpeed",   DEF_SPIN_SPEED,   t_Float},
95   {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
96   {&gear_size,    "gearSize",    "GearSize",    DEF_GEAR_SIZE,    t_Float},
97   {&max_rpm,      "maxRPM",      "MaxRPM",      DEF_MAX_RPM,      t_Float},
98   {&debug_p,      "debug",       "Debug",       "False",          t_Bool},
99   {&verbose_p,    "verbose",     "Verbose",     "False",          t_Bool},
100 };
101
102 ENTRYPOINT ModeSpecOpt pinion_opts = {countof(opts), opts, countof(vars), vars, NULL};
103
104 \f
105 /* Font stuff
106  */
107
108 static void
109 load_fonts (ModeInfo *mi)
110 {
111   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
112   load_font (mi->dpy, "titleFont",  &pp->xfont1, &pp->font1_dlist);
113   load_font (mi->dpy, "titleFont2", &pp->xfont2, &pp->font2_dlist);
114   load_font (mi->dpy, "titleFont3", &pp->xfont3, &pp->font3_dlist);
115 }
116
117
118
119 static void rpm_string (double rpm, char *s);
120
121 static void
122 new_label (ModeInfo *mi)
123 {
124   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
125   char label[1024];
126   int i;
127   gear *g = 0;
128
129   if (pp->mouse_gear_id)
130     for (i = 0; i < pp->ngears; i++)
131       if (pp->gears[i]->id == pp->mouse_gear_id)
132         {
133           g = pp->gears[i];
134           break;
135         }
136
137   if (!g)
138     *label = 0;
139   else
140     {
141       sprintf (label, "%d teeth\n", (int) g->nteeth);
142       rpm_string (g->rpm, label + strlen(label));
143       if (debug_p)
144         sprintf (label + strlen (label), "\nPolys:  %d\nModel:  %s  (%.2f)\n",
145                  g->polygons,
146                  (g->size == INVOLUTE_SMALL ? "small" :
147                   g->size == INVOLUTE_MEDIUM ? "medium"
148                   : "large"),
149                  g->tooth_h * MI_HEIGHT(mi));
150     }
151
152   glNewList (pp->title_list, GL_COMPILE);
153   if (*label)
154     {
155       XFontStruct *f;
156       GLuint fl;
157       if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
158         f = pp->xfont1, fl = pp->font1_dlist;                  /* big font */
159       else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
160         f = pp->xfont2, fl = pp->font2_dlist;                  /* small font */
161       else
162         f = pp->xfont3, fl = pp->font3_dlist;                  /* tiny font */
163
164       glColor3f (0.8, 0.8, 0);
165       print_gl_string (mi->dpy, f, fl,
166                        mi->xgwa.width, mi->xgwa.height,
167                        10, mi->xgwa.height - 10,
168                        label, False);
169     }
170   glEndList ();
171 }
172
173 \f
174 /* Some utilities
175  */
176
177
178 /* Find the gear in the scene that is farthest to the right or left.
179  */
180 static gear *
181 farthest_gear (ModeInfo *mi, Bool left_p)
182 {
183   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
184   int i;
185   gear *rg = 0;
186   double x = (left_p ? 999999 : -999999);
187   for (i = 0; i < pp->ngears; i++)
188     {
189       gear *g = pp->gears[i];
190       double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
191       if (left_p ? (x > gx) : (x < gx))
192         {
193           rg = g;
194           x = gx;
195         }
196     }
197   return rg;
198 }
199
200
201 /* Compute the revolutions per minute of a gear.
202  */
203 static void
204 compute_rpm (ModeInfo *mi, gear *g)
205 {
206   double fps, rpf, rps;
207   fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
208
209   if (fps > 150) fps = 150;  /* let's be reasonable... */
210   if (fps < 10)  fps = 10;
211
212   rpf    = (g->ratio * spin_speed) / 360.0;   /* rotations per frame */
213   rps    = rpf * fps;                         /* rotations per second */
214   g->rpm = rps * 60;
215 }
216
217 /* Prints the RPM into a string, doing fancy float formatting.
218  */
219 static void
220 rpm_string (double rpm, char *s)
221 {
222   char buf[30];
223   int L;
224   if (rpm >= 0.1)          sprintf (buf, "%.2f", rpm);
225   else if (rpm >= 0.001)   sprintf (buf, "%.4f", rpm);
226   else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
227   else                     sprintf (buf, "%.16f",rpm);
228
229   L = strlen(buf);
230   while (buf[L-1] == '0') buf[--L] = 0;
231   if (buf[L-1] == '.') buf[--L] = 0;
232   strcpy (s, buf);
233   strcat (s, " RPM");
234 }
235
236
237 \f
238 /* Layout and stuff.
239  */
240
241
242 /* Create and return a new gear sized for placement next to or on top of
243    the given parent gear (if any.)  Returns 0 if out of memory.
244  */
245 static gear *
246 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
247 {
248   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
249   gear *g = (gear *) calloc (1, sizeof (*g));
250   int loop_count = 0;
251   static unsigned long id = 0;  /* only used in debugging output */
252
253   if (!g) return 0;
254   if (coaxial_p && !parent) abort();
255   g->id = ++id;
256
257   g->coax_displacement = pp->plane_displacement;
258
259   while (1)
260     {
261       loop_count++;
262       if (loop_count > 1000)
263         /* The only time we loop in here is when making a coaxial gear, and
264            trying to pick a radius that is either significantly larger or
265            smaller than its parent.  That shouldn't be hard, so something
266            must be really wrong if we can't do that in only a few tries.
267          */
268         abort();
269
270       /* Pick the size of the teeth.
271        */
272       if (parent && !coaxial_p) /* adjascent gears need matching teeth */
273         {
274           g->tooth_w = parent->tooth_w;
275           g->tooth_h = parent->tooth_h;
276           g->thickness  = parent->thickness;
277           g->thickness2 = parent->thickness2;
278           g->thickness3 = parent->thickness3;
279         }
280       else                 /* gears that begin trains get any size they want */
281         {
282           double scale = (1.0 + BELLRAND(4.0)) * gear_size;
283           g->tooth_w = 0.007 * scale;
284           g->tooth_h = 0.005 * scale;
285           g->thickness  = g->tooth_h * (0.1 + BELLRAND(1.5));
286           g->thickness2 = g->thickness / 4;
287           g->thickness3 = g->thickness;
288         }
289
290       /* Pick the number of teeth, and thus, the radius.
291        */
292       {
293         double c;
294
295       AGAIN:
296         g->nteeth = 3 + (random() % 97);    /* from 3 to 100 teeth */
297
298         if (g->nteeth < 7 && (random() % 5) != 0)
299           goto AGAIN;   /* Let's make very small tooth-counts more rare */
300
301         c = g->nteeth * g->tooth_w * 2;     /* circumference = teeth + gaps */
302         g->r = c / (M_PI * 2);              /* c = 2 pi r  */
303       }
304
305
306       /* Are we done now?
307        */
308       if (! coaxial_p) break;   /* yes */
309       if (g->nteeth == parent->nteeth) continue; /* ugly */
310       if (g->r  < parent->r * 0.6) break;  /* g much smaller than parent */
311       if (parent->r < g->r  * 0.6) break;  /* g much larger than parent  */
312     }
313
314   /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */
315
316   /* Colorize
317    */
318   g->color[0] = 0.5 + frand(0.5);
319   g->color[1] = 0.5 + frand(0.5);
320   g->color[2] = 0.5 + frand(0.5);
321   g->color[3] = 1.0;
322
323   g->color2[0] = g->color[0] * 0.85;
324   g->color2[1] = g->color[1] * 0.85;
325   g->color2[2] = g->color[2] * 0.85;
326   g->color2[3] = g->color[3];
327
328
329   /* Decide on shape of gear interior:
330      - just a ring with teeth;
331      - that, plus a thinner in-set "plate" in the middle;
332      - that, plus a thin raised "lip" on the inner plate;
333      - or, a wide lip (really, a thicker third inner plate.)
334    */
335   if ((random() % 10) == 0)
336     {
337       /* inner_r can go all the way in; there's no inset disc. */
338       g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
339       g->inner_r2 = 0;
340       g->inner_r3 = 0;
341     }
342   else
343     {
344       /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
345       g->inner_r  = (g->r * 0.5)  + frand((g->r - g->tooth_h) * 0.4);
346       g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
347       g->inner_r3 = 0;
348
349       if (g->inner_r2 > (g->r * 0.2))
350         {
351           int nn = (random() % 10);
352           if (nn <= 2)
353             g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
354           else if (nn <= 7 && g->inner_r2 >= 0.1)
355             g->inner_r3 = g->inner_r2 - 0.01;
356         }
357     }
358
359   /* Coaxial gears need to have the same innermost hole size (for the axle.)
360      Use whichever of the two is smaller.  (Modifies parent.)
361    */
362   if (coaxial_p)
363     {
364       double hole1 = (g->inner_r3 ? g->inner_r3 :
365                       g->inner_r2 ? g->inner_r2 :
366                       g->inner_r);
367       double hole2 = (parent->inner_r3 ? parent->inner_r3 :
368                       parent->inner_r2 ? parent->inner_r2 :
369                       parent->inner_r);
370       double hole = (hole1 < hole2 ? hole1 : hole2);
371       if (hole <= 0) abort();
372
373       if      (g->inner_r3) g->inner_r3 = hole;
374       else if (g->inner_r2) g->inner_r2 = hole;
375       else                  g->inner_r  = hole;
376
377       if      (parent->inner_r3) parent->inner_r3 = hole;
378       else if (parent->inner_r2) parent->inner_r2 = hole;
379       else                       parent->inner_r  = hole;
380     }
381
382   /* If we have three discs, sometimes make the middle disc be spokes.
383    */
384   if (g->inner_r3 && ((random() % 5) == 0))
385     {
386       g->spokes = 2 + BELLRAND (5);
387       g->spoke_thickness = 1 + frand(7.0);
388       if (g->spokes == 2 && g->spoke_thickness < 2)
389         g->spoke_thickness += 1;
390     }
391
392   /* Sometimes add little nubbly bits, if there is room.
393    */
394   if (g->nteeth > 5)
395     {
396       double size = 0;
397       involute_biggest_ring (g, 0, &size, 0);
398       if (size > g->r * 0.2 && (random() % 5) == 0)
399         {
400           g->nubs = 1 + (random() % 16);
401           if (g->nubs > 8) g->nubs = 1;
402         }
403     }
404
405   if (g->inner_r3 > g->inner_r2) abort();
406   if (g->inner_r2 > g->inner_r) abort();
407   if (g->inner_r  > g->r) abort();
408
409   /* Decide how complex the polygon model should be.
410    */
411   {
412     double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
413     if (pix <= 2.5)      g->size = INVOLUTE_SMALL;
414     else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
415     else                 g->size = INVOLUTE_LARGE;
416   }
417
418   g->base_p = !parent;
419
420   return g;
421 }
422
423
424 /* Given a newly-created gear, place it next to its parent in the scene,
425    with its teeth meshed and the proper velocity.  Returns False if it
426    didn't work.  (Call this a bunch of times until either it works, or
427    you decide it's probably not going to.)
428  */
429 static Bool
430 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
431 {
432   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
433
434   /* If this gear takes up more than 1/3rd of the screen, it's no good.
435    */
436   if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
437       ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
438     {
439       if (verbose_p && debug_placement_p && 0)
440         fprintf (stderr,
441                  "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
442                  progname,
443                  (g->r + g->tooth_h), gear_size,
444                  pp->vp_width, pp->vp_height);
445       pp->debug_size_failures++;
446       return False;
447     }
448
449   /* Compute this gear's velocity.
450    */
451   if (! parent)
452     {
453       g->ratio = 0.8 + BELLRAND(0.4);  /* 0.8-1.2 = 8-12rpm @ 60fps */
454       g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
455     }
456   else if (coaxial_p)
457     {
458       g->ratio = parent->ratio; /* bound gears have the same ratio */
459       g->th = parent->th;
460       g->rpm = parent->rpm;
461       g->wobble = parent->wobble;
462     }
463   else
464     {
465       /* Gearing ratio is the ratio of the number of teeth to previous gear
466          (which is also the ratio of the circumferences.)
467        */
468       g->ratio = (double) parent->nteeth / (double) g->nteeth;
469
470       /* Set our initial rotation to match that of the previous gear,
471          multiplied by the gearing ratio.  (This is finessed later,
472          once we know the exact position of the gear relative to its
473          parent.)
474       */
475       g->th = -(parent->th * g->ratio);
476
477       if (g->nteeth & 1)    /* rotate 1/2 tooth-size if odd number of teeth */
478         {
479           double off = (180.0 / g->nteeth);
480           if (g->th > 0)
481             g->th += off;
482           else
483             g->th -= off;
484         }
485
486       /* ratios are cumulative for all gears in the train. */
487       g->ratio *= parent->ratio;
488     }
489
490
491   /* Place the gear relative to the parent.
492    */
493
494   if (! parent)
495     {
496       gear *rg = farthest_gear (mi, False);
497       double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
498       if (right < pp->layout_left) /* place off screen */
499         right = pp->layout_left;
500
501       g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
502       g->y = 0;
503       g->z = 0;
504
505       if (debug_one_gear_p)
506         g->x = 0;
507     }
508   else if (coaxial_p)
509     {
510       double off = pp->plane_displacement;
511
512       g->x = parent->x;
513       g->y = parent->y;
514       g->z = parent->z + (g->r > parent->r      /* small gear on top */
515                           ? -off : off);
516
517       if (parent->r > g->r)     /* mark which is top and which is bottom */
518         {
519           parent->coax_p = 1;
520           g->coax_p      = 2;
521           parent->wobble = 0;   /* looks bad when axle moves */
522         }
523       else
524         {
525           parent->coax_p = 2;
526           g->coax_p      = 1;
527           g->wobble      = 0;
528         }
529
530       g->coax_thickness      = parent->thickness;
531       parent->coax_thickness = g->thickness;
532
533       /* Don't let the train get too close to or far from the screen.
534          If we're getting too close, give up on this gear.
535          (But getting very far away is fine.)
536        */
537       if (g->z >=  off * 4 ||
538           g->z <= -off * 4)
539         {
540           if (verbose_p && debug_placement_p)
541             fprintf (stderr, "%s: placement: bad depth: %.2f\n",
542                      progname, g->z);
543           pp->debug_position_failures++;
544           return False;
545         }
546     }
547   else                          /* position it somewhere next to the parent. */
548     {
549       double r_off = parent->r + g->r;
550       int angle;
551
552       if ((random() % 3) != 0)
553         angle = (random() % 240) - 120;   /* mostly -120 to +120 degrees */
554       else
555         angle = (random() % 360) - 180;   /* sometimes -180 to +180 degrees */
556
557       g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
558       g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
559       g->z = parent->z;
560
561       /* If the angle we picked would have positioned this gear
562          more than halfway off screen, that's no good. */
563       if (g->y > pp->vp_top ||
564           g->y < pp->vp_bottom)
565         {
566           if (verbose_p && debug_placement_p)
567             fprintf (stderr, "%s: placement: out of bounds: %s\n",
568                      progname, (g->y > pp->vp_top ? "top" : "bottom"));
569           pp->debug_position_failures++;
570           return False;
571         }
572
573       /* avoid accidentally changing sign of "th" in the math below. */
574       g->th += (g->th > 0 ? 360 : -360);
575
576       /* Adjust the rotation of the gear so that its teeth line up with its
577          parent, based on the position of the gear and the current rotation
578          of the parent.
579        */
580       {
581         double p_c = 2 * M_PI * parent->r;  /* circumference of parent */
582         double g_c = 2 * M_PI * g->r;       /* circumference of g  */
583
584         double p_t = p_c * (angle/360.0);   /* distance travelled along
585                                                circumference of parent when
586                                                moving "angle" degrees along
587                                                parent. */
588         double g_rat = p_t / g_c;           /* if travelling that distance
589                                                along circumference of g,
590                                                ratio of g's circumference
591                                                travelled. */
592         double g_th = 360.0 * g_rat;        /* that ratio in degrees */
593
594         g->th += angle + g_th;
595       }
596     }
597
598   if (debug_one_gear_p)
599     {
600       compute_rpm (mi, g);
601       return True;
602     }
603
604   /* If the position we picked for this gear would cause it to already
605      be visible on the screen, give up.  This can happen when the train
606      is growing backwards, and we don't want to see gears flash into
607      existence.
608    */
609   if (g->x - g->r - g->tooth_h < pp->render_right)
610     {
611       if (verbose_p && debug_placement_p)
612         fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
613       pp->debug_position_failures++;
614       return False;
615     }
616
617   /* If the position we picked for this gear causes it to overlap
618      with any earlier gear in the train, give up.
619    */
620   {
621     int i;
622
623     for (i = pp->ngears-1; i >= 0; i--)
624       {
625         gear *og = pp->gears[i];
626
627         if (og == g) continue;
628         if (og == parent) continue;
629         if (g->z != og->z) continue;    /* Ignore unless on same layer */
630
631         /* Collision detection without sqrt:
632              d = sqrt(a^2 + b^2)   d^2 = a^2 + b^2
633              d < r1 + r2           d^2 < (r1 + r2)^2
634          */
635         if (((g->x - og->x) * (g->x - og->x) +
636              (g->y - og->y) * (g->y - og->y)) <
637             ((g->r + g->tooth_h + og->r + og->tooth_h) *
638              (g->r + g->tooth_h + og->r + og->tooth_h)))
639           {
640             if (verbose_p && debug_placement_p)
641               fprintf (stderr, "%s: placement: collision with %lu\n",
642                        progname, og->id);
643             pp->debug_position_failures++;
644             return False;
645           }
646       }
647   }
648
649   compute_rpm (mi, g);
650
651
652   /* Make deeper gears be darker.
653    */
654   {
655     double depth = g->z / pp->plane_displacement;
656     double brightness = 1 + (depth / 6);
657     double limit = 0.4;
658     if (brightness < limit)   brightness = limit;
659     if (brightness > 1/limit) brightness = 1/limit;
660     g->color[0]  *= brightness;
661     g->color[1]  *= brightness;
662     g->color[2]  *= brightness;
663     g->color2[0] *= brightness;
664     g->color2[1] *= brightness;
665     g->color2[2] *= brightness;
666   }
667
668   /* If a single frame of animation would cause the gear to rotate by
669      more than 1/2 the size of a single tooth, then it won't look right:
670      the gear will appear to be turning at some lower harmonic of its
671      actual speed.
672    */
673   {
674     double ratio = g->ratio * spin_speed;
675     double blur_limit = 180.0 / g->nteeth;
676
677     if (ratio > blur_limit)
678       g->motion_blur_p = 1;
679
680     if (!coaxial_p)
681       {
682         /* ride until the wheels fall off... */
683         if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
684         if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
685         if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
686         if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
687         if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
688         if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
689       }
690   }
691
692   return True;
693 }
694
695 static void
696 free_gear (gear *g)
697 {
698   if (g->dlist)
699     glDeleteLists (g->dlist, 1);
700   free (g);
701 }
702
703
704 /* Make a new gear, place it next to its parent in the scene,
705    with its teeth meshed and the proper velocity.  Returns the gear;
706    or 0 if it didn't work.  (Call this a bunch of times until either
707    it works, or you decide it's probably not going to.)
708  */
709 static gear *
710 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
711 {
712   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
713   int loop_count = 0;
714   gear *g = 0;
715
716   while (1)
717     {
718       loop_count++;
719       if (loop_count >= 100)
720         {
721           if (g)
722             free_gear (g);
723           g = 0;
724           break;
725         }
726
727       g = new_gear (mi, parent, coaxial_p);
728       if (!g) return 0;  /* out of memory? */
729
730       if (place_gear (mi, g, parent, coaxial_p))
731         break;
732     }
733
734   if (! g) return 0;
735
736   /* We got a gear, and it is properly positioned.
737      Insert it in the scene.
738    */
739   if (pp->ngears + 2 >= pp->gears_size)
740     {
741       pp->gears_size += 100;
742       pp->gears = (gear **) realloc (pp->gears,
743                                      pp->gears_size * sizeof (*pp->gears));
744       if (! pp->gears)
745         {
746           fprintf (stderr, "%s: out of memory (%d gears)\n",
747                    progname, pp->gears_size);
748         }
749     }
750
751   pp->gears[pp->ngears++] = g;
752   return g;
753 }
754
755
756 static void delete_gear (ModeInfo *mi, gear *g);
757
758 static void
759 push_gear (ModeInfo *mi)
760 {
761   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
762   gear *g;
763   gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
764
765   Bool tried_coaxial_p = False;
766   Bool coaxial_p = False;
767   Bool last_ditch_coax_p = False;
768   int loop_count = 0;
769
770   pp->debug_size_failures = 0;
771   pp->debug_position_failures = 0;
772
773  AGAIN:
774   loop_count++;
775   if (loop_count > 100) abort();  /* we're doomed! */
776   
777   g = 0;
778
779   /* If the gears are turning at LUDICROUS SPEED, unhook the train to
780      reset things to a sane velocity.
781
782      10,000 RPM at 30 FPS = 5.5 rotations per frame.
783       1,000 RPM at 30 FPS = 0.5 rotations per frame.
784         600 RPM at 30 FPS =  3 frames per rotation.
785         200 RPM at 30 FPS =  9 frames per rotation.
786         100 RPM at 30 FPS = 18 frames per rotation.
787          50 RPM at 30 FPS = 36 frames per rotation.
788          10 RPM at 30 FPS =  3 sec per rotation.
789           1 RPM at 30 FPS = 30 sec per rotation.
790          .5 RPM at 30 FPS =  1 min per rotation.
791          .1 RPM at 30 FPS =  5 min per rotation.
792    */
793   if (parent && parent->rpm > max_rpm)
794     {
795       if (verbose_p)
796         {
797           char buf[100];
798           rpm_string (parent->rpm, buf);
799           fprintf (stderr, "%s: ludicrous speed!  %s\n\n", progname, buf);
800         }
801       parent = 0;
802     }
803
804   /* If the last N gears we've placed have all been motion-blurred, then
805      it's a safe guess that we've wandered off into the woods and aren't
806      coming back.  Bail on this train.
807    */
808   if (pp->current_blur_length >= 10)
809     {
810       if (verbose_p)
811         fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
812       parent = 0;
813     }
814
815
816
817   /* Sometimes, try to make a coaxial gear.
818    */
819   if (parent && !parent->coax_p && (random() % 40) == 0)
820     {
821       tried_coaxial_p = True;
822       coaxial_p = True;
823       g = place_new_gear (mi, parent, coaxial_p);
824     }
825
826   /* Try to make a regular gear.
827    */
828   if (!g)
829     {
830       coaxial_p = False;
831       g = place_new_gear (mi, parent, coaxial_p);
832     }
833
834   /* If we couldn't make a regular gear, then try to make a coxial gear
835      (unless we already tried that.)
836    */
837   if (!g && !tried_coaxial_p && parent && !parent->coax_p)
838     {
839       tried_coaxial_p = True;
840       coaxial_p = True;
841       g = place_new_gear (mi, parent, coaxial_p);
842       if (g)
843         last_ditch_coax_p = True;
844     }
845
846   /* If we couldn't do that either, then the train has hit a dead end:
847      start a new train.
848    */
849   if (!g)
850     {
851       coaxial_p = False;
852       if (verbose_p)
853         fprintf (stderr, "%s: dead end!\n\n", progname);
854       parent = 0;
855       g = place_new_gear (mi, parent, coaxial_p);
856     }
857
858   if (! g)
859     {
860       /* Unable to make/place any gears at all!
861          This can happen if we've backed ourself into a corner very near
862          the top-right or bottom-right corner of the growth zone.
863          It's time to add a gear, but there's no room to add one!
864          In that case, let's just wipe all the gears that are in the
865          growth zone and try again.
866        */
867       int i;
868
869       if (verbose_p && debug_placement_p)
870         fprintf (stderr,
871                  "%s: placement: resetting growth zone!  "
872                  "failed: %d size, %d pos\n",
873                  progname,
874                  pp->debug_size_failures, pp->debug_position_failures);
875       for (i = pp->ngears-1; i >= 0; i--)
876         {
877           gear *g = pp->gears[i];
878           if (g->x - g->r - g->tooth_h < pp->render_left)
879             delete_gear (mi, g);
880         }
881       goto AGAIN;
882     }
883
884   if (g->coax_p)
885     {
886       if (!parent) abort();
887       if (g->x != parent->x) abort();
888       if (g->y != parent->y) abort();
889       if (g->z == parent->z) abort();
890       if (g->r == parent->r) abort();
891       if (g->th != parent->th) abort();
892       if (g->ratio != parent->ratio) abort();
893       if (g->rpm != parent->rpm) abort();
894     }
895
896   if (verbose_p)
897     {
898       fprintf (stderr, "%s: %5lu ", progname, g->id);
899
900       fputc ((g->motion_blur_p ? '*' : ' '), stderr);
901       fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
902               g->coax_p ? '1' : ' '),
903              stderr);
904
905       fprintf (stderr, " %2d%%",
906                (int) (g->r * 2 * 100 / pp->vp_height));
907       fprintf (stderr, "  %2d teeth", (int) g->nteeth);
908       fprintf (stderr, " %3.0f rpm;", g->rpm);
909
910       {
911         char buf1[50], buf2[50], buf3[100];
912         *buf1 = 0; *buf2 = 0; *buf3 = 0;
913         if (pp->debug_size_failures)
914           sprintf (buf1, "%3d sz", pp->debug_size_failures);
915         if (pp->debug_position_failures)
916           sprintf (buf2, "%2d pos", pp->debug_position_failures);
917         if (*buf1 || *buf2)
918           sprintf (buf3, " tries: %-7s%s", buf1, buf2);
919         fprintf (stderr, "%-21s", buf3);
920       }
921
922       if (g->base_p) fprintf (stderr, " RESET %lu", pp->current_length);
923       fprintf (stderr, "\n");
924     }
925
926   if (g->base_p)
927     pp->current_length = 1;
928   else
929     pp->current_length++;
930
931   if (g->motion_blur_p)
932     pp->current_blur_length++;
933   else
934     pp->current_blur_length = 0;
935 }
936
937
938
939 /* Remove the given gear from the scene and free it.
940  */
941 static void
942 delete_gear (ModeInfo *mi, gear *g)
943 {
944   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
945   int i;
946
947   for (i = 0; i < pp->ngears; i++)      /* find this gear in the list */
948     if (pp->gears[i] == g) break;
949   if (pp->gears[i] != g) abort();
950
951   for (; i < pp->ngears-1; i++)         /* pull later entries forward */
952     pp->gears[i] = pp->gears[i+1];
953   pp->gears[i] = 0;
954   pp->ngears--;
955   free_gear (g);
956 }
957
958
959 /* Update the position of each gear in the scene.
960  */
961 static void
962 scroll_gears (ModeInfo *mi)
963 {
964   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
965   int i;
966
967   for (i = 0; i < pp->ngears; i++)
968     pp->gears[i]->x -= (scroll_speed * 0.002);
969
970   /* if the right edge of any gear is off screen to the left, delete it.
971    */
972   for (i = pp->ngears-1; i >= 0; i--)
973     {
974       gear *g = pp->gears[i];
975       if (g->x + g->r + g->tooth_h < pp->render_left)
976         delete_gear (mi, g);
977     }
978
979   /* if the right edge of the last-added gear is left of the right edge
980      of the layout area, add another gear.
981    */
982   i = 0;
983   while (1)
984     {
985       gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
986       if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
987         push_gear (mi);
988       else
989         break;
990       i++;
991       if (debug_one_gear_p) break;
992     }
993
994   /*
995   if (i > 1 && verbose_p)
996     fprintf (stderr, "%s: pushed %d gears\n", progname, i);
997    */
998 }
999
1000
1001 /* Update the rotation of each gear in the scene.
1002  */
1003 static void
1004 spin_gears (ModeInfo *mi)
1005 {
1006   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1007   int i;
1008
1009   for (i = 0; i < pp->ngears; i++)
1010     {
1011       gear *g = pp->gears[i];
1012       double off = (g->ratio * spin_speed);
1013
1014       if (g->th > 0)
1015         g->th += off;
1016       else
1017         g->th -= off;
1018     }
1019 }
1020
1021
1022 /* Run the animation fast (without displaying anything) until the first
1023    gear is just about to come on screen.  This is to avoid a big delay
1024    with a blank screen when -scroll is low.
1025  */
1026 static void
1027 ffwd (ModeInfo *mi)
1028 {
1029   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1030   if (debug_one_gear_p) return;
1031   while (1)
1032     {
1033       gear *g = farthest_gear (mi, True);
1034       if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1035         break;
1036       scroll_gears (mi);
1037     }
1038 }
1039
1040
1041 \f
1042 /* Render one gear in the proper position, creating the gear's
1043    display list first if necessary.
1044  */
1045 static void
1046 draw_gear (ModeInfo *mi, int which)
1047 {
1048   Bool wire_p = MI_IS_WIREFRAME(mi);
1049   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1050   gear *g = pp->gears[which];
1051   GLfloat th;
1052
1053   Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
1054                     g->x - g->r - g->tooth_h <= pp->render_right);
1055
1056   if (!visible_p && !debug_p)
1057     return;
1058
1059   if (! g->dlist)
1060     {
1061       g->dlist = glGenLists (1);
1062       if (! g->dlist)
1063         {
1064           /* I don't know how many display lists a GL implementation
1065              is supposed to provide, but hopefully it's more than
1066              "a few hundred", or we'll be in trouble...
1067            */
1068           check_gl_error ("glGenLists");
1069           abort();
1070         }
1071
1072       glNewList (g->dlist, GL_COMPILE);
1073       g->polygons = draw_involute_gear (g, (wire_p && debug_p ? 2 : wire_p));
1074       glEndList ();
1075     }
1076
1077   glPushMatrix();
1078
1079   glTranslatef (g->x, g->y, g->z);
1080
1081   if (g->motion_blur_p && !pp->button_down_p)
1082     {
1083       /* If we're in motion-blur mode, then draw the gear so that each
1084          frame rotates it by exactly one half tooth-width, so that it
1085          looks flickery around the edges.  But, revert to the normal
1086          way when the mouse button is down lest the user see overlapping
1087          polygons.
1088        */
1089       th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
1090       g->motion_blur_p++;
1091     }
1092   else
1093     th = g->th;
1094
1095   glRotatef (th, 0, 0, 1);
1096
1097   glPushName (g->id);
1098
1099   if (! visible_p)
1100     mi->polygon_count += draw_involute_schematic (g, wire_p);
1101   else
1102     {
1103       glCallList (g->dlist);
1104       mi->polygon_count += g->polygons;
1105     }
1106
1107   glPopName();
1108   glPopMatrix();
1109 }
1110
1111
1112 /* Render all gears.
1113  */
1114 static void
1115 draw_gears (ModeInfo *mi)
1116 {
1117   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1118   Bool wire_p = MI_IS_WIREFRAME(mi);
1119   int i;
1120
1121   glColor4f (1, 1, 0.8, 1);
1122
1123   glInitNames();
1124
1125   for (i = 0; i < pp->ngears; i++)
1126     draw_gear (mi, i);
1127
1128   /* draw a line connecting gears that are, uh, geared. */
1129   if (debug_p)
1130     {
1131       static const GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
1132       GLfloat off = 0.1;
1133       GLfloat ox=0, oy=0, oz=0;
1134
1135       if (!wire_p) glDisable(GL_LIGHTING);
1136       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
1137       glColor3f (color[0], color[1], color[2]);
1138
1139       for (i = 0; i < pp->ngears; i++)
1140         {
1141           gear *g = pp->gears[i];
1142           glBegin(GL_LINE_STRIP);
1143           glVertex3f (g->x, g->y, g->z - off);
1144           glVertex3f (g->x, g->y, g->z + off);
1145           if (i > 0 && !g->base_p)
1146             glVertex3f (ox, oy, oz + off);
1147           glEnd();
1148           ox = g->x;
1149           oy = g->y;
1150           oz = g->z;
1151         }
1152       if (!wire_p) glEnable(GL_LIGHTING);
1153     }
1154 }
1155
1156
1157 /* Mouse hit detection
1158  */
1159 static void
1160 find_mouse_gear (ModeInfo *mi)
1161 {
1162   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1163   int screen_width = MI_WIDTH (mi);
1164   int screen_height = MI_HEIGHT (mi);
1165   GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
1166   int x, y;
1167   int hits;
1168
1169   pp->mouse_gear_id = 0;
1170
1171   /* Poll mouse position */
1172   {
1173     Window r, c;
1174     int rx, ry;
1175     unsigned int m;
1176     XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
1177                    &r, &c, &rx, &ry, &x, &y, &m);
1178   }
1179
1180   if (x < 0 || y < 0 || x > screen_width || y > screen_height)
1181     return;  /* out of window */
1182
1183   /* Run OpenGL hit detector */
1184   {
1185     GLint vp[4];
1186     GLuint selbuf[512];
1187
1188     glSelectBuffer (sizeof(selbuf), selbuf);  /* set up "select" mode */
1189     glRenderMode (GL_SELECT);
1190     glMatrixMode (GL_PROJECTION);
1191     glPushMatrix();
1192     glLoadIdentity();
1193     glGetIntegerv (GL_VIEWPORT, vp);         /* save old vp */
1194     gluPickMatrix (x, vp[3]-y, 5, 5, vp);
1195     gluPerspective (30.0, 1/h, 1.0, 100.0);  /* must match reshape_pinion() */
1196     glMatrixMode (GL_MODELVIEW);
1197
1198     draw_gears (mi);                         /* render into "select" buffer */
1199
1200     glMatrixMode (GL_PROJECTION);            /* restore old vp */
1201     glPopMatrix ();
1202     glMatrixMode (GL_MODELVIEW);
1203     glFlush();
1204     hits = glRenderMode (GL_RENDER);         /* done selecting */
1205
1206     if (hits > 0)
1207       {
1208         int i;
1209         GLuint name_count = 0;
1210         GLuint *p = (GLuint *) selbuf;
1211         GLuint *pnames = 0;
1212         GLuint min_z = ~0;
1213
1214         for (i = 0; i < hits; i++)
1215           {     
1216             int names = *p++;
1217             if (*p < min_z)                  /* find match closest to screen */
1218               {
1219                 name_count = names;
1220                 min_z = *p;
1221                 pnames = p+2;
1222               }
1223             p += names+2;
1224           }
1225
1226         if (name_count > 0)                  /* take first hit */
1227           pp->mouse_gear_id = pnames[0];
1228       }
1229   }
1230 }
1231
1232
1233 /* Window management, etc
1234  */
1235 ENTRYPOINT void
1236 reshape_pinion (ModeInfo *mi, int width, int height)
1237 {
1238   GLfloat h = (GLfloat) height / (GLfloat) width;
1239   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1240
1241   glViewport (0, 0, (GLint) width, (GLint) height);
1242
1243   glMatrixMode(GL_PROJECTION);
1244   glLoadIdentity();
1245   gluPerspective (30.0, 1/h, 1.0, 100.0);
1246
1247   glMatrixMode(GL_MODELVIEW);
1248   glLoadIdentity();
1249   gluLookAt( 0.0, 0.0, 30.0,
1250              0.0, 0.0, 0.0,
1251              0.0, 1.0, 0.0);
1252
1253   glClear(GL_COLOR_BUFFER_BIT);
1254
1255   {
1256     GLfloat render_width, layout_width;
1257     pp->vp_height = 1.0;
1258     pp->vp_width  = 1/h;
1259
1260     pp->vp_left   = -pp->vp_width/2;
1261     pp->vp_right  =  pp->vp_width/2;
1262     pp->vp_top    =  pp->vp_height/2;
1263     pp->vp_bottom = -pp->vp_height/2;
1264
1265     render_width = pp->vp_width * 2;
1266     layout_width = pp->vp_width * 0.8 * gear_size;
1267
1268     pp->render_left  = -render_width/2;
1269     pp->render_right =  render_width/2;
1270
1271     pp->layout_left  = pp->render_right;
1272     pp->layout_right = pp->layout_left + layout_width;
1273   }
1274 }
1275
1276
1277 ENTRYPOINT Bool
1278 pinion_handle_event (ModeInfo *mi, XEvent *event)
1279 {
1280   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1281
1282   if (event->xany.type == ButtonPress &&
1283       event->xbutton.button == Button1)
1284     {
1285       pp->button_down_p = True;
1286       gltrackball_start (pp->trackball,
1287                          event->xbutton.x, event->xbutton.y,
1288                          MI_WIDTH (mi), MI_HEIGHT (mi));
1289       return True;
1290     }
1291   else if (event->xany.type == ButtonRelease &&
1292            event->xbutton.button == Button1)
1293     {
1294       pp->button_down_p = False;
1295       return True;
1296     }
1297   else if (event->xany.type == ButtonPress &&
1298            (event->xbutton.button == Button4 ||
1299             event->xbutton.button == Button5 ||
1300             event->xbutton.button == Button6 ||
1301             event->xbutton.button == Button7))
1302     {
1303       gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
1304                               !!event->xbutton.state);
1305       return True;
1306     }
1307   else if (event->xany.type == MotionNotify &&
1308            pp->button_down_p)
1309     {
1310       gltrackball_track (pp->trackball,
1311                          event->xmotion.x, event->xmotion.y,
1312                          MI_WIDTH (mi), MI_HEIGHT (mi));
1313       return True;
1314     }
1315   else if (event->xany.type == KeyPress)
1316     {
1317       KeySym keysym;
1318       char c = 0;
1319       XLookupString (&event->xkey, &c, 1, &keysym, 0);
1320       if (c == ' ' && debug_one_gear_p && pp->ngears)
1321         {
1322           delete_gear (mi, pp->gears[0]);
1323           return True;
1324         }
1325     }
1326
1327   return False;
1328 }
1329
1330
1331 ENTRYPOINT void 
1332 init_pinion (ModeInfo *mi)
1333 {
1334   pinion_configuration *pp;
1335   int wire = MI_IS_WIREFRAME(mi);
1336
1337   if (!pps) {
1338     pps = (pinion_configuration *)
1339       calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
1340     if (!pps) {
1341       fprintf(stderr, "%s: out of memory\n", progname);
1342       exit(1);
1343     }
1344   }
1345
1346   pp = &pps[MI_SCREEN(mi)];
1347
1348   pp->glx_context = init_GL(mi);
1349
1350   load_fonts (mi);
1351   reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1352
1353   pp->title_list  = glGenLists (1);
1354
1355   pp->ngears = 0;
1356   pp->gears_size = 0;
1357   pp->gears = 0;
1358
1359   pp->plane_displacement = gear_size * 0.1;
1360
1361   if (!wire)
1362     {
1363       GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
1364       GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
1365       GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
1366       GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
1367
1368       glEnable(GL_LIGHTING);
1369       glEnable(GL_LIGHT0);
1370       glEnable(GL_DEPTH_TEST);
1371       glEnable(GL_CULL_FACE);
1372
1373       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1374       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1375       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1376       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1377     }
1378
1379   pp->trackball = gltrackball_init ();
1380
1381   ffwd (mi);
1382 }
1383
1384
1385 ENTRYPOINT void
1386 draw_pinion (ModeInfo *mi)
1387 {
1388   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1389   Display *dpy = MI_DISPLAY(mi);
1390   Window window = MI_WINDOW(mi);
1391   Bool wire_p = MI_IS_WIREFRAME(mi);
1392
1393   if (!pp->glx_context)
1394     return;
1395
1396   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
1397
1398   if (!pp->button_down_p)
1399     {
1400       if (!debug_one_gear_p || pp->ngears == 0)
1401         scroll_gears (mi);
1402       spin_gears (mi);
1403     }
1404
1405   glShadeModel(GL_SMOOTH);
1406
1407   glEnable(GL_DEPTH_TEST);
1408   glEnable(GL_NORMALIZE);
1409   glEnable(GL_CULL_FACE);
1410
1411   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1412
1413   glPushMatrix ();
1414   {
1415     gltrackball_rotate (pp->trackball);
1416     mi->polygon_count = 0;
1417
1418     glScalef (16, 16, 16);   /* map vp_width/height to the screen */
1419
1420     if (debug_one_gear_p)    /* zoom in */
1421       glScalef (3, 3, 3);
1422     else if (debug_p)        /* show the "visible" and "layout" areas */
1423       {
1424         GLfloat ow = pp->layout_right - pp->render_left;
1425         GLfloat rw = pp->render_right - pp->render_left;
1426         GLfloat s = (pp->vp_width / ow) * 0.85;
1427         glScalef (s, s, s);
1428         glTranslatef (-(ow - rw) / 2, 0, 0);
1429       }
1430     else
1431       {
1432         GLfloat s = 1.2;
1433         glScalef (s, s, s);           /* zoom in a little more */
1434         glRotatef (-35, 1, 0, 0);     /* tilt back */
1435         glRotatef (  8, 0, 1, 0);     /* tilt left */
1436         glTranslatef (0.02, 0.1, 0);  /* pan up */
1437       }
1438
1439     draw_gears (mi);
1440
1441     if (debug_p)
1442       {
1443         if (!wire_p) glDisable(GL_LIGHTING);
1444         glColor3f (0.6, 0, 0);
1445         glBegin(GL_LINE_LOOP);
1446         glVertex3f (pp->render_left,  pp->vp_top,    0);
1447         glVertex3f (pp->render_right, pp->vp_top,    0);
1448         glVertex3f (pp->render_right, pp->vp_bottom, 0);
1449         glVertex3f (pp->render_left,  pp->vp_bottom, 0);
1450         glEnd();
1451         glColor3f (0.4, 0, 0);
1452         glBegin(GL_LINES);
1453         glVertex3f (pp->vp_left,      pp->vp_top,    0);
1454         glVertex3f (pp->vp_left,      pp->vp_bottom, 0);
1455         glVertex3f (pp->vp_right,     pp->vp_top,    0);
1456         glVertex3f (pp->vp_right,     pp->vp_bottom, 0);
1457         glEnd();
1458         glColor3f (0, 0.4, 0);
1459         glBegin(GL_LINE_LOOP);
1460         glVertex3f (pp->layout_left,  pp->vp_top,    0);
1461         glVertex3f (pp->layout_right, pp->vp_top,    0);
1462         glVertex3f (pp->layout_right, pp->vp_bottom, 0);
1463         glVertex3f (pp->layout_left,  pp->vp_bottom, 0);
1464         glEnd();
1465         if (!wire_p) glEnable(GL_LIGHTING);
1466       }
1467
1468     if (pp->draw_tick++ > 10)   /* only do this every N frames */
1469       {
1470         pp->draw_tick = 0;
1471         find_mouse_gear (mi);
1472         new_label (mi);
1473       }
1474   }
1475   glPopMatrix ();
1476
1477   glCallList (pp->title_list);
1478
1479   if (mi->fps_p) do_fps (mi);
1480   glFinish();
1481
1482   glXSwapBuffers(dpy, window);
1483 }
1484
1485 XSCREENSAVER_MODULE ("Pinion", pinion)
1486
1487 #endif /* USE_GL */