From http://www.jwz.org/xscreensaver/xscreensaver-5.30.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 "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   texture_font_data *font1, *font2, *font3;
57
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   pp->font1 = load_texture_font (mi->dpy, "titleFont");
113   pp->font2 = load_texture_font (mi->dpy, "titleFont2");
114   pp->font3 = load_texture_font (mi->dpy, "titleFont3");
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       texture_font_data *fd;
156       if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
157         fd = pp->font1;
158       else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
159         fd = pp->font2;
160       else
161         fd = pp->font3;
162
163       glColor3f (0.8, 0.8, 0);
164       print_gl_string (mi->dpy, fd,
165                        mi->xgwa.width, mi->xgwa.height,
166                        10, mi->xgwa.height - 10,
167                        label, False);
168     }
169   glEndList ();
170 }
171
172 \f
173 /* Some utilities
174  */
175
176
177 /* Find the gear in the scene that is farthest to the right or left.
178  */
179 static gear *
180 farthest_gear (ModeInfo *mi, Bool left_p)
181 {
182   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
183   int i;
184   gear *rg = 0;
185   double x = (left_p ? 999999 : -999999);
186   for (i = 0; i < pp->ngears; i++)
187     {
188       gear *g = pp->gears[i];
189       double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
190       if (left_p ? (x > gx) : (x < gx))
191         {
192           rg = g;
193           x = gx;
194         }
195     }
196   return rg;
197 }
198
199
200 /* Compute the revolutions per minute of a gear.
201  */
202 static void
203 compute_rpm (ModeInfo *mi, gear *g)
204 {
205   double fps, rpf, rps;
206   fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
207
208   if (fps > 150) fps = 150;  /* let's be reasonable... */
209   if (fps < 10)  fps = 10;
210
211   rpf    = (g->ratio * spin_speed) / 360.0;   /* rotations per frame */
212   rps    = rpf * fps;                         /* rotations per second */
213   g->rpm = rps * 60;
214 }
215
216 /* Prints the RPM into a string, doing fancy float formatting.
217  */
218 static void
219 rpm_string (double rpm, char *s)
220 {
221   char buf[30];
222   int L;
223   if (rpm >= 0.1)          sprintf (buf, "%.2f", rpm);
224   else if (rpm >= 0.001)   sprintf (buf, "%.4f", rpm);
225   else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
226   else                     sprintf (buf, "%.16f",rpm);
227
228   L = strlen(buf);
229   while (buf[L-1] == '0') buf[--L] = 0;
230   if (buf[L-1] == '.') buf[--L] = 0;
231   strcpy (s, buf);
232   strcat (s, " RPM");
233 }
234
235
236 \f
237 /* Layout and stuff.
238  */
239
240
241 /* Create and return a new gear sized for placement next to or on top of
242    the given parent gear (if any.)  Returns 0 if out of memory.
243  */
244 static gear *
245 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
246 {
247   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
248   gear *g = (gear *) calloc (1, sizeof (*g));
249   int loop_count = 0;
250   static unsigned long id = 0;  /* only used in debugging output */
251
252   if (!g) return 0;
253   if (coaxial_p && !parent) abort();
254   g->id = ++id;
255
256   g->coax_displacement = pp->plane_displacement;
257
258   while (1)
259     {
260       loop_count++;
261       if (loop_count > 1000)
262         /* The only time we loop in here is when making a coaxial gear, and
263            trying to pick a radius that is either significantly larger or
264            smaller than its parent.  That shouldn't be hard, so something
265            must be really wrong if we can't do that in only a few tries.
266          */
267         abort();
268
269       /* Pick the size of the teeth.
270        */
271       if (parent && !coaxial_p) /* adjascent gears need matching teeth */
272         {
273           g->tooth_w = parent->tooth_w;
274           g->tooth_h = parent->tooth_h;
275           g->thickness  = parent->thickness;
276           g->thickness2 = parent->thickness2;
277           g->thickness3 = parent->thickness3;
278         }
279       else                 /* gears that begin trains get any size they want */
280         {
281           double scale = (1.0 + BELLRAND(4.0)) * gear_size;
282           g->tooth_w = 0.007 * scale;
283           g->tooth_h = 0.005 * scale;
284           g->thickness  = g->tooth_h * (0.1 + BELLRAND(1.5));
285           g->thickness2 = g->thickness / 4;
286           g->thickness3 = g->thickness;
287         }
288
289       /* Pick the number of teeth, and thus, the radius.
290        */
291       {
292         double c;
293
294       AGAIN:
295         g->nteeth = 3 + (random() % 97);    /* from 3 to 100 teeth */
296
297         if (g->nteeth < 7 && (random() % 5) != 0)
298           goto AGAIN;   /* Let's make very small tooth-counts more rare */
299
300         c = g->nteeth * g->tooth_w * 2;     /* circumference = teeth + gaps */
301         g->r = c / (M_PI * 2);              /* c = 2 pi r  */
302       }
303
304
305       /* Are we done now?
306        */
307       if (! coaxial_p) break;   /* yes */
308       if (g->nteeth == parent->nteeth) continue; /* ugly */
309       if (g->r  < parent->r * 0.6) break;  /* g much smaller than parent */
310       if (parent->r < g->r  * 0.6) break;  /* g much larger than parent  */
311     }
312
313   /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */
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                 g->size = INVOLUTE_LARGE;
415   }
416
417   g->base_p = !parent;
418
419   return g;
420 }
421
422
423 /* Given a newly-created gear, place it next to its parent in the scene,
424    with its teeth meshed and the proper velocity.  Returns False if it
425    didn't work.  (Call this a bunch of times until either it works, or
426    you decide it's probably not going to.)
427  */
428 static Bool
429 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
430 {
431   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
432
433   /* If this gear takes up more than 1/3rd of the screen, it's no good.
434    */
435   if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
436       ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
437     {
438       if (verbose_p && debug_placement_p && 0)
439         fprintf (stderr,
440                  "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
441                  progname,
442                  (g->r + g->tooth_h), gear_size,
443                  pp->vp_width, pp->vp_height);
444       pp->debug_size_failures++;
445       return False;
446     }
447
448   /* Compute this gear's velocity.
449    */
450   if (! parent)
451     {
452       g->ratio = 0.8 + BELLRAND(0.4);  /* 0.8-1.2 = 8-12rpm @ 60fps */
453       g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
454     }
455   else if (coaxial_p)
456     {
457       g->ratio = parent->ratio; /* bound gears have the same ratio */
458       g->th = parent->th;
459       g->rpm = parent->rpm;
460       g->wobble = parent->wobble;
461     }
462   else
463     {
464       /* Gearing ratio is the ratio of the number of teeth to previous gear
465          (which is also the ratio of the circumferences.)
466        */
467       g->ratio = (double) parent->nteeth / (double) g->nteeth;
468
469       /* Set our initial rotation to match that of the previous gear,
470          multiplied by the gearing ratio.  (This is finessed later,
471          once we know the exact position of the gear relative to its
472          parent.)
473       */
474       g->th = -(parent->th * g->ratio);
475
476       if (g->nteeth & 1)    /* rotate 1/2 tooth-size if odd number of teeth */
477         {
478           double off = (180.0 / g->nteeth);
479           if (g->th > 0)
480             g->th += off;
481           else
482             g->th -= off;
483         }
484
485       /* ratios are cumulative for all gears in the train. */
486       g->ratio *= parent->ratio;
487     }
488
489
490   /* Place the gear relative to the parent.
491    */
492
493   if (! parent)
494     {
495       gear *rg = farthest_gear (mi, False);
496       double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
497       if (right < pp->layout_left) /* place off screen */
498         right = pp->layout_left;
499
500       g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
501       g->y = 0;
502       g->z = 0;
503
504       if (debug_one_gear_p)
505         g->x = 0;
506     }
507   else if (coaxial_p)
508     {
509       double off = pp->plane_displacement;
510
511       g->x = parent->x;
512       g->y = parent->y;
513       g->z = parent->z + (g->r > parent->r      /* small gear on top */
514                           ? -off : off);
515
516       if (parent->r > g->r)     /* mark which is top and which is bottom */
517         {
518           parent->coax_p = 1;
519           g->coax_p      = 2;
520           parent->wobble = 0;   /* looks bad when axle moves */
521         }
522       else
523         {
524           parent->coax_p = 2;
525           g->coax_p      = 1;
526           g->wobble      = 0;
527         }
528
529       g->coax_thickness      = parent->thickness;
530       parent->coax_thickness = g->thickness;
531
532       /* Don't let the train get too close to or far from the screen.
533          If we're getting too close, give up on this gear.
534          (But getting very far away is fine.)
535        */
536       if (g->z >=  off * 4 ||
537           g->z <= -off * 4)
538         {
539           if (verbose_p && debug_placement_p)
540             fprintf (stderr, "%s: placement: bad depth: %.2f\n",
541                      progname, g->z);
542           pp->debug_position_failures++;
543           return False;
544         }
545     }
546   else                          /* position it somewhere next to the parent. */
547     {
548       double r_off = parent->r + g->r;
549       int angle;
550
551       if ((random() % 3) != 0)
552         angle = (random() % 240) - 120;   /* mostly -120 to +120 degrees */
553       else
554         angle = (random() % 360) - 180;   /* sometimes -180 to +180 degrees */
555
556       g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
557       g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
558       g->z = parent->z;
559
560       /* If the angle we picked would have positioned this gear
561          more than halfway off screen, that's no good. */
562       if (g->y > pp->vp_top ||
563           g->y < pp->vp_bottom)
564         {
565           if (verbose_p && debug_placement_p)
566             fprintf (stderr, "%s: placement: out of bounds: %s\n",
567                      progname, (g->y > pp->vp_top ? "top" : "bottom"));
568           pp->debug_position_failures++;
569           return False;
570         }
571
572       /* avoid accidentally changing sign of "th" in the math below. */
573       g->th += (g->th > 0 ? 360 : -360);
574
575       /* Adjust the rotation of the gear so that its teeth line up with its
576          parent, based on the position of the gear and the current rotation
577          of the parent.
578        */
579       {
580         double p_c = 2 * M_PI * parent->r;  /* circumference of parent */
581         double g_c = 2 * M_PI * g->r;       /* circumference of g  */
582
583         double p_t = p_c * (angle/360.0);   /* distance travelled along
584                                                circumference of parent when
585                                                moving "angle" degrees along
586                                                parent. */
587         double g_rat = p_t / g_c;           /* if travelling that distance
588                                                along circumference of g,
589                                                ratio of g's circumference
590                                                travelled. */
591         double g_th = 360.0 * g_rat;        /* that ratio in degrees */
592
593         g->th += angle + g_th;
594       }
595     }
596
597   if (debug_one_gear_p)
598     {
599       compute_rpm (mi, g);
600       return True;
601     }
602
603   /* If the position we picked for this gear would cause it to already
604      be visible on the screen, give up.  This can happen when the train
605      is growing backwards, and we don't want to see gears flash into
606      existence.
607    */
608   if (g->x - g->r - g->tooth_h < pp->render_right)
609     {
610       if (verbose_p && debug_placement_p)
611         fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
612       pp->debug_position_failures++;
613       return False;
614     }
615
616   /* If the position we picked for this gear causes it to overlap
617      with any earlier gear in the train, give up.
618    */
619   {
620     int i;
621
622     for (i = pp->ngears-1; i >= 0; i--)
623       {
624         gear *og = pp->gears[i];
625
626         if (og == g) continue;
627         if (og == parent) continue;
628         if (g->z != og->z) continue;    /* Ignore unless on same layer */
629
630         /* Collision detection without sqrt:
631              d = sqrt(a^2 + b^2)   d^2 = a^2 + b^2
632              d < r1 + r2           d^2 < (r1 + r2)^2
633          */
634         if (((g->x - og->x) * (g->x - og->x) +
635              (g->y - og->y) * (g->y - og->y)) <
636             ((g->r + g->tooth_h + og->r + og->tooth_h) *
637              (g->r + g->tooth_h + og->r + og->tooth_h)))
638           {
639             if (verbose_p && debug_placement_p)
640               fprintf (stderr, "%s: placement: collision with %lu\n",
641                        progname, og->id);
642             pp->debug_position_failures++;
643             return False;
644           }
645       }
646   }
647
648   compute_rpm (mi, g);
649
650
651   /* Make deeper gears be darker.
652    */
653   {
654     double depth = g->z / pp->plane_displacement;
655     double brightness = 1 + (depth / 6);
656     double limit = 0.4;
657     if (brightness < limit)   brightness = limit;
658     if (brightness > 1/limit) brightness = 1/limit;
659     g->color[0]  *= brightness;
660     g->color[1]  *= brightness;
661     g->color[2]  *= brightness;
662     g->color2[0] *= brightness;
663     g->color2[1] *= brightness;
664     g->color2[2] *= brightness;
665   }
666
667   /* If a single frame of animation would cause the gear to rotate by
668      more than 1/2 the size of a single tooth, then it won't look right:
669      the gear will appear to be turning at some lower harmonic of its
670      actual speed.
671    */
672   {
673     double ratio = g->ratio * spin_speed;
674     double blur_limit = 180.0 / g->nteeth;
675
676     if (ratio > blur_limit)
677       g->motion_blur_p = 1;
678
679     if (!coaxial_p)
680       {
681         /* ride until the wheels fall off... */
682         if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
683         if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
684         if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
685         if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
686         if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
687         if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
688       }
689   }
690
691   return True;
692 }
693
694 static void
695 free_gear (gear *g)
696 {
697   if (g->dlist)
698     glDeleteLists (g->dlist, 1);
699   free (g);
700 }
701
702
703 /* Make a new gear, place it next to its parent in the scene,
704    with its teeth meshed and the proper velocity.  Returns the gear;
705    or 0 if it didn't work.  (Call this a bunch of times until either
706    it works, or you decide it's probably not going to.)
707  */
708 static gear *
709 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
710 {
711   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
712   int loop_count = 0;
713   gear *g = 0;
714
715   while (1)
716     {
717       loop_count++;
718       if (loop_count >= 100)
719         {
720           if (g)
721             free_gear (g);
722           g = 0;
723           break;
724         }
725
726       g = new_gear (mi, parent, coaxial_p);
727       if (!g) return 0;  /* out of memory? */
728
729       if (place_gear (mi, g, parent, coaxial_p))
730         break;
731     }
732
733   if (! g) return 0;
734
735   /* We got a gear, and it is properly positioned.
736      Insert it in the scene.
737    */
738   if (pp->ngears + 2 >= pp->gears_size)
739     {
740       pp->gears_size += 100;
741       pp->gears = (gear **) realloc (pp->gears,
742                                      pp->gears_size * sizeof (*pp->gears));
743       if (! pp->gears)
744         {
745           fprintf (stderr, "%s: out of memory (%d gears)\n",
746                    progname, pp->gears_size);
747         }
748     }
749
750   pp->gears[pp->ngears++] = g;
751   return g;
752 }
753
754
755 static void delete_gear (ModeInfo *mi, gear *g);
756
757 static void
758 push_gear (ModeInfo *mi)
759 {
760   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
761   gear *g;
762   gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
763
764   Bool tried_coaxial_p = False;
765   Bool coaxial_p = False;
766   Bool last_ditch_coax_p = False;
767   int loop_count = 0;
768
769   pp->debug_size_failures = 0;
770   pp->debug_position_failures = 0;
771
772  AGAIN:
773   loop_count++;
774   if (loop_count > 100) abort();  /* we're doomed! */
775   
776   g = 0;
777
778   /* If the gears are turning at LUDICROUS SPEED, unhook the train to
779      reset things to a sane velocity.
780
781      10,000 RPM at 30 FPS = 5.5 rotations per frame.
782       1,000 RPM at 30 FPS = 0.5 rotations per frame.
783         600 RPM at 30 FPS =  3 frames per rotation.
784         200 RPM at 30 FPS =  9 frames per rotation.
785         100 RPM at 30 FPS = 18 frames per rotation.
786          50 RPM at 30 FPS = 36 frames per rotation.
787          10 RPM at 30 FPS =  3 sec per rotation.
788           1 RPM at 30 FPS = 30 sec per rotation.
789          .5 RPM at 30 FPS =  1 min per rotation.
790          .1 RPM at 30 FPS =  5 min per rotation.
791    */
792   if (parent && parent->rpm > max_rpm)
793     {
794       if (verbose_p)
795         {
796           char buf[100];
797           rpm_string (parent->rpm, buf);
798           fprintf (stderr, "%s: ludicrous speed!  %s\n\n", progname, buf);
799         }
800       parent = 0;
801     }
802
803   /* If the last N gears we've placed have all been motion-blurred, then
804      it's a safe guess that we've wandered off into the woods and aren't
805      coming back.  Bail on this train.
806    */
807   if (pp->current_blur_length >= 10)
808     {
809       if (verbose_p)
810         fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
811       parent = 0;
812     }
813
814
815
816   /* Sometimes, try to make a coaxial gear.
817    */
818   if (parent && !parent->coax_p && (random() % 40) == 0)
819     {
820       tried_coaxial_p = True;
821       coaxial_p = True;
822       g = place_new_gear (mi, parent, coaxial_p);
823     }
824
825   /* Try to make a regular gear.
826    */
827   if (!g)
828     {
829       coaxial_p = False;
830       g = place_new_gear (mi, parent, coaxial_p);
831     }
832
833   /* If we couldn't make a regular gear, then try to make a coxial gear
834      (unless we already tried that.)
835    */
836   if (!g && !tried_coaxial_p && parent && !parent->coax_p)
837     {
838       tried_coaxial_p = True;
839       coaxial_p = True;
840       g = place_new_gear (mi, parent, coaxial_p);
841       if (g)
842         last_ditch_coax_p = True;
843     }
844
845   /* If we couldn't do that either, then the train has hit a dead end:
846      start a new train.
847    */
848   if (!g)
849     {
850       coaxial_p = False;
851       if (verbose_p)
852         fprintf (stderr, "%s: dead end!\n\n", progname);
853       parent = 0;
854       g = place_new_gear (mi, parent, coaxial_p);
855     }
856
857   if (! g)
858     {
859       /* Unable to make/place any gears at all!
860          This can happen if we've backed ourself into a corner very near
861          the top-right or bottom-right corner of the growth zone.
862          It's time to add a gear, but there's no room to add one!
863          In that case, let's just wipe all the gears that are in the
864          growth zone and try again.
865        */
866       int i;
867
868       if (verbose_p && debug_placement_p)
869         fprintf (stderr,
870                  "%s: placement: resetting growth zone!  "
871                  "failed: %d size, %d pos\n",
872                  progname,
873                  pp->debug_size_failures, pp->debug_position_failures);
874       for (i = pp->ngears-1; i >= 0; i--)
875         {
876           gear *g = pp->gears[i];
877           if (g->x - g->r - g->tooth_h < pp->render_left)
878             delete_gear (mi, g);
879         }
880       goto AGAIN;
881     }
882
883   if (g->coax_p)
884     {
885       if (!parent) abort();
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
1163 # ifndef HAVE_JWZGLES
1164
1165   int screen_width = MI_WIDTH (mi);
1166   int screen_height = MI_HEIGHT (mi);
1167   GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
1168   int x, y;
1169   int hits;
1170
1171   pp->mouse_gear_id = 0;
1172
1173   /* Poll mouse position */
1174   {
1175     Window r, c;
1176     int rx, ry;
1177     unsigned int m;
1178     XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
1179                    &r, &c, &rx, &ry, &x, &y, &m);
1180   }
1181
1182   if (x < 0 || y < 0 || x > screen_width || y > screen_height)
1183     return;  /* out of window */
1184
1185   /* Run OpenGL hit detector */
1186   {
1187     GLint vp[4];
1188     GLuint selbuf[512];
1189
1190     glSelectBuffer (sizeof(selbuf), selbuf);  /* set up "select" mode */
1191     glRenderMode (GL_SELECT);
1192     glMatrixMode (GL_PROJECTION);
1193     glPushMatrix();
1194     glLoadIdentity();
1195     glGetIntegerv (GL_VIEWPORT, vp);         /* save old vp */
1196     gluPickMatrix (x, vp[3]-y, 5, 5, vp);
1197     gluPerspective (30.0, 1/h, 1.0, 100.0);  /* must match reshape_pinion() */
1198     glMatrixMode (GL_MODELVIEW);
1199
1200     draw_gears (mi);                         /* render into "select" buffer */
1201
1202     glMatrixMode (GL_PROJECTION);            /* restore old vp */
1203     glPopMatrix ();
1204     glMatrixMode (GL_MODELVIEW);
1205     glFlush();
1206     hits = glRenderMode (GL_RENDER);         /* done selecting */
1207
1208     if (hits > 0)
1209       {
1210         int i;
1211         GLuint name_count = 0;
1212         GLuint *p = (GLuint *) selbuf;
1213         GLuint *pnames = 0;
1214         GLuint min_z = ~0;
1215
1216         for (i = 0; i < hits; i++)
1217           {     
1218             int names = *p++;
1219             if (*p < min_z)                  /* find match closest to screen */
1220               {
1221                 name_count = names;
1222                 min_z = *p;
1223                 pnames = p+2;
1224               }
1225             p += names+2;
1226           }
1227
1228         if (name_count > 0)                  /* take first hit */
1229           pp->mouse_gear_id = pnames[0];
1230       }
1231   }
1232
1233 #else  /* HAVE_JWZGLES */
1234   /* #### not yet implemented */
1235   pp->mouse_gear_id = pp->gears[1]->id;
1236   return;
1237 #endif /* HAVE_JWZGLES */
1238
1239
1240 }
1241
1242
1243 /* Window management, etc
1244  */
1245 ENTRYPOINT void
1246 reshape_pinion (ModeInfo *mi, int width, int height)
1247 {
1248   GLfloat h = (GLfloat) height / (GLfloat) width;
1249   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1250
1251   glViewport (0, 0, (GLint) width, (GLint) height);
1252
1253   glMatrixMode(GL_PROJECTION);
1254   glLoadIdentity();
1255   gluPerspective (30.0, 1/h, 1.0, 100.0);
1256
1257   glMatrixMode(GL_MODELVIEW);
1258   glLoadIdentity();
1259   gluLookAt( 0.0, 0.0, 30.0,
1260              0.0, 0.0, 0.0,
1261              0.0, 1.0, 0.0);
1262
1263   glClear(GL_COLOR_BUFFER_BIT);
1264
1265   {
1266     GLfloat render_width, layout_width;
1267     pp->vp_height = 1.0;
1268     pp->vp_width  = 1/h;
1269
1270     pp->vp_left   = -pp->vp_width/2;
1271     pp->vp_right  =  pp->vp_width/2;
1272     pp->vp_top    =  pp->vp_height/2;
1273     pp->vp_bottom = -pp->vp_height/2;
1274
1275     render_width = pp->vp_width * 2;
1276     layout_width = pp->vp_width * 0.8 * gear_size;
1277
1278     pp->render_left  = -render_width/2;
1279     pp->render_right =  render_width/2;
1280
1281     pp->layout_left  = pp->render_right;
1282     pp->layout_right = pp->layout_left + layout_width;
1283   }
1284 }
1285
1286
1287 ENTRYPOINT Bool
1288 pinion_handle_event (ModeInfo *mi, XEvent *event)
1289 {
1290   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1291
1292   if (gltrackball_event_handler (event, pp->trackball,
1293                                  MI_WIDTH (mi), MI_HEIGHT (mi),
1294                                  &pp->button_down_p))
1295     return True;
1296   else if (event->xany.type == KeyPress)
1297     {
1298       KeySym keysym;
1299       char c = 0;
1300       XLookupString (&event->xkey, &c, 1, &keysym, 0);
1301       if (c == ' ' && debug_one_gear_p && pp->ngears)
1302         {
1303           delete_gear (mi, pp->gears[0]);
1304           return True;
1305         }
1306     }
1307
1308   return False;
1309 }
1310
1311
1312 ENTRYPOINT void 
1313 init_pinion (ModeInfo *mi)
1314 {
1315   pinion_configuration *pp;
1316
1317   if (!pps) {
1318     pps = (pinion_configuration *)
1319       calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
1320     if (!pps) {
1321       fprintf(stderr, "%s: out of memory\n", progname);
1322       exit(1);
1323     }
1324   }
1325
1326   pp = &pps[MI_SCREEN(mi)];
1327
1328   pp->glx_context = init_GL(mi);
1329
1330   load_fonts (mi);
1331   reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1332   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
1333
1334   pp->title_list  = glGenLists (1);
1335
1336   pp->ngears = 0;
1337   pp->gears_size = 0;
1338   pp->gears = 0;
1339
1340   pp->plane_displacement = gear_size * 0.1;
1341
1342   pp->trackball = gltrackball_init (False);
1343
1344   ffwd (mi);
1345 }
1346
1347
1348 ENTRYPOINT void
1349 draw_pinion (ModeInfo *mi)
1350 {
1351   pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1352   Display *dpy = MI_DISPLAY(mi);
1353   Window window = MI_WINDOW(mi);
1354   Bool wire_p = MI_IS_WIREFRAME(mi);
1355
1356   if (!pp->glx_context)
1357     return;
1358
1359   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
1360
1361   glPushMatrix();
1362   glRotatef(current_device_rotation(), 0, 0, 1);
1363
1364   if (!wire_p)
1365     {
1366       GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
1367       GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
1368       GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
1369       GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
1370
1371       glEnable(GL_LIGHTING);
1372       glEnable(GL_LIGHT0);
1373       glEnable(GL_DEPTH_TEST);
1374       glEnable(GL_CULL_FACE);
1375
1376       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1377       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1378       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1379       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1380     }
1381
1382   if (!pp->button_down_p)
1383     {
1384       if (!debug_one_gear_p || pp->ngears == 0)
1385         scroll_gears (mi);
1386       spin_gears (mi);
1387     }
1388
1389   glShadeModel(GL_SMOOTH);
1390
1391   glEnable(GL_DEPTH_TEST);
1392   glEnable(GL_NORMALIZE);
1393   glEnable(GL_CULL_FACE);
1394
1395   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1396
1397   glPushMatrix ();
1398   {
1399     gltrackball_rotate (pp->trackball);
1400     mi->polygon_count = 0;
1401
1402     glScalef (16, 16, 16);   /* map vp_width/height to the screen */
1403
1404     if (debug_one_gear_p)    /* zoom in */
1405       glScalef (3, 3, 3);
1406     else if (debug_p)        /* show the "visible" and "layout" areas */
1407       {
1408         GLfloat ow = pp->layout_right - pp->render_left;
1409         GLfloat rw = pp->render_right - pp->render_left;
1410         GLfloat s = (pp->vp_width / ow) * 0.85;
1411         glScalef (s, s, s);
1412         glTranslatef (-(ow - rw) / 2, 0, 0);
1413       }
1414     else
1415       {
1416         GLfloat s = 1.2;
1417         glScalef (s, s, s);           /* zoom in a little more */
1418         glRotatef (-35, 1, 0, 0);     /* tilt back */
1419         glRotatef (  8, 0, 1, 0);     /* tilt left */
1420         glTranslatef (0.02, 0.1, 0);  /* pan up */
1421       }
1422
1423     draw_gears (mi);
1424
1425     if (debug_p)
1426       {
1427         if (!wire_p) glDisable(GL_LIGHTING);
1428         glColor3f (0.6, 0, 0);
1429         glBegin(GL_LINE_LOOP);
1430         glVertex3f (pp->render_left,  pp->vp_top,    0);
1431         glVertex3f (pp->render_right, pp->vp_top,    0);
1432         glVertex3f (pp->render_right, pp->vp_bottom, 0);
1433         glVertex3f (pp->render_left,  pp->vp_bottom, 0);
1434         glEnd();
1435         glColor3f (0.4, 0, 0);
1436         glBegin(GL_LINES);
1437         glVertex3f (pp->vp_left,      pp->vp_top,    0);
1438         glVertex3f (pp->vp_left,      pp->vp_bottom, 0);
1439         glVertex3f (pp->vp_right,     pp->vp_top,    0);
1440         glVertex3f (pp->vp_right,     pp->vp_bottom, 0);
1441         glEnd();
1442         glColor3f (0, 0.4, 0);
1443         glBegin(GL_LINE_LOOP);
1444         glVertex3f (pp->layout_left,  pp->vp_top,    0);
1445         glVertex3f (pp->layout_right, pp->vp_top,    0);
1446         glVertex3f (pp->layout_right, pp->vp_bottom, 0);
1447         glVertex3f (pp->layout_left,  pp->vp_bottom, 0);
1448         glEnd();
1449         if (!wire_p) glEnable(GL_LIGHTING);
1450       }
1451
1452     if (pp->draw_tick++ > 10)   /* only do this every N frames */
1453       {
1454         pp->draw_tick = 0;
1455         find_mouse_gear (mi);
1456         new_label (mi);
1457       }
1458   }
1459   glPopMatrix ();
1460
1461   glCallList (pp->title_list);
1462   glPopMatrix ();
1463
1464   if (mi->fps_p) do_fps (mi);
1465   glFinish();
1466
1467   glXSwapBuffers(dpy, window);
1468 }
1469
1470 XSCREENSAVER_MODULE ("Pinion", pinion)
1471
1472 #endif /* USE_GL */