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