From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / hacks / glx / gears.c
1 /* gears, Copyright (c) 2007-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  * Originally written by Brian Paul in 1996 or earlier;
12  * rewritten by jwz in Nov 2007.
13  */
14
15 #define DEFAULTS        "*delay:        30000       \n" \
16                         "*count:        0           \n" \
17                         "*showFPS:      False       \n" \
18                         "*wireframe:    False       \n" \
19
20 # define refresh_gears 0
21 # define release_gears 0
22 #undef countof
23 #define countof(x) (sizeof((x))/sizeof((*x)))
24
25 #include "xlockmore.h"
26 #include "involute.h"
27 #include "normals.h"
28 #include "tube.h"
29 #include "rotator.h"
30 #include "gltrackball.h"
31 #include <ctype.h>
32
33 #ifdef USE_GL /* whole file */
34
35 #undef BELLRAND
36 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
37
38 #define DEF_SPIN        "True"
39 #define DEF_WANDER      "True"
40 #define DEF_SPEED       "1.0"
41
42 typedef struct {
43   GLXContext *glx_context;
44   rotator *rot;
45   trackball_state *trackball;
46   Bool button_down_p;
47   Bool planetary_p;
48
49   int ngears;
50   gear **gears;
51
52   GLuint armature_dlist;
53   int armature_polygons;
54
55   struct { GLfloat x1, y1, x2, y2; } bbox;
56
57 } gears_configuration;
58
59 static gears_configuration *bps = NULL;
60
61 static Bool do_spin;
62 static GLfloat speed;
63 static Bool do_wander;
64
65 static XrmOptionDescRec opts[] = {
66   { "-spin",   ".spin",   XrmoptionNoArg, "True"  },
67   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
68   { "-speed",  ".speed",  XrmoptionSepArg, 0      },
69   { "-wander", ".wander", XrmoptionNoArg, "True"  },
70   { "+wander", ".wander", XrmoptionNoArg, "False" },
71 };
72
73 static argtype vars[] = {
74   {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_Bool},
75   {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
76   {&speed,     "speed",  "Speed",  DEF_SPEED,  t_Float},
77 };
78
79 ENTRYPOINT ModeSpecOpt gears_opts = {countof(opts), opts, countof(vars), vars, NULL};
80
81
82 /* Window management, etc
83  */
84 ENTRYPOINT void
85 reshape_gears (ModeInfo *mi, int width, int height)
86 {
87   GLfloat h = (GLfloat) height / (GLfloat) width;
88
89   glViewport (0, 0, (GLint) width, (GLint) height);
90
91   glMatrixMode(GL_PROJECTION);
92   glLoadIdentity();
93   gluPerspective (30.0, 1/h, 1.0, 100.0);
94
95   glMatrixMode(GL_MODELVIEW);
96   glLoadIdentity();
97   gluLookAt( 0.0, 0.0, 30.0,
98              0.0, 0.0, 0.0,
99              0.0, 1.0, 0.0);
100
101   glClear(GL_COLOR_BUFFER_BIT);
102 }
103
104
105 static void
106 free_gear (gear *g)
107 {
108   if (g->dlist)
109     glDeleteLists (g->dlist, 1);
110   free (g);
111 }
112
113
114 /* Create and return a new gear sized for placement next to or on top of
115    the given parent gear (if any.)  Returns 0 if out of memory.
116    [Mostly lifted from pinion.c]
117  */
118 static gear *
119 new_gear (ModeInfo *mi, gear *parent)
120 {
121   gears_configuration *bp = &bps[MI_SCREEN(mi)];
122   gear *g = (gear *) calloc (1, sizeof (*g));
123   static unsigned long id = 0;  /* only used in debugging output */
124
125   if (!g) return 0;
126   g->id = ++id;
127
128   /* Pick the size of the teeth.
129    */
130   if (parent) /* adjascent gears need matching teeth */
131     {
132       g->tooth_w = parent->tooth_w;
133       g->tooth_h = parent->tooth_h;
134       g->tooth_slope = -parent->tooth_slope;
135     }
136   else                 /* gears that begin trains get any size they want */
137     {
138       g->tooth_w = 0.007 * (1.0 + BELLRAND(4.0));
139       g->tooth_h = 0.005 * (1.0 + BELLRAND(8.0));
140 /*
141       g->tooth_slope = ((random() % 8)
142                         ? 0
143                         : 0.5 + BELLRAND(1));
144  */
145     }
146
147   /* Pick the number of teeth, and thus, the radius.
148    */
149   {
150     double c;
151
152     if (!parent || bp->ngears > 4)
153       g->nteeth = 5 + BELLRAND (20);
154     else
155       g->nteeth = parent->nteeth * (0.5 + BELLRAND(2));
156
157     c = g->nteeth * g->tooth_w * 2;     /* circumference = teeth + gaps */
158     g->r = c / (M_PI * 2);              /* c = 2 pi r  */
159   }
160
161   g->thickness  = g->tooth_w + frand (g->r);
162   g->thickness2 = g->thickness * 0.7;
163   g->thickness3 = g->thickness;
164
165   /* Colorize
166    */
167   g->color[0] = 0.5 + frand(0.5);
168   g->color[1] = 0.5 + frand(0.5);
169   g->color[2] = 0.5 + frand(0.5);
170   g->color[3] = 1.0;
171
172   g->color2[0] = g->color[0] * 0.85;
173   g->color2[1] = g->color[1] * 0.85;
174   g->color2[2] = g->color[2] * 0.85;
175   g->color2[3] = g->color[3];
176
177
178   /* Decide on shape of gear interior:
179      - just a ring with teeth;
180      - that, plus a thinner in-set "plate" in the middle;
181      - that, plus a thin raised "lip" on the inner plate;
182      - or, a wide lip (really, a thicker third inner plate.)
183    */
184   if ((random() % 10) == 0)
185     {
186       /* inner_r can go all the way in; there's no inset disc. */
187       g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
188       g->inner_r2 = 0;
189       g->inner_r3 = 0;
190     }
191   else
192     {
193       /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
194       g->inner_r  = (g->r * 0.5)  + frand((g->r - g->tooth_h) * 0.4);
195       g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
196       g->inner_r3 = 0;
197
198       if (g->inner_r2 > (g->r * 0.2))
199         {
200           int nn = (random() % 10);
201           if (nn <= 2)
202             g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
203           else if (nn <= 7 && g->inner_r2 >= 0.1)
204             g->inner_r3 = g->inner_r2 - 0.01;
205         }
206     }
207
208   /* If we have three discs, sometimes make the middle disc be spokes.
209    */
210   if (g->inner_r3 && ((random() % 5) == 0))
211     {
212       g->spokes = 2 + BELLRAND (5);
213       g->spoke_thickness = 1 + frand(7.0);
214       if (g->spokes == 2 && g->spoke_thickness < 2)
215         g->spoke_thickness += 1;
216     }
217
218   /* Sometimes add little nubbly bits, if there is room.
219    */
220   if (g->nteeth > 5)
221     {
222       double size = 0;
223       involute_biggest_ring (g, 0, &size, 0);
224       if (size > g->r * 0.2 && (random() % 5) == 0)
225         {
226           g->nubs = 1 + (random() % 16);
227           if (g->nubs > 8) g->nubs = 1;
228         }
229     }
230
231   if (g->inner_r3 > g->inner_r2) abort();
232   if (g->inner_r2 > g->inner_r) abort();
233   if (g->inner_r  > g->r) abort();
234
235   /* Decide how complex the polygon model should be.
236    */
237   {
238     double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
239     if (pix <= 2.5)      g->size = INVOLUTE_SMALL;
240     else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
241     else                 g->size = INVOLUTE_LARGE;
242   }
243
244   g->base_p = !parent;
245
246   return g;
247 }
248
249
250 /* Given a newly-created gear, place it next to its parent in the scene,
251    with its teeth meshed and the proper velocity.  Returns False if it
252    didn't work.  (Call this a bunch of times until either it works, or
253    you decide it's probably not going to.)
254    [Mostly lifted from pinion.c]
255  */
256 static Bool
257 place_gear (ModeInfo *mi, gear *g, gear *parent)
258 {
259   gears_configuration *bp = &bps[MI_SCREEN(mi)];
260
261   /* Compute this gear's velocity.
262    */
263   if (! parent)
264     {
265       g->ratio = 0.8 + BELLRAND(0.4);  /* 0.8-1.2 = 8-12rpm @ 60fps */
266       g->th = 1; /* not 0 */
267     }
268   else
269     {
270       /* Gearing ratio is the ratio of the number of teeth to previous gear
271          (which is also the ratio of the circumferences.)
272        */
273       g->ratio = (double) parent->nteeth / (double) g->nteeth;
274
275       /* Set our initial rotation to match that of the previous gear,
276          multiplied by the gearing ratio.  (This is finessed later,
277          once we know the exact position of the gear relative to its
278          parent.)
279       */
280       g->th = -(parent->th * g->ratio);
281
282       if (g->nteeth & 1)    /* rotate 1/2 tooth-size if odd number of teeth */
283         {
284           double off = (180.0 / g->nteeth);
285           if (g->th > 0)
286             g->th += off;
287           else
288             g->th -= off;
289         }
290
291       /* ratios are cumulative for all gears in the train. */
292       g->ratio *= parent->ratio;
293     }
294
295
296   if (parent)   /* Place the gear next to the parent. */
297     {
298       double r_off = parent->r + g->r;
299       int angle;
300
301       angle = (random() % 360) - 180;   /* -180 to +180 degrees */
302
303       g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
304       g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
305       g->z = parent->z;
306
307       /* avoid accidentally changing sign of "th" in the math below. */
308       g->th += (g->th > 0 ? 360 : -360);
309
310       /* Adjust the rotation of the gear so that its teeth line up with its
311          parent, based on the position of the gear and the current rotation
312          of the parent.
313        */
314       {
315         double p_c = 2 * M_PI * parent->r;  /* circumference of parent */
316         double g_c = 2 * M_PI * g->r;       /* circumference of g  */
317
318         double p_t = p_c * (angle/360.0);   /* distance travelled along
319                                                circumference of parent when
320                                                moving "angle" degrees along
321                                                parent. */
322         double g_rat = p_t / g_c;           /* if travelling that distance
323                                                along circumference of g,
324                                                ratio of g's circumference
325                                                travelled. */
326         double g_th = 360.0 * g_rat;        /* that ratio in degrees */
327
328         g->th += angle + g_th;
329       }
330     }
331
332   /* If the position we picked for this gear causes it to overlap
333      with any earlier gear in the train, give up.
334    */
335   {
336     int i;
337
338     for (i = bp->ngears-1; i >= 0; i--)
339       {
340         gear *og = bp->gears[i];
341
342         if (og == g) continue;
343         if (og == parent) continue;
344         if (g->z != og->z) continue;    /* Ignore unless on same layer */
345
346         /* Collision detection without sqrt:
347              d = sqrt(a^2 + b^2)   d^2 = a^2 + b^2
348              d < r1 + r2           d^2 < (r1 + r2)^2
349          */
350         if (((g->x - og->x) * (g->x - og->x) +
351              (g->y - og->y) * (g->y - og->y)) <
352             ((g->r + g->tooth_h + og->r + og->tooth_h) *
353              (g->r + g->tooth_h + og->r + og->tooth_h)))
354           return False;
355       }
356   }
357
358   return True;
359 }
360
361
362 /* Make a new gear, place it next to its parent in the scene,
363    with its teeth meshed and the proper velocity.  Returns the gear;
364    or 0 if it didn't work.  (Call this a bunch of times until either
365    it works, or you decide it's probably not going to.)
366    [Mostly lifted from pinion.c]
367  */
368 static gear *
369 place_new_gear (ModeInfo *mi, gear *parent)
370 {
371   gears_configuration *bp = &bps[MI_SCREEN(mi)];
372   int loop_count = 0;
373   gear *g = 0;
374
375   while (1)
376     {
377       loop_count++;
378       if (loop_count >= 100)
379         {
380           if (g)
381             free_gear (g);
382           g = 0;
383           break;
384         }
385
386       g = new_gear (mi, parent);
387       if (!g) return 0;  /* out of memory? */
388
389       if (place_gear (mi, g, parent))
390         break;
391     }
392
393   if (! g) return 0;
394
395   /* We got a gear, and it is properly positioned.
396      Insert it in the scene.
397    */
398   bp->gears[bp->ngears++] = g;
399   return g;
400 }
401
402
403 static int
404 arm (GLfloat length,
405      GLfloat width1, GLfloat height1,
406      GLfloat width2, GLfloat height2,
407      Bool wire)
408 {
409   int polys = 0;
410   glShadeModel(GL_FLAT);
411
412 #if 0  /* don't need these - they're embedded in other objects */
413   /* draw end 1 */
414   glFrontFace(GL_CW);
415   glNormal3f(-1, 0, 0);
416   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
417   glVertex3f(-length/2, -width1/2, -height1/2);
418   glVertex3f(-length/2,  width1/2, -height1/2);
419   glVertex3f(-length/2,  width1/2,  height1/2);
420   glVertex3f(-length/2, -width1/2,  height1/2);
421   polys++;
422   glEnd();
423
424   /* draw end 2 */
425   glFrontFace(GL_CCW);
426   glNormal3f(1, 0, 0);
427   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
428   glVertex3f(length/2, -width2/2, -height2/2);
429   glVertex3f(length/2,  width2/2, -height2/2);
430   glVertex3f(length/2,  width2/2,  height2/2);
431   glVertex3f(length/2, -width2/2,  height2/2);
432   polys++;
433   glEnd();
434 #endif
435
436   /* draw top */
437   glFrontFace(GL_CCW);
438   glNormal3f(0, 0, -1);
439   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
440   glVertex3f(-length/2, -width1/2, -height1/2);
441   glVertex3f(-length/2,  width1/2, -height1/2);
442   glVertex3f( length/2,  width2/2, -height2/2);
443   glVertex3f( length/2, -width2/2, -height2/2);
444   polys++;
445   glEnd();
446
447   /* draw bottom */
448   glFrontFace(GL_CW);
449   glNormal3f(0, 0, 1);
450   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
451   glVertex3f(-length/2, -width1/2, height1/2);
452   glVertex3f(-length/2,  width1/2, height1/2);
453   glVertex3f( length/2,  width2/2, height2/2);
454   glVertex3f( length/2, -width2/2, height2/2);
455   polys++;
456   glEnd();
457
458   /* draw left */
459   glFrontFace(GL_CW);
460   glNormal3f(0, -1, 0);
461   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
462   glVertex3f(-length/2, -width1/2, -height1/2);
463   glVertex3f(-length/2, -width1/2,  height1/2);
464   glVertex3f( length/2, -width2/2,  height2/2);
465   glVertex3f( length/2, -width2/2, -height2/2);
466   polys++;
467   glEnd();
468
469   /* draw right */
470   glFrontFace(GL_CCW);
471   glNormal3f(0, 1, 0);
472   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
473   glVertex3f(-length/2,  width1/2, -height1/2);
474   glVertex3f(-length/2,  width1/2,  height1/2);
475   glVertex3f( length/2,  width2/2,  height2/2);
476   glVertex3f( length/2,  width2/2, -height2/2);
477   polys++;
478   glEnd();
479
480   glFrontFace(GL_CCW);
481
482   return polys;
483 }
484
485
486 static int
487 ctube (GLfloat diameter, GLfloat width, Bool wire)
488 {
489   tube (0, 0,  width/2,
490         0, 0, -width/2,
491         diameter, 0, 
492         32, True, True, wire);
493   return 0; /* #### */
494 }
495
496 static void
497 armature (ModeInfo *mi)
498 {
499   gears_configuration *bp = &bps[MI_SCREEN(mi)];
500   int wire = MI_IS_WIREFRAME(mi);
501
502   static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
503   GLfloat shiny = 128.0;
504   GLfloat color[4];
505
506   color[0] = 0.5 + frand(0.5);
507   color[1] = 0.5 + frand(0.5);
508   color[2] = 0.5 + frand(0.5);
509   color[3] = 1.0;
510
511   bp->armature_polygons = 0;
512
513   bp->armature_dlist = glGenLists (1);
514   if (! bp->armature_dlist)
515     {
516       check_gl_error ("glGenLists");
517       abort();
518     }
519
520   glNewList (bp->armature_dlist, GL_COMPILE);
521
522   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
523   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
524   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
525   glColor3f (color[0], color[1], color[2]);
526
527   glPushMatrix();
528
529   {
530     GLfloat s = bp->gears[0]->r * 2.7;
531     s = s/5.6;
532     glScalef (s, s, s);
533   }
534
535   glTranslatef (0, 0, 1.4 + bp->gears[0]->thickness);
536   glRotatef (30, 0, 0, 1);
537
538   bp->armature_polygons += ctube (0.5, 10, wire);       /* center axle */
539
540   glPushMatrix();
541   glTranslatef(0.0, 4.2, -1);
542   bp->armature_polygons += ctube (0.5, 3, wire);       /* axle 1 */
543   glTranslatef(0, 0, 1.8);
544   bp->armature_polygons += ctube (0.7, 0.7, wire);
545   glPopMatrix();
546
547   glPushMatrix();
548   glRotatef(120, 0.0, 0.0, 1.0);
549   glTranslatef(0.0, 4.2, -1);
550   bp->armature_polygons += ctube (0.5, 3, wire);       /* axle 2 */
551   glTranslatef(0, 0, 1.8);
552   bp->armature_polygons += ctube (0.7, 0.7, wire);
553   glPopMatrix();
554
555   glPushMatrix();
556   glRotatef(240, 0.0, 0.0, 1.0);
557   glTranslatef(0.0, 4.2, -1);
558   bp->armature_polygons += ctube (0.5, 3, wire);       /* axle 3 */
559   glTranslatef(0, 0, 1.8);
560   bp->armature_polygons += ctube (0.7, 0.7, wire);
561   glPopMatrix();
562
563   glTranslatef(0, 0, 1.5);                            /* center disk */
564   bp->armature_polygons += ctube (1.5, 2, wire);
565
566   glPushMatrix();
567   glRotatef(270, 0, 0, 1);
568   glRotatef(-10, 0, 1, 0);
569   glTranslatef(-2.2, 0, 0);
570   bp->armature_polygons += arm (4.0, 1.0, 0.5,
571                                 2.0, 1.0, wire);        /* arm 1 */
572   glPopMatrix();
573
574   glPushMatrix();
575   glRotatef(30, 0, 0, 1);
576   glRotatef(-10, 0, 1, 0);
577   glTranslatef(-2.2, 0, 0);
578   bp->armature_polygons += arm (4.0, 1.0, 0.5,
579                                 2.0, 1.0, wire);        /* arm 2 */
580   glPopMatrix();
581
582   glPushMatrix();
583   glRotatef(150, 0, 0, 1);
584   glRotatef(-10, 0, 1, 0);
585   glTranslatef(-2.2, 0, 0);
586   bp->armature_polygons += arm (4.0, 1.0, 0.5,
587                                 2.0, 1.0, wire);        /* arm 3 */
588   glPopMatrix();
589
590   glPopMatrix();
591
592   glEndList ();
593 }
594
595
596 static void
597 planetary_gears (ModeInfo *mi)
598 {
599   gears_configuration *bp = &bps[MI_SCREEN(mi)];
600   gear *g0, *g1, *g2, *g3, *g4;
601   GLfloat distance = 2.02;
602
603   bp->planetary_p = True;
604
605   g0 = new_gear (mi, 0);
606   g1 = new_gear (mi, 0);
607   g2 = new_gear (mi, 0);
608   g3 = new_gear (mi, 0);
609   g4 = new_gear (mi, 0);
610
611   if (! place_gear (mi, g0, 0)) abort();
612   if (! place_gear (mi, g1, 0)) abort();
613   if (! place_gear (mi, g2, 0)) abort();
614   if (! place_gear (mi, g3, 0)) abort();
615   if (! place_gear (mi, g4, 0)) abort();
616
617   g0->nteeth = 12 + (3 * (random() % 10));  /* must be multiple of 3 */
618   g0->tooth_w = g0->r / g0->nteeth;
619   g0->tooth_h = g0->tooth_w * 2.8;
620
621 # define COPY(F) g4->F = g3->F = g2->F = g1->F = g0->F
622   COPY(r);
623   COPY(th);
624   COPY(nteeth);
625   COPY(tooth_w);
626   COPY(tooth_h);
627   COPY(tooth_slope);
628   COPY(inner_r);
629   COPY(inner_r2);
630   COPY(inner_r3);
631   COPY(thickness);
632   COPY(thickness2);
633   COPY(thickness3);
634   COPY(ratio);
635   COPY(size);
636 # undef COPY
637
638   g1->x = cos (M_PI * 2 / 3) * g1->r * distance;
639   g1->y = sin (M_PI * 2 / 3) * g1->r * distance;
640
641   g2->x = cos (M_PI * 4 / 3) * g2->r * distance;
642   g2->y = sin (M_PI * 4 / 3) * g2->r * distance;
643
644   g3->x = cos (M_PI * 6 / 3) * g3->r * distance;
645   g3->y = sin (M_PI * 6 / 3) * g3->r * distance;
646
647   g4->x = 0;
648   g4->y = 0;
649   g4->th = -g3->th;
650
651   /* rotate central gear 1/2 tooth-size if odd number of teeth */
652   if (g4->nteeth & 1)
653     g4->th -= (180.0 / g4->nteeth);
654
655   g0->inverted_p  = True;
656   g0->x           = 0;
657   g0->y           = 0;
658   g0->nteeth      = g1->nteeth * 3;
659   g0->r           = g1->r * 3.05;
660   g0->inner_r     = g0->r * 0.8;
661   g0->inner_r2    = 0;
662   g0->inner_r3    = 0;
663   g0->th          = g1->th + (180 / g0->nteeth);
664   g0->ratio       = g1->ratio / 3;
665
666   g0->tooth_slope = 0;
667   g0->nubs        = 3;
668   g0->spokes      = 0;
669   g0->size        = INVOLUTE_LARGE;
670
671   bp->gears = (gear **) calloc (6, sizeof(**bp->gears));
672   bp->ngears = 0;
673
674   bp->gears[bp->ngears++] = g1;
675   bp->gears[bp->ngears++] = g2;
676   bp->gears[bp->ngears++] = g3;
677   bp->gears[bp->ngears++] = g4;
678   bp->gears[bp->ngears++] = g0;
679 }
680
681
682
683
684 ENTRYPOINT void 
685 init_gears (ModeInfo *mi)
686 {
687   gears_configuration *bp;
688   int wire = MI_IS_WIREFRAME(mi);
689   int i;
690
691   if (!bps) {
692     bps = (gears_configuration *)
693       calloc (MI_NUM_SCREENS(mi), sizeof (gears_configuration));
694     if (!bps) {
695       fprintf(stderr, "%s: out of memory\n", progname);
696       exit(1);
697     }
698   }
699
700   bp = &bps[MI_SCREEN(mi)];
701
702   bp->glx_context = init_GL(mi);
703
704   reshape_gears (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
705
706   if (!wire)
707     {
708       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
709       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
710       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
711       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
712
713       glEnable(GL_LIGHTING);
714       glEnable(GL_LIGHT0);
715       glEnable(GL_DEPTH_TEST);
716       glEnable(GL_CULL_FACE);
717
718       glLightfv(GL_LIGHT0, GL_POSITION, pos);
719       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
720       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
721       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
722     }
723
724   if (! bp->rot)
725     {
726       double spin_speed   = 0.5;
727       double wander_speed = 0.01;
728       double spin_accel   = 0.25;
729
730       bp->rot = make_rotator (do_spin ? spin_speed : 0,
731                               do_spin ? spin_speed : 0,
732                               do_spin ? spin_speed : 0,
733                               spin_accel,
734                               do_wander ? wander_speed : 0,
735                               True
736                               );
737       bp->trackball = gltrackball_init (True);
738     }
739
740   if (bp->gears)
741     {
742       for (i = 0; i < bp->ngears; i++)
743         free_gear (bp->gears[i]);
744       free (bp->gears);
745       bp->gears = 0;
746       bp->ngears = 0;
747     }
748
749   if (!(random() % 8))
750     {
751       planetary_gears (mi);
752     }
753   else
754     {
755       gear *g = 0;
756       int total_gears = MI_COUNT (mi);
757
758       bp->planetary_p = False;
759
760       if (total_gears <= 0)
761         total_gears = 3 + abs (BELLRAND (8) - 4);  /* 3 - 7, mostly 3. */
762       bp->gears = (gear **) calloc (total_gears+2, sizeof(**bp->gears));
763       bp->ngears = 0;
764
765       for (i = 0; i < total_gears; i++)
766         g = place_new_gear (mi, g);
767     }
768
769
770   /* Center gears in scene. */
771   {
772     GLfloat minx=99999, miny=99999, maxx=-99999, maxy=-99999;
773     int i;
774     for (i = 0; i < bp->ngears; i++)
775       {
776         gear *g = bp->gears[i];
777         if (g->x - g->r < minx) minx = g->x - g->r;
778         if (g->x + g->r > maxx) maxx = g->x + g->r;
779         if (g->y - g->r < miny) miny = g->y - g->r;
780         if (g->y + g->r > maxy) maxy = g->y + g->r;
781       }
782     bp->bbox.x1 = minx;
783     bp->bbox.y1 = miny;
784     bp->bbox.x2 = maxx;
785     bp->bbox.y2 = maxy;
786   }
787
788   /* Now render each gear into its display list.
789    */
790   for (i = 0; i < bp->ngears; i++)
791     {
792       gear *g = bp->gears[i];
793       g->dlist = glGenLists (1);
794       if (! g->dlist)
795         {
796           check_gl_error ("glGenLists");
797           abort();
798         }
799
800       glNewList (g->dlist, GL_COMPILE);
801       g->polygons += draw_involute_gear (g, wire);
802       glEndList ();
803     }
804   if (bp->planetary_p)
805     armature (mi);
806 }
807
808
809 ENTRYPOINT void
810 draw_gears (ModeInfo *mi)
811 {
812   gears_configuration *bp = &bps[MI_SCREEN(mi)];
813   Display *dpy = MI_DISPLAY(mi);
814   Window window = MI_WINDOW(mi);
815   int i;
816
817   if (!bp->glx_context)
818     return;
819
820   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
821
822   glShadeModel(GL_SMOOTH);
823
824   glEnable(GL_DEPTH_TEST);
825   glEnable(GL_NORMALIZE);
826   glEnable(GL_CULL_FACE);
827
828   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
829
830   glPushMatrix ();
831
832   {
833     double x, y, z;
834     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
835     glTranslatef ((x - 0.5) * 4,
836                   (y - 0.5) * 4,
837                   (z - 0.5) * 7);
838
839     gltrackball_rotate (bp->trackball);
840
841     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
842
843     /* add a little rotation for -no-spin mode */
844     x -= 0.14;
845     y -= 0.06;
846
847     glRotatef (x * 360, 1.0, 0.0, 0.0);
848     glRotatef (y * 360, 0.0, 1.0, 0.0);
849     glRotatef (z * 360, 0.0, 0.0, 1.0);
850   }
851
852   /* Center the scene's bounding box in the window,
853      and scale it to fit. 
854    */
855   {
856     GLfloat w = bp->bbox.x2 - bp->bbox.x1;
857     GLfloat h = bp->bbox.y2 - bp->bbox.y1;
858     GLfloat s = 10.0 / (w > h ? w : h);
859     glScalef (s, s, s);
860     glTranslatef (-(bp->bbox.x1 + w/2),
861                   -(bp->bbox.y1 + h/2),
862                   0);
863   }
864
865   mi->polygon_count = 0;
866
867   for (i = 0; i < bp->ngears; i++)
868     {
869       gear *g = bp->gears[i];
870
871       glPushMatrix();
872
873       glTranslatef (g->x, g->y, g->z);
874       glRotatef (g->th, 0, 0, 1);
875
876       glCallList (g->dlist);
877       mi->polygon_count += g->polygons;
878
879       glPopMatrix ();
880     }
881
882   if (bp->planetary_p)
883     {
884       glCallList (bp->armature_dlist);
885       mi->polygon_count += bp->armature_polygons;
886     }
887
888   glPopMatrix ();
889
890   /* spin gears */
891   if (!bp->button_down_p)
892     for (i = 0; i < bp->ngears; i++)
893       {
894         gear *g = bp->gears[i];
895         double off = g->ratio * 5 * speed;
896         if (g->th > 0)
897           g->th += off;
898         else
899           g->th -= off;
900       }
901
902   if (mi->fps_p) do_fps (mi);
903   glFinish();
904
905   glXSwapBuffers(dpy, window);
906 }
907
908 ENTRYPOINT Bool
909 gears_handle_event (ModeInfo *mi, XEvent *event)
910 {
911   gears_configuration *bp = &bps[MI_SCREEN(mi)];
912
913   if (gltrackball_event_handler (event, bp->trackball,
914                                  MI_WIDTH (mi), MI_HEIGHT (mi),
915                                  &bp->button_down_p))
916     return True;
917   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
918     {
919       init_gears (mi);
920       return True;
921     }
922
923   return False;
924 }
925
926 XSCREENSAVER_MODULE ("Gears", gears)
927
928 #endif /* USE_GL */