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