From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / pinion.c
1 /* pinion, Copyright (c) 2004-2014 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 "texfont.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   texture_font_data *font1, *font2, *font3;
57
58   int draw_tick;
59
60   GLfloat plane_displacement;        /* distance between coaxial gears */
61
62   int debug_size_failures;           /* for debugging messages */
63   int debug_position_failures;
64   unsigned long current_length;      /* gear count in current train */
65   unsigned long current_blur_length; /* how long have we been blurring? */
66
67 } pinion_configuration;
68
69
70 static pinion_configuration *pps = NULL;
71
72 /* command line arguments */
73 static GLfloat spin_speed, scroll_speed, gear_size, max_rpm;
74
75 static Bool verbose_p = False;            /* print progress on stderr */
76 static Bool debug_p = False;              /* render as flat schematic */
77
78 /* internal debugging variables */
79 static Bool debug_placement_p = False;    /* extreme verbosity on stderr */
80 static Bool debug_one_gear_p = False;     /* draw one big stationary gear */
81
82
83 static XrmOptionDescRec opts[] = {
84   { "-spin",   ".spinSpeed",   XrmoptionSepArg, 0 },
85   { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
86   { "-size",   ".gearSize",    XrmoptionSepArg, 0 },
87   { "-max-rpm",".maxRPM",      XrmoptionSepArg, 0 },
88   { "-debug",  ".debug",       XrmoptionNoArg, "True" },
89   { "-verbose",".verbose",     XrmoptionNoArg, "True" },
90 };
91
92 static argtype vars[] = {
93   {&spin_speed,   "spinSpeed",   "SpinSpeed",   DEF_SPIN_SPEED,   t_Float},
94   {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
95   {&gear_size,    "gearSize",    "GearSize",    DEF_GEAR_SIZE,    t_Float},
96   {&max_rpm,      "maxRPM",      "MaxRPM",      DEF_MAX_RPM,      t_Float},
97   {&debug_p,      "debug",       "Debug",       "False",          t_Bool},
98   {&verbose_p,    "verbose",     "Verbose",     "False",          t_Bool},
99 };
100
101 ENTRYPOINT ModeSpecOpt pinion_opts = {countof(opts), opts, countof(vars), vars, NULL};
102
103 \f
104 /* Font stuff
105  */
106
107 static void
108 load_fonts (ModeInfo *mi)
109 {
110   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
111   pp->font1 = load_texture_font (mi->dpy, "titleFont");
112   pp->font2 = load_texture_font (mi->dpy, "titleFont2");
113   pp->font3 = load_texture_font (mi->dpy, "titleFont3");
114 }
115
116
117
118 static void rpm_string (double rpm, char *s);
119
120 static void
121 draw_label (ModeInfo *mi)
122 {
123   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
124   char label[1024];
125   int i;
126   gear *g = 0;
127
128   if (pp->mouse_gear_id)
129     for (i = 0; i < pp->ngears; i++)
130       if (pp->gears[i]->id == pp->mouse_gear_id)
131         {
132           g = pp->gears[i];
133           break;
134         }
135
136   if (!g)
137     *label = 0;
138   else
139     {
140       sprintf (label, "%d teeth\n", (int) g->nteeth);
141       rpm_string (g->rpm, label + strlen(label));
142       if (debug_p)
143         sprintf (label + strlen (label), "\nPolys:  %d\nModel:  %s  (%.2f)\n",
144                  g->polygons,
145                  (g->size == INVOLUTE_SMALL ? "small" :
146                   g->size == INVOLUTE_MEDIUM ? "medium"
147                   : "large"),
148                  g->tooth_h * MI_HEIGHT(mi));
149     }
150
151   if (*label)
152     {
153       texture_font_data *fd;
154       if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
155         fd = pp->font1;
156       else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
157         fd = pp->font2;
158       else
159         fd = pp->font3;
160
161       glColor3f (0.8, 0.8, 0);
162       print_texture_label (mi->dpy, fd,
163                            mi->xgwa.width, mi->xgwa.height,
164                            1, label);
165     }
166 }
167
168 \f
169 /* Some utilities
170  */
171
172
173 /* Find the gear in the scene that is farthest to the right or left.
174  */
175 static gear *
176 farthest_gear (ModeInfo *mi, Bool left_p)
177 {
178   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
179   int i;
180   gear *rg = 0;
181   double x = (left_p ? 999999 : -999999);
182   for (i = 0; i < pp->ngears; i++)
183     {
184       gear *g = pp->gears[i];
185       double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
186       if (left_p ? (x > gx) : (x < gx))
187         {
188           rg = g;
189           x = gx;
190         }
191     }
192   return rg;
193 }
194
195
196 /* Compute the revolutions per minute of a gear.
197  */
198 static void
199 compute_rpm (ModeInfo *mi, gear *g)
200 {
201   double fps, rpf, rps;
202   fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
203
204   if (fps > 150) fps = 150;  /* let's be reasonable... */
205   if (fps < 10)  fps = 10;
206
207   rpf    = (g->ratio * spin_speed) / 360.0;   /* rotations per frame */
208   rps    = rpf * fps;                         /* rotations per second */
209   g->rpm = rps * 60;
210 }
211
212 /* Prints the RPM into a string, doing fancy float formatting.
213  */
214 static void
215 rpm_string (double rpm, char *s)
216 {
217   char buf[30];
218   int L;
219   if (rpm >= 0.1)          sprintf (buf, "%.2f", rpm);
220   else if (rpm >= 0.001)   sprintf (buf, "%.4f", rpm);
221   else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
222   else                     sprintf (buf, "%.16f",rpm);
223
224   L = strlen(buf);
225   while (buf[L-1] == '0') buf[--L] = 0;
226   if (buf[L-1] == '.') buf[--L] = 0;
227   strcpy (s, buf);
228   strcat (s, " RPM");
229 }
230
231
232 \f
233 /* Layout and stuff.
234  */
235
236
237 /* Create and return a new gear sized for placement next to or on top of
238    the given parent gear (if any.)  Returns 0 if out of memory.
239  */
240 static gear *
241 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
242 {
243   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
244   gear *g = (gear *) calloc (1, sizeof (*g));
245   int loop_count = 0;
246   static unsigned long id = 0;  /* only used in debugging output */
247
248   if (!g) return 0;
249   if (coaxial_p && !parent) abort();
250   g->id = ++id;
251
252   g->coax_displacement = pp->plane_displacement;
253
254   while (1)
255     {
256       loop_count++;
257       if (loop_count > 1000)
258         /* The only time we loop in here is when making a coaxial gear, and
259            trying to pick a radius that is either significantly larger or
260            smaller than its parent.  That shouldn't be hard, so something
261            must be really wrong if we can't do that in only a few tries.
262          */
263         abort();
264
265       /* Pick the size of the teeth.
266        */
267       if (parent && !coaxial_p) /* adjascent gears need matching teeth */
268         {
269           g->tooth_w = parent->tooth_w;
270           g->tooth_h = parent->tooth_h;
271           g->thickness  = parent->thickness;
272           g->thickness2 = parent->thickness2;
273           g->thickness3 = parent->thickness3;
274         }
275       else                 /* gears that begin trains get any size they want */
276         {
277           double scale = (1.0 + BELLRAND(4.0)) * gear_size;
278           g->tooth_w = 0.007 * scale;
279           g->tooth_h = 0.005 * scale;
280           g->thickness  = g->tooth_h * (0.1 + BELLRAND(1.5));
281           g->thickness2 = g->thickness / 4;
282           g->thickness3 = g->thickness;
283         }
284
285       /* Pick the number of teeth, and thus, the radius.
286        */
287       {
288         double c;
289
290       AGAIN:
291         g->nteeth = 3 + (random() % 97);    /* from 3 to 100 teeth */
292
293         if (g->nteeth < 7 && (random() % 5) != 0)
294           goto AGAIN;   /* Let's make very small tooth-counts more rare */
295
296         c = g->nteeth * g->tooth_w * 2;     /* circumference = teeth + gaps */
297         g->r = c / (M_PI * 2);              /* c = 2 pi r  */
298       }
299
300
301       /* Are we done now?
302        */
303       if (! coaxial_p) break;   /* yes */
304       if (g->nteeth == parent->nteeth) continue; /* ugly */
305       if (g->r  < parent->r * 0.6) break;  /* g much smaller than parent */
306       if (parent->r < g->r  * 0.6) break;  /* g much larger than parent  */
307     }
308
309   /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */
310
311   if (debug_one_gear_p)
312     g->tooth_slope = frand(20)-10;
313
314
315   /* Colorize
316    */
317   g->color[0] = 0.5 + frand(0.5);
318   g->color[1] = 0.5 + frand(0.5);
319   g->color[2] = 0.5 + frand(0.5);
320   g->color[3] = 1.0;
321
322   g->color2[0] = g->color[0] * 0.85;
323   g->color2[1] = g->color[1] * 0.85;
324   g->color2[2] = g->color[2] * 0.85;
325   g->color2[3] = g->color[3];
326
327
328   /* Decide on shape of gear interior:
329      - just a ring with teeth;
330      - that, plus a thinner in-set "plate" in the middle;
331      - that, plus a thin raised "lip" on the inner plate;
332      - or, a wide lip (really, a thicker third inner plate.)
333    */
334   if ((random() % 10) == 0)
335     {
336       /* inner_r can go all the way in; there's no inset disc. */
337       g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
338       g->inner_r2 = 0;
339       g->inner_r3 = 0;
340     }
341   else
342     {
343       /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
344       g->inner_r  = (g->r * 0.5)  + frand((g->r - g->tooth_h) * 0.4);
345       g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
346       g->inner_r3 = 0;
347
348       if (g->inner_r2 > (g->r * 0.2))
349         {
350           int nn = (random() % 10);
351           if (nn <= 2)
352             g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
353           else if (nn <= 7 && g->inner_r2 >= 0.1)
354             g->inner_r3 = g->inner_r2 - 0.01;
355         }
356     }
357
358   /* Coaxial gears need to have the same innermost hole size (for the axle.)
359      Use whichever of the two is smaller.  (Modifies parent.)
360    */
361   if (coaxial_p)
362     {
363       double hole1 = (g->inner_r3 ? g->inner_r3 :
364                       g->inner_r2 ? g->inner_r2 :
365                       g->inner_r);
366       double hole2 = (parent->inner_r3 ? parent->inner_r3 :
367                       parent->inner_r2 ? parent->inner_r2 :
368                       parent->inner_r);
369       double hole = (hole1 < hole2 ? hole1 : hole2);
370       if (hole <= 0) abort();
371
372       if      (g->inner_r3) g->inner_r3 = hole;
373       else if (g->inner_r2) g->inner_r2 = hole;
374       else                  g->inner_r  = hole;
375
376       if      (parent->inner_r3) parent->inner_r3 = hole;
377       else if (parent->inner_r2) parent->inner_r2 = hole;
378       else                       parent->inner_r  = hole;
379     }
380
381   /* If we have three discs, sometimes make the middle disc be spokes.
382    */
383   if (g->inner_r3 && ((random() % 5) == 0))
384     {
385       g->spokes = 2 + BELLRAND (5);
386       g->spoke_thickness = 1 + frand(7.0);
387       if (g->spokes == 2 && g->spoke_thickness < 2)
388         g->spoke_thickness += 1;
389     }
390
391   /* Sometimes add little nubbly bits, if there is room.
392    */
393   if (g->nteeth > 5)
394     {
395       double size = 0;
396       involute_biggest_ring (g, 0, &size, 0);
397       if (size > g->r * 0.2 && (random() % 5) == 0)
398         {
399           g->nubs = 1 + (random() % 16);
400           if (g->nubs > 8) g->nubs = 1;
401         }
402     }
403
404   if (g->inner_r3 > g->inner_r2) abort();
405   if (g->inner_r2 > g->inner_r) abort();
406   if (g->inner_r  > g->r) abort();
407
408   /* Decide how complex the polygon model should be.
409    */
410   {
411     double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
412     if (pix <= 2.5)      g->size = INVOLUTE_SMALL;
413     else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
414     else if (pix <= 25)  g->size = INVOLUTE_LARGE;
415     else                 g->size = INVOLUTE_HUGE;
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
1164 # ifndef HAVE_JWZGLES
1165
1166   int screen_width = MI_WIDTH (mi);
1167   int screen_height = MI_HEIGHT (mi);
1168   GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
1169   int x, y;
1170   int hits;
1171
1172   pp->mouse_gear_id = 0;
1173
1174   /* Poll mouse position */
1175   {
1176     Window r, c;
1177     int rx, ry;
1178     unsigned int m;
1179     XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
1180                    &r, &c, &rx, &ry, &x, &y, &m);
1181   }
1182
1183   if (x < 0 || y < 0 || x > screen_width || y > screen_height)
1184     return;  /* out of window */
1185
1186   /* Run OpenGL hit detector */
1187   {
1188     GLint vp[4];
1189     GLuint selbuf[512];
1190
1191     glSelectBuffer (sizeof(selbuf), selbuf);  /* set up "select" mode */
1192     glRenderMode (GL_SELECT);
1193     glMatrixMode (GL_PROJECTION);
1194     glPushMatrix();
1195     glLoadIdentity();
1196     glGetIntegerv (GL_VIEWPORT, vp);         /* save old vp */
1197     gluPickMatrix (x, vp[3]-y, 5, 5, vp);
1198     gluPerspective (30.0, 1/h, 1.0, 100.0);  /* must match reshape_pinion() */
1199     glMatrixMode (GL_MODELVIEW);
1200
1201     draw_gears (mi);                         /* render into "select" buffer */
1202
1203     glMatrixMode (GL_PROJECTION);            /* restore old vp */
1204     glPopMatrix ();
1205     glMatrixMode (GL_MODELVIEW);
1206     glFlush();
1207     hits = glRenderMode (GL_RENDER);         /* done selecting */
1208
1209     if (hits > 0)
1210       {
1211         int i;
1212         GLuint name_count = 0;
1213         GLuint *p = (GLuint *) selbuf;
1214         GLuint *pnames = 0;
1215         GLuint min_z = ~0;
1216
1217         for (i = 0; i < hits; i++)
1218           {     
1219             int names = *p++;
1220             if (*p < min_z)                  /* find match closest to screen */
1221               {
1222                 name_count = names;
1223                 min_z = *p;
1224                 pnames = p+2;
1225               }
1226             p += names+2;
1227           }
1228
1229         if (name_count > 0)                  /* take first hit */
1230           pp->mouse_gear_id = pnames[0];
1231       }
1232   }
1233
1234 #else  /* HAVE_JWZGLES */
1235   /* #### not yet implemented */
1236   pp->mouse_gear_id = (pp->ngears > 1 ? pp->gears[1]->id : 0);
1237   return;
1238 #endif /* HAVE_JWZGLES */
1239
1240
1241 }
1242
1243
1244 /* Window management, etc
1245  */
1246 ENTRYPOINT void
1247 reshape_pinion (ModeInfo *mi, int width, int height)
1248 {
1249   GLfloat h = (GLfloat) height / (GLfloat) width;
1250   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1251
1252   glViewport (0, 0, (GLint) width, (GLint) height);
1253
1254   glMatrixMode(GL_PROJECTION);
1255   glLoadIdentity();
1256   gluPerspective (30.0, 1/h, 1.0, 100.0);
1257
1258   glMatrixMode(GL_MODELVIEW);
1259   glLoadIdentity();
1260   gluLookAt( 0.0, 0.0, 30.0,
1261              0.0, 0.0, 0.0,
1262              0.0, 1.0, 0.0);
1263
1264   glClear(GL_COLOR_BUFFER_BIT);
1265
1266   {
1267     GLfloat render_width, layout_width;
1268     pp->vp_height = 1.0;
1269     pp->vp_width  = 1/h;
1270
1271     pp->vp_left   = -pp->vp_width/2;
1272     pp->vp_right  =  pp->vp_width/2;
1273     pp->vp_top    =  pp->vp_height/2;
1274     pp->vp_bottom = -pp->vp_height/2;
1275
1276     render_width = pp->vp_width * 2;
1277     layout_width = pp->vp_width * 0.8 * gear_size;
1278
1279     pp->render_left  = -render_width/2;
1280     pp->render_right =  render_width/2;
1281
1282     pp->layout_left  = pp->render_right;
1283     pp->layout_right = pp->layout_left + layout_width;
1284   }
1285 }
1286
1287
1288 ENTRYPOINT Bool
1289 pinion_handle_event (ModeInfo *mi, XEvent *event)
1290 {
1291   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1292
1293   if (gltrackball_event_handler (event, pp->trackball,
1294                                  MI_WIDTH (mi), MI_HEIGHT (mi),
1295                                  &pp->button_down_p))
1296     return True;
1297   else if (event->xany.type == KeyPress)
1298     {
1299       KeySym keysym;
1300       char c = 0;
1301       XLookupString (&event->xkey, &c, 1, &keysym, 0);
1302       if (c == ' ' && debug_one_gear_p && pp->ngears)
1303         {
1304           delete_gear (mi, pp->gears[0]);
1305           return True;
1306         }
1307     }
1308
1309   return False;
1310 }
1311
1312
1313 ENTRYPOINT void 
1314 init_pinion (ModeInfo *mi)
1315 {
1316   pinion_configuration *pp;
1317
1318   MI_INIT (mi, pps, NULL);
1319
1320   pp = &pps[MI_SCREEN(mi)];
1321
1322   pp->glx_context = init_GL(mi);
1323
1324   load_fonts (mi);
1325   reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1326   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
1327
1328   pp->ngears = 0;
1329   pp->gears_size = 0;
1330   pp->gears = 0;
1331
1332   pp->plane_displacement = gear_size * 0.1;
1333
1334   pp->trackball = gltrackball_init (False);
1335
1336   ffwd (mi);
1337 }
1338
1339
1340 ENTRYPOINT void
1341 draw_pinion (ModeInfo *mi)
1342 {
1343   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1344   Display *dpy = MI_DISPLAY(mi);
1345   Window window = MI_WINDOW(mi);
1346   Bool wire_p = MI_IS_WIREFRAME(mi);
1347
1348   if (!pp->glx_context)
1349     return;
1350
1351   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
1352
1353   glPushMatrix();
1354   glRotatef(current_device_rotation(), 0, 0, 1);
1355
1356   if (!wire_p)
1357     {
1358       GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
1359       GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
1360       GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
1361       GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
1362
1363       glEnable(GL_LIGHTING);
1364       glEnable(GL_LIGHT0);
1365       glEnable(GL_DEPTH_TEST);
1366       glEnable(GL_CULL_FACE);
1367
1368       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1369       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1370       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1371       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1372     }
1373
1374   if (!pp->button_down_p)
1375     {
1376       if (!debug_one_gear_p || pp->ngears == 0)
1377         scroll_gears (mi);
1378       spin_gears (mi);
1379     }
1380
1381   glShadeModel(GL_SMOOTH);
1382
1383   glEnable(GL_DEPTH_TEST);
1384   glEnable(GL_NORMALIZE);
1385   glEnable(GL_CULL_FACE);
1386
1387   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1388
1389   glPushMatrix ();
1390   {
1391     gltrackball_rotate (pp->trackball);
1392     mi->polygon_count = 0;
1393
1394     glScalef (16, 16, 16);   /* map vp_width/height to the screen */
1395
1396     if (debug_one_gear_p)    /* zoom in */
1397       glScalef (3, 3, 3);
1398     else if (debug_p)        /* show the "visible" and "layout" areas */
1399       {
1400         GLfloat ow = pp->layout_right - pp->render_left;
1401         GLfloat rw = pp->render_right - pp->render_left;
1402         GLfloat s = (pp->vp_width / ow) * 0.85;
1403         glScalef (s, s, s);
1404         glTranslatef (-(ow - rw) / 2, 0, 0);
1405       }
1406     else
1407       {
1408         GLfloat s = 1.2;
1409         glScalef (s, s, s);           /* zoom in a little more */
1410         glRotatef (-35, 1, 0, 0);     /* tilt back */
1411         glRotatef (  8, 0, 1, 0);     /* tilt left */
1412         glTranslatef (0.02, 0.1, 0);  /* pan up */
1413       }
1414
1415     draw_gears (mi);
1416
1417     if (debug_p)
1418       {
1419         if (!wire_p) glDisable(GL_LIGHTING);
1420         glColor3f (0.6, 0, 0);
1421         glBegin(GL_LINE_LOOP);
1422         glVertex3f (pp->render_left,  pp->vp_top,    0);
1423         glVertex3f (pp->render_right, pp->vp_top,    0);
1424         glVertex3f (pp->render_right, pp->vp_bottom, 0);
1425         glVertex3f (pp->render_left,  pp->vp_bottom, 0);
1426         glEnd();
1427         glColor3f (0.4, 0, 0);
1428         glBegin(GL_LINES);
1429         glVertex3f (pp->vp_left,      pp->vp_top,    0);
1430         glVertex3f (pp->vp_left,      pp->vp_bottom, 0);
1431         glVertex3f (pp->vp_right,     pp->vp_top,    0);
1432         glVertex3f (pp->vp_right,     pp->vp_bottom, 0);
1433         glEnd();
1434         glColor3f (0, 0.4, 0);
1435         glBegin(GL_LINE_LOOP);
1436         glVertex3f (pp->layout_left,  pp->vp_top,    0);
1437         glVertex3f (pp->layout_right, pp->vp_top,    0);
1438         glVertex3f (pp->layout_right, pp->vp_bottom, 0);
1439         glVertex3f (pp->layout_left,  pp->vp_bottom, 0);
1440         glEnd();
1441         if (!wire_p) glEnable(GL_LIGHTING);
1442       }
1443
1444     if (pp->draw_tick++ > 10)   /* only do this every N frames */
1445       {
1446         pp->draw_tick = 0;
1447         find_mouse_gear (mi);
1448       }
1449   }
1450   glPopMatrix ();
1451
1452   draw_label (mi);
1453   glPopMatrix ();
1454
1455   if (mi->fps_p) do_fps (mi);
1456   glFinish();
1457
1458   glXSwapBuffers(dpy, window);
1459 }
1460
1461 XSCREENSAVER_MODULE ("Pinion", pinion)
1462
1463 #endif /* USE_GL */