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