From http://www.jwz.org/xscreensaver/xscreensaver-5.31.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 if (pix <= 25)  g->size = INVOLUTE_LARGE;
242     else                 g->size = INVOLUTE_HUGE;
243   }
244
245   g->base_p = !parent;
246
247   return g;
248 }
249
250
251 /* Given a newly-created gear, place it next to its parent in the scene,
252    with its teeth meshed and the proper velocity.  Returns False if it
253    didn't work.  (Call this a bunch of times until either it works, or
254    you decide it's probably not going to.)
255    [Mostly lifted from pinion.c]
256  */
257 static Bool
258 place_gear (ModeInfo *mi, gear *g, gear *parent)
259 {
260   gears_configuration *bp = &bps[MI_SCREEN(mi)];
261
262   /* Compute this gear's velocity.
263    */
264   if (! parent)
265     {
266       g->ratio = 0.8 + BELLRAND(0.4);  /* 0.8-1.2 = 8-12rpm @ 60fps */
267       g->th = 1; /* not 0 */
268     }
269   else
270     {
271       /* Gearing ratio is the ratio of the number of teeth to previous gear
272          (which is also the ratio of the circumferences.)
273        */
274       g->ratio = (double) parent->nteeth / (double) g->nteeth;
275
276       /* Set our initial rotation to match that of the previous gear,
277          multiplied by the gearing ratio.  (This is finessed later,
278          once we know the exact position of the gear relative to its
279          parent.)
280       */
281       g->th = -(parent->th * g->ratio);
282
283       if (g->nteeth & 1)    /* rotate 1/2 tooth-size if odd number of teeth */
284         {
285           double off = (180.0 / g->nteeth);
286           if (g->th > 0)
287             g->th += off;
288           else
289             g->th -= off;
290         }
291
292       /* ratios are cumulative for all gears in the train. */
293       g->ratio *= parent->ratio;
294     }
295
296
297   if (parent)   /* Place the gear next to the parent. */
298     {
299       double r_off = parent->r + g->r;
300       int angle;
301
302       angle = (random() % 360) - 180;   /* -180 to +180 degrees */
303
304       g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
305       g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
306       g->z = parent->z;
307
308       /* avoid accidentally changing sign of "th" in the math below. */
309       g->th += (g->th > 0 ? 360 : -360);
310
311       /* Adjust the rotation of the gear so that its teeth line up with its
312          parent, based on the position of the gear and the current rotation
313          of the parent.
314        */
315       {
316         double p_c = 2 * M_PI * parent->r;  /* circumference of parent */
317         double g_c = 2 * M_PI * g->r;       /* circumference of g  */
318
319         double p_t = p_c * (angle/360.0);   /* distance travelled along
320                                                circumference of parent when
321                                                moving "angle" degrees along
322                                                parent. */
323         double g_rat = p_t / g_c;           /* if travelling that distance
324                                                along circumference of g,
325                                                ratio of g's circumference
326                                                travelled. */
327         double g_th = 360.0 * g_rat;        /* that ratio in degrees */
328
329         g->th += angle + g_th;
330       }
331     }
332
333   /* If the position we picked for this gear causes it to overlap
334      with any earlier gear in the train, give up.
335    */
336   {
337     int i;
338
339     for (i = bp->ngears-1; i >= 0; i--)
340       {
341         gear *og = bp->gears[i];
342
343         if (og == g) continue;
344         if (og == parent) continue;
345         if (g->z != og->z) continue;    /* Ignore unless on same layer */
346
347         /* Collision detection without sqrt:
348              d = sqrt(a^2 + b^2)   d^2 = a^2 + b^2
349              d < r1 + r2           d^2 < (r1 + r2)^2
350          */
351         if (((g->x - og->x) * (g->x - og->x) +
352              (g->y - og->y) * (g->y - og->y)) <
353             ((g->r + g->tooth_h + og->r + og->tooth_h) *
354              (g->r + g->tooth_h + og->r + og->tooth_h)))
355           return False;
356       }
357   }
358
359   return True;
360 }
361
362
363 /* Make a new gear, place it next to its parent in the scene,
364    with its teeth meshed and the proper velocity.  Returns the gear;
365    or 0 if it didn't work.  (Call this a bunch of times until either
366    it works, or you decide it's probably not going to.)
367    [Mostly lifted from pinion.c]
368  */
369 static gear *
370 place_new_gear (ModeInfo *mi, gear *parent)
371 {
372   gears_configuration *bp = &bps[MI_SCREEN(mi)];
373   int loop_count = 0;
374   gear *g = 0;
375
376   while (1)
377     {
378       loop_count++;
379       if (loop_count >= 100)
380         {
381           if (g)
382             free_gear (g);
383           g = 0;
384           break;
385         }
386
387       g = new_gear (mi, parent);
388       if (!g) return 0;  /* out of memory? */
389
390       if (place_gear (mi, g, parent))
391         break;
392     }
393
394   if (! g) return 0;
395
396   /* We got a gear, and it is properly positioned.
397      Insert it in the scene.
398    */
399   bp->gears[bp->ngears++] = g;
400   return g;
401 }
402
403
404 static int
405 arm (GLfloat length,
406      GLfloat width1, GLfloat height1,
407      GLfloat width2, GLfloat height2,
408      Bool wire)
409 {
410   int polys = 0;
411   glShadeModel(GL_FLAT);
412
413 #if 0  /* don't need these - they're embedded in other objects */
414   /* draw end 1 */
415   glFrontFace(GL_CW);
416   glNormal3f(-1, 0, 0);
417   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
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   glVertex3f(-length/2, -width1/2,  height1/2);
422   polys++;
423   glEnd();
424
425   /* draw end 2 */
426   glFrontFace(GL_CCW);
427   glNormal3f(1, 0, 0);
428   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
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   glVertex3f(length/2, -width2/2,  height2/2);
433   polys++;
434   glEnd();
435 #endif
436
437   /* draw top */
438   glFrontFace(GL_CCW);
439   glNormal3f(0, 0, -1);
440   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
441   glVertex3f(-length/2, -width1/2, -height1/2);
442   glVertex3f(-length/2,  width1/2, -height1/2);
443   glVertex3f( length/2,  width2/2, -height2/2);
444   glVertex3f( length/2, -width2/2, -height2/2);
445   polys++;
446   glEnd();
447
448   /* draw bottom */
449   glFrontFace(GL_CW);
450   glNormal3f(0, 0, 1);
451   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
452   glVertex3f(-length/2, -width1/2, height1/2);
453   glVertex3f(-length/2,  width1/2, height1/2);
454   glVertex3f( length/2,  width2/2, height2/2);
455   glVertex3f( length/2, -width2/2, height2/2);
456   polys++;
457   glEnd();
458
459   /* draw left */
460   glFrontFace(GL_CW);
461   glNormal3f(0, -1, 0);
462   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
463   glVertex3f(-length/2, -width1/2, -height1/2);
464   glVertex3f(-length/2, -width1/2,  height1/2);
465   glVertex3f( length/2, -width2/2,  height2/2);
466   glVertex3f( length/2, -width2/2, -height2/2);
467   polys++;
468   glEnd();
469
470   /* draw right */
471   glFrontFace(GL_CCW);
472   glNormal3f(0, 1, 0);
473   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
474   glVertex3f(-length/2,  width1/2, -height1/2);
475   glVertex3f(-length/2,  width1/2,  height1/2);
476   glVertex3f( length/2,  width2/2,  height2/2);
477   glVertex3f( length/2,  width2/2, -height2/2);
478   polys++;
479   glEnd();
480
481   glFrontFace(GL_CCW);
482
483   return polys;
484 }
485
486
487 static int
488 ctube (GLfloat diameter, GLfloat width, Bool wire)
489 {
490   tube (0, 0,  width/2,
491         0, 0, -width/2,
492         diameter, 0, 
493         32, True, True, wire);
494   return 0; /* #### */
495 }
496
497 static void
498 armature (ModeInfo *mi)
499 {
500   gears_configuration *bp = &bps[MI_SCREEN(mi)];
501   int wire = MI_IS_WIREFRAME(mi);
502
503   static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
504   GLfloat shiny = 128.0;
505   GLfloat color[4];
506
507   color[0] = 0.5 + frand(0.5);
508   color[1] = 0.5 + frand(0.5);
509   color[2] = 0.5 + frand(0.5);
510   color[3] = 1.0;
511
512   bp->armature_polygons = 0;
513
514   bp->armature_dlist = glGenLists (1);
515   if (! bp->armature_dlist)
516     {
517       check_gl_error ("glGenLists");
518       abort();
519     }
520
521   glNewList (bp->armature_dlist, GL_COMPILE);
522
523   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
524   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
525   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
526   glColor3f (color[0], color[1], color[2]);
527
528   glPushMatrix();
529
530   {
531     GLfloat s = bp->gears[0]->r * 2.7;
532     s = s/5.6;
533     glScalef (s, s, s);
534   }
535
536   glTranslatef (0, 0, 1.4 + bp->gears[0]->thickness);
537   glRotatef (30, 0, 0, 1);
538
539   bp->armature_polygons += ctube (0.5, 10, wire);       /* center axle */
540
541   glPushMatrix();
542   glTranslatef(0.0, 4.2, -1);
543   bp->armature_polygons += ctube (0.5, 3, wire);       /* axle 1 */
544   glTranslatef(0, 0, 1.8);
545   bp->armature_polygons += ctube (0.7, 0.7, wire);
546   glPopMatrix();
547
548   glPushMatrix();
549   glRotatef(120, 0.0, 0.0, 1.0);
550   glTranslatef(0.0, 4.2, -1);
551   bp->armature_polygons += ctube (0.5, 3, wire);       /* axle 2 */
552   glTranslatef(0, 0, 1.8);
553   bp->armature_polygons += ctube (0.7, 0.7, wire);
554   glPopMatrix();
555
556   glPushMatrix();
557   glRotatef(240, 0.0, 0.0, 1.0);
558   glTranslatef(0.0, 4.2, -1);
559   bp->armature_polygons += ctube (0.5, 3, wire);       /* axle 3 */
560   glTranslatef(0, 0, 1.8);
561   bp->armature_polygons += ctube (0.7, 0.7, wire);
562   glPopMatrix();
563
564   glTranslatef(0, 0, 1.5);                            /* center disk */
565   bp->armature_polygons += ctube (1.5, 2, wire);
566
567   glPushMatrix();
568   glRotatef(270, 0, 0, 1);
569   glRotatef(-10, 0, 1, 0);
570   glTranslatef(-2.2, 0, 0);
571   bp->armature_polygons += arm (4.0, 1.0, 0.5,
572                                 2.0, 1.0, wire);        /* arm 1 */
573   glPopMatrix();
574
575   glPushMatrix();
576   glRotatef(30, 0, 0, 1);
577   glRotatef(-10, 0, 1, 0);
578   glTranslatef(-2.2, 0, 0);
579   bp->armature_polygons += arm (4.0, 1.0, 0.5,
580                                 2.0, 1.0, wire);        /* arm 2 */
581   glPopMatrix();
582
583   glPushMatrix();
584   glRotatef(150, 0, 0, 1);
585   glRotatef(-10, 0, 1, 0);
586   glTranslatef(-2.2, 0, 0);
587   bp->armature_polygons += arm (4.0, 1.0, 0.5,
588                                 2.0, 1.0, wire);        /* arm 3 */
589   glPopMatrix();
590
591   glPopMatrix();
592
593   glEndList ();
594 }
595
596
597 static void
598 planetary_gears (ModeInfo *mi)
599 {
600   gears_configuration *bp = &bps[MI_SCREEN(mi)];
601   gear *g0, *g1, *g2, *g3, *g4;
602   GLfloat distance = 2.02;
603
604   bp->planetary_p = True;
605
606   g0 = new_gear (mi, 0);
607   g1 = new_gear (mi, 0);
608   g2 = new_gear (mi, 0);
609   g3 = new_gear (mi, 0);
610   g4 = new_gear (mi, 0);
611
612   if (! place_gear (mi, g0, 0)) abort();
613   if (! place_gear (mi, g1, 0)) abort();
614   if (! place_gear (mi, g2, 0)) abort();
615   if (! place_gear (mi, g3, 0)) abort();
616   if (! place_gear (mi, g4, 0)) abort();
617
618   g0->nteeth = 12 + (3 * (random() % 10));  /* must be multiple of 3 */
619   g0->tooth_w = g0->r / g0->nteeth;
620   g0->tooth_h = g0->tooth_w * 2.8;
621
622 # define COPY(F) g4->F = g3->F = g2->F = g1->F = g0->F
623   COPY(r);
624   COPY(th);
625   COPY(nteeth);
626   COPY(tooth_w);
627   COPY(tooth_h);
628   COPY(tooth_slope);
629   COPY(inner_r);
630   COPY(inner_r2);
631   COPY(inner_r3);
632   COPY(thickness);
633   COPY(thickness2);
634   COPY(thickness3);
635   COPY(ratio);
636   COPY(size);
637 # undef COPY
638
639   g1->x = cos (M_PI * 2 / 3) * g1->r * distance;
640   g1->y = sin (M_PI * 2 / 3) * g1->r * distance;
641
642   g2->x = cos (M_PI * 4 / 3) * g2->r * distance;
643   g2->y = sin (M_PI * 4 / 3) * g2->r * distance;
644
645   g3->x = cos (M_PI * 6 / 3) * g3->r * distance;
646   g3->y = sin (M_PI * 6 / 3) * g3->r * distance;
647
648   g4->x = 0;
649   g4->y = 0;
650   g4->th = -g3->th;
651
652   /* rotate central gear 1/2 tooth-size if odd number of teeth */
653   if (g4->nteeth & 1)
654     g4->th -= (180.0 / g4->nteeth);
655
656   g0->inverted_p  = True;
657   g0->x           = 0;
658   g0->y           = 0;
659   g0->nteeth      = g1->nteeth * 3;
660   g0->r           = g1->r * 3.05;
661   g0->inner_r     = g0->r * 0.8;
662   g0->inner_r2    = 0;
663   g0->inner_r3    = 0;
664   g0->th          = g1->th + (180 / g0->nteeth);
665   g0->ratio       = g1->ratio / 3;
666
667   g0->tooth_slope = 0;
668   g0->nubs        = 3;
669   g0->spokes      = 0;
670   g0->size        = INVOLUTE_LARGE;
671
672   bp->gears = (gear **) calloc (6, sizeof(**bp->gears));
673   bp->ngears = 0;
674
675   bp->gears[bp->ngears++] = g1;
676   bp->gears[bp->ngears++] = g2;
677   bp->gears[bp->ngears++] = g3;
678   bp->gears[bp->ngears++] = g4;
679   bp->gears[bp->ngears++] = g0;
680 }
681
682
683
684
685 ENTRYPOINT void 
686 init_gears (ModeInfo *mi)
687 {
688   gears_configuration *bp;
689   int wire = MI_IS_WIREFRAME(mi);
690   int i;
691
692   if (!bps) {
693     bps = (gears_configuration *)
694       calloc (MI_NUM_SCREENS(mi), sizeof (gears_configuration));
695     if (!bps) {
696       fprintf(stderr, "%s: out of memory\n", progname);
697       exit(1);
698     }
699   }
700
701   bp = &bps[MI_SCREEN(mi)];
702
703   bp->glx_context = init_GL(mi);
704
705   reshape_gears (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
706
707   if (!wire)
708     {
709       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
710       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
711       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
712       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
713
714       glEnable(GL_LIGHTING);
715       glEnable(GL_LIGHT0);
716       glEnable(GL_DEPTH_TEST);
717       glEnable(GL_CULL_FACE);
718
719       glLightfv(GL_LIGHT0, GL_POSITION, pos);
720       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
721       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
722       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
723     }
724
725   if (! bp->rot)
726     {
727       double spin_speed   = 0.5;
728       double wander_speed = 0.01;
729       double spin_accel   = 0.25;
730
731       bp->rot = make_rotator (do_spin ? spin_speed : 0,
732                               do_spin ? spin_speed : 0,
733                               do_spin ? spin_speed : 0,
734                               spin_accel,
735                               do_wander ? wander_speed : 0,
736                               True
737                               );
738       bp->trackball = gltrackball_init (True);
739     }
740
741   if (bp->gears)
742     {
743       for (i = 0; i < bp->ngears; i++)
744         free_gear (bp->gears[i]);
745       free (bp->gears);
746       bp->gears = 0;
747       bp->ngears = 0;
748     }
749
750   if (!(random() % 8))
751     {
752       planetary_gears (mi);
753     }
754   else
755     {
756       gear *g = 0;
757       int total_gears = MI_COUNT (mi);
758
759       bp->planetary_p = False;
760
761       if (total_gears <= 0)
762         total_gears = 3 + abs (BELLRAND (8) - 4);  /* 3 - 7, mostly 3. */
763       bp->gears = (gear **) calloc (total_gears+2, sizeof(**bp->gears));
764       bp->ngears = 0;
765
766       for (i = 0; i < total_gears; i++)
767         g = place_new_gear (mi, g);
768     }
769
770
771   /* Center gears in scene. */
772   {
773     GLfloat minx=99999, miny=99999, maxx=-99999, maxy=-99999;
774     int i;
775     for (i = 0; i < bp->ngears; i++)
776       {
777         gear *g = bp->gears[i];
778         if (g->x - g->r < minx) minx = g->x - g->r;
779         if (g->x + g->r > maxx) maxx = g->x + g->r;
780         if (g->y - g->r < miny) miny = g->y - g->r;
781         if (g->y + g->r > maxy) maxy = g->y + g->r;
782       }
783     bp->bbox.x1 = minx;
784     bp->bbox.y1 = miny;
785     bp->bbox.x2 = maxx;
786     bp->bbox.y2 = maxy;
787   }
788
789   /* Now render each gear into its display list.
790    */
791   for (i = 0; i < bp->ngears; i++)
792     {
793       gear *g = bp->gears[i];
794       g->dlist = glGenLists (1);
795       if (! g->dlist)
796         {
797           check_gl_error ("glGenLists");
798           abort();
799         }
800
801       glNewList (g->dlist, GL_COMPILE);
802       g->polygons += draw_involute_gear (g, wire);
803       glEndList ();
804     }
805   if (bp->planetary_p)
806     armature (mi);
807 }
808
809
810 ENTRYPOINT void
811 draw_gears (ModeInfo *mi)
812 {
813   gears_configuration *bp = &bps[MI_SCREEN(mi)];
814   Display *dpy = MI_DISPLAY(mi);
815   Window window = MI_WINDOW(mi);
816   int i;
817
818   if (!bp->glx_context)
819     return;
820
821   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
822
823   glShadeModel(GL_SMOOTH);
824
825   glEnable(GL_DEPTH_TEST);
826   glEnable(GL_NORMALIZE);
827   glEnable(GL_CULL_FACE);
828
829   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
830
831   glPushMatrix ();
832
833   {
834     double x, y, z;
835     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
836     glTranslatef ((x - 0.5) * 4,
837                   (y - 0.5) * 4,
838                   (z - 0.5) * 7);
839
840     gltrackball_rotate (bp->trackball);
841
842     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
843
844     /* add a little rotation for -no-spin mode */
845     x -= 0.14;
846     y -= 0.06;
847
848     glRotatef (x * 360, 1.0, 0.0, 0.0);
849     glRotatef (y * 360, 0.0, 1.0, 0.0);
850     glRotatef (z * 360, 0.0, 0.0, 1.0);
851   }
852
853   /* Center the scene's bounding box in the window,
854      and scale it to fit. 
855    */
856   {
857     GLfloat w = bp->bbox.x2 - bp->bbox.x1;
858     GLfloat h = bp->bbox.y2 - bp->bbox.y1;
859     GLfloat s = 10.0 / (w > h ? w : h);
860     glScalef (s, s, s);
861     glTranslatef (-(bp->bbox.x1 + w/2),
862                   -(bp->bbox.y1 + h/2),
863                   0);
864   }
865
866   mi->polygon_count = 0;
867
868   for (i = 0; i < bp->ngears; i++)
869     {
870       gear *g = bp->gears[i];
871
872       glPushMatrix();
873
874       glTranslatef (g->x, g->y, g->z);
875       glRotatef (g->th, 0, 0, 1);
876
877       glCallList (g->dlist);
878       mi->polygon_count += g->polygons;
879
880       glPopMatrix ();
881     }
882
883   if (bp->planetary_p)
884     {
885       glCallList (bp->armature_dlist);
886       mi->polygon_count += bp->armature_polygons;
887     }
888
889   glPopMatrix ();
890
891   /* spin gears */
892   if (!bp->button_down_p)
893     for (i = 0; i < bp->ngears; i++)
894       {
895         gear *g = bp->gears[i];
896         double off = g->ratio * 5 * speed;
897         if (g->th > 0)
898           g->th += off;
899         else
900           g->th -= off;
901       }
902
903   if (mi->fps_p) do_fps (mi);
904   glFinish();
905
906   glXSwapBuffers(dpy, window);
907 }
908
909 ENTRYPOINT Bool
910 gears_handle_event (ModeInfo *mi, XEvent *event)
911 {
912   gears_configuration *bp = &bps[MI_SCREEN(mi)];
913
914   if (gltrackball_event_handler (event, bp->trackball,
915                                  MI_WIDTH (mi), MI_HEIGHT (mi),
916                                  &bp->button_down_p))
917     return True;
918   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
919     {
920       init_gears (mi);
921       return True;
922     }
923
924   return False;
925 }
926
927 XSCREENSAVER_MODULE ("Gears", gears)
928
929 #endif /* USE_GL */