b18eb73cc5c1658fce1b1a24b9e70d4a1af0abd8
[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 "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);
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 (g->x != parent->x) abort();
887       if (g->y != parent->y) abort();
888       if (g->z == parent->z) abort();
889       if (g->r == parent->r) abort();
890       if (g->th != parent->th) abort();
891       if (g->ratio != parent->ratio) abort();
892       if (g->rpm != parent->rpm) abort();
893     }
894
895   if (verbose_p)
896     {
897       fprintf (stderr, "%s: %5lu ", progname, g->id);
898
899       fputc ((g->motion_blur_p ? '*' : ' '), stderr);
900       fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
901               g->coax_p ? '1' : ' '),
902              stderr);
903
904       fprintf (stderr, " %2d%%",
905                (int) (g->r * 2 * 100 / pp->vp_height));
906       fprintf (stderr, "  %2d teeth", (int) g->nteeth);
907       fprintf (stderr, " %3.0f rpm;", g->rpm);
908
909       {
910         char buf1[50], buf2[50], buf3[100];
911         *buf1 = 0; *buf2 = 0; *buf3 = 0;
912         if (pp->debug_size_failures)
913           sprintf (buf1, "%3d sz", pp->debug_size_failures);
914         if (pp->debug_position_failures)
915           sprintf (buf2, "%2d pos", pp->debug_position_failures);
916         if (*buf1 || *buf2)
917           sprintf (buf3, " tries: %-7s%s", buf1, buf2);
918         fprintf (stderr, "%-21s", buf3);
919       }
920
921       if (g->base_p) fprintf (stderr, " RESET %lu", pp->current_length);
922       fprintf (stderr, "\n");
923     }
924
925   if (g->base_p)
926     pp->current_length = 1;
927   else
928     pp->current_length++;
929
930   if (g->motion_blur_p)
931     pp->current_blur_length++;
932   else
933     pp->current_blur_length = 0;
934 }
935
936
937
938 /* Remove the given gear from the scene and free it.
939  */
940 static void
941 delete_gear (ModeInfo *mi, gear *g)
942 {
943   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
944   int i;
945
946   for (i = 0; i < pp->ngears; i++)      /* find this gear in the list */
947     if (pp->gears[i] == g) break;
948   if (pp->gears[i] != g) abort();
949
950   for (; i < pp->ngears-1; i++)         /* pull later entries forward */
951     pp->gears[i] = pp->gears[i+1];
952   pp->gears[i] = 0;
953   pp->ngears--;
954   free_gear (g);
955 }
956
957
958 /* Update the position of each gear in the scene.
959  */
960 static void
961 scroll_gears (ModeInfo *mi)
962 {
963   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
964   int i;
965
966   for (i = 0; i < pp->ngears; i++)
967     pp->gears[i]->x -= (scroll_speed * 0.002);
968
969   /* if the right edge of any gear is off screen to the left, delete it.
970    */
971   for (i = pp->ngears-1; i >= 0; i--)
972     {
973       gear *g = pp->gears[i];
974       if (g->x + g->r + g->tooth_h < pp->render_left)
975         delete_gear (mi, g);
976     }
977
978   /* if the right edge of the last-added gear is left of the right edge
979      of the layout area, add another gear.
980    */
981   i = 0;
982   while (1)
983     {
984       gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
985       if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
986         push_gear (mi);
987       else
988         break;
989       i++;
990       if (debug_one_gear_p) break;
991     }
992
993   /*
994   if (i > 1 && verbose_p)
995     fprintf (stderr, "%s: pushed %d gears\n", progname, i);
996    */
997 }
998
999
1000 /* Update the rotation of each gear in the scene.
1001  */
1002 static void
1003 spin_gears (ModeInfo *mi)
1004 {
1005   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1006   int i;
1007
1008   for (i = 0; i < pp->ngears; i++)
1009     {
1010       gear *g = pp->gears[i];
1011       double off = (g->ratio * spin_speed);
1012
1013       if (g->th > 0)
1014         g->th += off;
1015       else
1016         g->th -= off;
1017     }
1018 }
1019
1020
1021 /* Run the animation fast (without displaying anything) until the first
1022    gear is just about to come on screen.  This is to avoid a big delay
1023    with a blank screen when -scroll is low.
1024  */
1025 static void
1026 ffwd (ModeInfo *mi)
1027 {
1028   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1029   if (debug_one_gear_p) return;
1030   while (1)
1031     {
1032       gear *g = farthest_gear (mi, True);
1033       if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1034         break;
1035       scroll_gears (mi);
1036     }
1037 }
1038
1039
1040 \f
1041 /* Render one gear in the proper position, creating the gear's
1042    display list first if necessary.
1043  */
1044 static void
1045 draw_gear (ModeInfo *mi, int which)
1046 {
1047   Bool wire_p = MI_IS_WIREFRAME(mi);
1048   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1049   gear *g = pp->gears[which];
1050   GLfloat th;
1051
1052   Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
1053                     g->x - g->r - g->tooth_h <= pp->render_right);
1054
1055   if (!visible_p && !debug_p)
1056     return;
1057
1058   if (! g->dlist)
1059     {
1060       g->dlist = glGenLists (1);
1061       if (! g->dlist)
1062         {
1063           /* I don't know how many display lists a GL implementation
1064              is supposed to provide, but hopefully it's more than
1065              "a few hundred", or we'll be in trouble...
1066            */
1067           check_gl_error ("glGenLists");
1068           abort();
1069         }
1070
1071       glNewList (g->dlist, GL_COMPILE);
1072       g->polygons = draw_involute_gear (g, (wire_p && debug_p ? 2 : wire_p));
1073       glEndList ();
1074     }
1075
1076   glPushMatrix();
1077
1078   glTranslatef (g->x, g->y, g->z);
1079
1080   if (g->motion_blur_p && !pp->button_down_p)
1081     {
1082       /* If we're in motion-blur mode, then draw the gear so that each
1083          frame rotates it by exactly one half tooth-width, so that it
1084          looks flickery around the edges.  But, revert to the normal
1085          way when the mouse button is down lest the user see overlapping
1086          polygons.
1087        */
1088       th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
1089       g->motion_blur_p++;
1090     }
1091   else
1092     th = g->th;
1093
1094   glRotatef (th, 0, 0, 1);
1095
1096   glPushName (g->id);
1097
1098   if (! visible_p)
1099     mi->polygon_count += draw_involute_schematic (g, wire_p);
1100   else
1101     {
1102       glCallList (g->dlist);
1103       mi->polygon_count += g->polygons;
1104     }
1105
1106   glPopName();
1107   glPopMatrix();
1108 }
1109
1110
1111 /* Render all gears.
1112  */
1113 static void
1114 draw_gears (ModeInfo *mi)
1115 {
1116   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1117   Bool wire_p = MI_IS_WIREFRAME(mi);
1118   int i;
1119
1120   glColor4f (1, 1, 0.8, 1);
1121
1122   glInitNames();
1123
1124   for (i = 0; i < pp->ngears; i++)
1125     draw_gear (mi, i);
1126
1127   /* draw a line connecting gears that are, uh, geared. */
1128   if (debug_p)
1129     {
1130       static const GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
1131       GLfloat off = 0.1;
1132       GLfloat ox=0, oy=0, oz=0;
1133
1134       if (!wire_p) glDisable(GL_LIGHTING);
1135       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
1136       glColor3f (color[0], color[1], color[2]);
1137
1138       for (i = 0; i < pp->ngears; i++)
1139         {
1140           gear *g = pp->gears[i];
1141           glBegin(GL_LINE_STRIP);
1142           glVertex3f (g->x, g->y, g->z - off);
1143           glVertex3f (g->x, g->y, g->z + off);
1144           if (i > 0 && !g->base_p)
1145             glVertex3f (ox, oy, oz + off);
1146           glEnd();
1147           ox = g->x;
1148           oy = g->y;
1149           oz = g->z;
1150         }
1151       if (!wire_p) glEnable(GL_LIGHTING);
1152     }
1153 }
1154
1155
1156 /* Mouse hit detection
1157  */
1158 static void
1159 find_mouse_gear (ModeInfo *mi)
1160 {
1161   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1162   int screen_width = MI_WIDTH (mi);
1163   int screen_height = MI_HEIGHT (mi);
1164   GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
1165   int x, y;
1166   int hits;
1167
1168   pp->mouse_gear_id = 0;
1169
1170   /* Poll mouse position */
1171   {
1172     Window r, c;
1173     int rx, ry;
1174     unsigned int m;
1175     XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
1176                    &r, &c, &rx, &ry, &x, &y, &m);
1177   }
1178
1179   if (x < 0 || y < 0 || x > screen_width || y > screen_height)
1180     return;  /* out of window */
1181
1182   /* Run OpenGL hit detector */
1183   {
1184     GLint vp[4];
1185     GLuint selbuf[512];
1186
1187     glSelectBuffer (sizeof(selbuf), selbuf);  /* set up "select" mode */
1188     glRenderMode (GL_SELECT);
1189     glMatrixMode (GL_PROJECTION);
1190     glPushMatrix();
1191     glLoadIdentity();
1192     glGetIntegerv (GL_VIEWPORT, vp);         /* save old vp */
1193     gluPickMatrix (x, vp[3]-y, 5, 5, vp);
1194     gluPerspective (30.0, 1/h, 1.0, 100.0);  /* must match reshape_pinion() */
1195     glMatrixMode (GL_MODELVIEW);
1196
1197     draw_gears (mi);                         /* render into "select" buffer */
1198
1199     glMatrixMode (GL_PROJECTION);            /* restore old vp */
1200     glPopMatrix ();
1201     glMatrixMode (GL_MODELVIEW);
1202     glFlush();
1203     hits = glRenderMode (GL_RENDER);         /* done selecting */
1204
1205     if (hits > 0)
1206       {
1207         int i;
1208         GLuint name_count = 0;
1209         GLuint *p = (GLuint *) selbuf;
1210         GLuint *pnames = 0;
1211         GLuint min_z = ~0;
1212
1213         for (i = 0; i < hits; i++)
1214           {     
1215             int names = *p++;
1216             if (*p < min_z)                  /* find match closest to screen */
1217               {
1218                 name_count = names;
1219                 min_z = *p;
1220                 pnames = p+2;
1221               }
1222             p += names+2;
1223           }
1224
1225         if (name_count > 0)                  /* take first hit */
1226           pp->mouse_gear_id = pnames[0];
1227       }
1228   }
1229 }
1230
1231
1232 /* Window management, etc
1233  */
1234 ENTRYPOINT void
1235 reshape_pinion (ModeInfo *mi, int width, int height)
1236 {
1237   GLfloat h = (GLfloat) height / (GLfloat) width;
1238   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1239
1240   glViewport (0, 0, (GLint) width, (GLint) height);
1241
1242   glMatrixMode(GL_PROJECTION);
1243   glLoadIdentity();
1244   gluPerspective (30.0, 1/h, 1.0, 100.0);
1245
1246   glMatrixMode(GL_MODELVIEW);
1247   glLoadIdentity();
1248   gluLookAt( 0.0, 0.0, 30.0,
1249              0.0, 0.0, 0.0,
1250              0.0, 1.0, 0.0);
1251
1252   glClear(GL_COLOR_BUFFER_BIT);
1253
1254   {
1255     GLfloat render_width, layout_width;
1256     pp->vp_height = 1.0;
1257     pp->vp_width  = 1/h;
1258
1259     pp->vp_left   = -pp->vp_width/2;
1260     pp->vp_right  =  pp->vp_width/2;
1261     pp->vp_top    =  pp->vp_height/2;
1262     pp->vp_bottom = -pp->vp_height/2;
1263
1264     render_width = pp->vp_width * 2;
1265     layout_width = pp->vp_width * 0.8 * gear_size;
1266
1267     pp->render_left  = -render_width/2;
1268     pp->render_right =  render_width/2;
1269
1270     pp->layout_left  = pp->render_right;
1271     pp->layout_right = pp->layout_left + layout_width;
1272   }
1273 }
1274
1275
1276 ENTRYPOINT Bool
1277 pinion_handle_event (ModeInfo *mi, XEvent *event)
1278 {
1279   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1280
1281   if (event->xany.type == ButtonPress &&
1282       event->xbutton.button == Button1)
1283     {
1284       pp->button_down_p = True;
1285       gltrackball_start (pp->trackball,
1286                          event->xbutton.x, event->xbutton.y,
1287                          MI_WIDTH (mi), MI_HEIGHT (mi));
1288       return True;
1289     }
1290   else if (event->xany.type == ButtonRelease &&
1291            event->xbutton.button == Button1)
1292     {
1293       pp->button_down_p = False;
1294       return True;
1295     }
1296   else if (event->xany.type == ButtonPress &&
1297            (event->xbutton.button == Button4 ||
1298             event->xbutton.button == Button5))
1299     {
1300       gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
1301                               !!event->xbutton.state);
1302       return True;
1303     }
1304   else if (event->xany.type == MotionNotify &&
1305            pp->button_down_p)
1306     {
1307       gltrackball_track (pp->trackball,
1308                          event->xmotion.x, event->xmotion.y,
1309                          MI_WIDTH (mi), MI_HEIGHT (mi));
1310       return True;
1311     }
1312   else if (event->xany.type == KeyPress)
1313     {
1314       KeySym keysym;
1315       char c = 0;
1316       XLookupString (&event->xkey, &c, 1, &keysym, 0);
1317       if (c == ' ' && debug_one_gear_p && pp->ngears)
1318         {
1319           delete_gear (mi, pp->gears[0]);
1320           return True;
1321         }
1322     }
1323
1324   return False;
1325 }
1326
1327
1328 ENTRYPOINT void 
1329 init_pinion (ModeInfo *mi)
1330 {
1331   pinion_configuration *pp;
1332   int wire = MI_IS_WIREFRAME(mi);
1333
1334   if (!pps) {
1335     pps = (pinion_configuration *)
1336       calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
1337     if (!pps) {
1338       fprintf(stderr, "%s: out of memory\n", progname);
1339       exit(1);
1340     }
1341
1342     pp = &pps[MI_SCREEN(mi)];
1343   }
1344
1345   pp = &pps[MI_SCREEN(mi)];
1346
1347   pp->glx_context = init_GL(mi);
1348
1349   load_fonts (mi);
1350   reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1351
1352   pp->title_list  = glGenLists (1);
1353
1354   pp->ngears = 0;
1355   pp->gears_size = 0;
1356   pp->gears = 0;
1357
1358   pp->plane_displacement = gear_size * 0.1;
1359
1360   if (!wire)
1361     {
1362       GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
1363       GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
1364       GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
1365       GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
1366
1367       glEnable(GL_LIGHTING);
1368       glEnable(GL_LIGHT0);
1369       glEnable(GL_DEPTH_TEST);
1370       glEnable(GL_CULL_FACE);
1371
1372       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1373       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1374       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1375       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1376     }
1377
1378   pp->trackball = gltrackball_init ();
1379
1380   ffwd (mi);
1381 }
1382
1383
1384 ENTRYPOINT void
1385 draw_pinion (ModeInfo *mi)
1386 {
1387   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1388   Display *dpy = MI_DISPLAY(mi);
1389   Window window = MI_WINDOW(mi);
1390   Bool wire_p = MI_IS_WIREFRAME(mi);
1391
1392   if (!pp->glx_context)
1393     return;
1394
1395   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
1396
1397   if (!pp->button_down_p)
1398     {
1399       if (!debug_one_gear_p || pp->ngears == 0)
1400         scroll_gears (mi);
1401       spin_gears (mi);
1402     }
1403
1404   glShadeModel(GL_SMOOTH);
1405
1406   glEnable(GL_DEPTH_TEST);
1407   glEnable(GL_NORMALIZE);
1408   glEnable(GL_CULL_FACE);
1409
1410   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1411
1412   glPushMatrix ();
1413   {
1414     gltrackball_rotate (pp->trackball);
1415     mi->polygon_count = 0;
1416
1417     glScalef (16, 16, 16);   /* map vp_width/height to the screen */
1418
1419     if (debug_one_gear_p)    /* zoom in */
1420       glScalef (3, 3, 3);
1421     else if (debug_p)        /* show the "visible" and "layout" areas */
1422       {
1423         GLfloat ow = pp->layout_right - pp->render_left;
1424         GLfloat rw = pp->render_right - pp->render_left;
1425         GLfloat s = (pp->vp_width / ow) * 0.85;
1426         glScalef (s, s, s);
1427         glTranslatef (-(ow - rw) / 2, 0, 0);
1428       }
1429     else
1430       {
1431         GLfloat s = 1.2;
1432         glScalef (s, s, s);           /* zoom in a little more */
1433         glRotatef (-35, 1, 0, 0);     /* tilt back */
1434         glRotatef (  8, 0, 1, 0);     /* tilt left */
1435         glTranslatef (0.02, 0.1, 0);  /* pan up */
1436       }
1437
1438     draw_gears (mi);
1439
1440     if (debug_p)
1441       {
1442         if (!wire_p) glDisable(GL_LIGHTING);
1443         glColor3f (0.6, 0, 0);
1444         glBegin(GL_LINE_LOOP);
1445         glVertex3f (pp->render_left,  pp->vp_top,    0);
1446         glVertex3f (pp->render_right, pp->vp_top,    0);
1447         glVertex3f (pp->render_right, pp->vp_bottom, 0);
1448         glVertex3f (pp->render_left,  pp->vp_bottom, 0);
1449         glEnd();
1450         glColor3f (0.4, 0, 0);
1451         glBegin(GL_LINES);
1452         glVertex3f (pp->vp_left,      pp->vp_top,    0);
1453         glVertex3f (pp->vp_left,      pp->vp_bottom, 0);
1454         glVertex3f (pp->vp_right,     pp->vp_top,    0);
1455         glVertex3f (pp->vp_right,     pp->vp_bottom, 0);
1456         glEnd();
1457         glColor3f (0, 0.4, 0);
1458         glBegin(GL_LINE_LOOP);
1459         glVertex3f (pp->layout_left,  pp->vp_top,    0);
1460         glVertex3f (pp->layout_right, pp->vp_top,    0);
1461         glVertex3f (pp->layout_right, pp->vp_bottom, 0);
1462         glVertex3f (pp->layout_left,  pp->vp_bottom, 0);
1463         glEnd();
1464         if (!wire_p) glEnable(GL_LIGHTING);
1465       }
1466
1467     if (pp->draw_tick++ > 10)   /* only do this every N frames */
1468       {
1469         pp->draw_tick = 0;
1470         find_mouse_gear (mi);
1471         new_label (mi);
1472       }
1473   }
1474   glPopMatrix ();
1475
1476   glCallList (pp->title_list);
1477
1478   if (mi->fps_p) do_fps (mi);
1479   glFinish();
1480
1481   glXSwapBuffers(dpy, window);
1482 }
1483
1484 XSCREENSAVER_MODULE ("Pinion", pinion)
1485
1486 #endif /* USE_GL */