From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / geodesicgears.c
1 /* geodesicgears, Copyright (c) 2014-2015 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  * Inspired by http://bugman123.com/Gears/
12  * and by http://kennethsnelson.net/PortraitOfAnAtom.pdf
13  */
14
15 #define DEFAULTS        "*delay:        30000       \n" \
16                         "*count:        4           \n" \
17                         "*wireframe:    False       \n" \
18                         "*showFPS:      False       \n" \
19                         "*texFontCacheSize: 100     \n" \
20                         "*suppressRotationAnimation: True\n" \
21                 "*font:  -*-helvetica-medium-r-normal-*-*-160-*-*-*-*-*-*\n" \
22
23 # define refresh_geodesic 0
24 # define release_geodesic 0
25 #undef countof
26 #define countof(x) (sizeof((x))/sizeof((*x)))
27
28 #include "xlockmore.h"
29 #include "involute.h"
30 #include "colors.h"
31 #include "normals.h"
32 #include "rotator.h"
33 #include "gltrackball.h"
34 #include <ctype.h>
35 #include "texfont.h"
36
37
38 #ifdef USE_GL /* whole file */
39
40 #include "gllist.h"
41
42 #define DEF_SPIN        "True"
43 #define DEF_WANDER      "True"
44 #define DEF_SPEED       "1.0"
45 #define DEF_LABELS      "False"
46 #define DEF_NUMBERS     "False"
47 #define DEF_TIMEOUT     "20"
48
49 typedef struct { double a, o; } LL;     /* latitude + longitude */
50
51 /* 10:6 is a mismesh. */
52
53 static const struct {
54   enum { PRISM, OCTO, DECA, G14, G18, G32, G92, G182 } type;
55   const GLfloat args[5];
56 } gear_templates[] = {
57   { PRISM },
58   { OCTO },
59   { DECA },
60   { G14 },
61   { G18 },
62   { G32, { 15, 6,  0.4535 }},           /* teeth1, teeth2, radius1 */
63   { G32, { 15, 12, 0.3560 }},
64   { G32, { 20, 6,  0.4850 }},
65   { G32, { 20, 12, 0.3995 }},           /* double of 10:6 */
66   { G32, { 20, 18, 0.3375 }},
67   { G32, { 25, 6,  0.5065 }},
68   { G32, { 25, 12, 0.4300 }},
69   { G32, { 25, 18, 0.3725 }},
70   { G32, { 25, 24, 0.3270 }},
71   { G32, { 30, 12, 0.4535 }},           /* double of 15:6 */
72   { G32, { 30, 18, 0.3995 }},
73   { G32, { 30, 24, 0.3560 }},           /* double of 15:12 */
74   { G32, { 30, 30, 0.3205 }},
75   { G32, { 35, 12, 0.4710 }},
76   { G32, { 35, 18, 0.4208 }},
77   { G32, { 35, 24, 0.3800 }},
78   { G32, { 35, 30, 0.3450 }},
79   { G32, { 35, 36, 0.3160 }},
80   { G32, { 40, 12, 0.4850 }},           /* double of 20:6 */
81   { G32, { 40, 24, 0.3995 }},           /* double of 10:6, 20:12 */
82 /*{ G32, { 40, 36, 0.3375 }},*/         /* double of 20:18 */
83   { G32, { 50, 12, 0.5065 }},           /* double of 25:6 */
84   { G32, { 50, 24, 0.4300 }},           /* double of 25:12 */
85
86   /* These all have phase errors and don't always mesh properly.
87      Maybe we should just omit them? */
88
89   { G92, { 35, 36, 16, 0.2660, 0.366 }},        /* teeth1, 2, 3, r1, pitch3 */
90   { G92, { 25, 36, 11, 0.2270, 0.315 }},
91 /*{ G92, { 15, 15,  8, 0.2650, 0.356 }},*/
92 /*{ G92, { 20, 21,  8, 0.2760, 0.355 }},*/
93   { G92, { 25, 27, 16, 0.2320, 0.359 }},
94   { G92, { 20, 36, 11, 0.1875, 0.283 }},
95   { G92, { 30, 30, 16, 0.2585, 0.374 }},        /* double of 15:15:8 */
96   { G92, { 20, 33, 11, 0.1970, 0.293 }},
97 /*{ G92, { 10, 12,  8, 0.2030, 0.345 }},*/
98   { G92, { 30, 33, 16, 0.2455, 0.354 }},
99 /*{ G92, { 25, 24,  8, 0.3050, 0.375 }},*/
100   { G92, { 20, 24, 16, 0.2030, 0.346 }},
101 };
102
103
104 typedef struct sphere_gear sphere_gear;
105 struct sphere_gear {
106   int id;                       /* name, for debugging */
107   XYZ axis;                     /* the vector on which this gear's axis lies */
108   int direction;                /* rotation, +1 or -1 */
109   GLfloat offset;               /* rotational degrees from parent gear */
110   sphere_gear *parent;          /* gear driving this one, or 0 for root */
111   sphere_gear **children;       /* gears driven by this one (no loops) */
112   sphere_gear **neighbors;      /* gears touching this one (circular!) */
113   int nchildren, children_size;
114   int nneighbors, neighbors_size;
115   const gear *g;                /* shape of this gear (shared) */
116 };
117
118
119 typedef struct {
120   GLXContext *glx_context;
121   rotator *rot;
122   trackball_state *trackball;
123   Bool button_down_p;
124   int ncolors;
125   XColor *colors;
126   GLfloat color1[4], color2[4];
127   texture_font_data *font;
128
129   int nshapes, shapes_size;     /* how many 'gear' objects there are */
130   int ngears, gears_size;       /* how many 'sphere_gear' objects there are */
131   gear *shapes;
132   sphere_gear *gears;
133
134   int which;
135   int mode;  /* 0 = normal, 1 = out, 2 = in */
136   int mode_tick;
137   int next;  /* 0 = random, -1 = back, 1 = forward */
138   time_t draw_time;
139   int draw_tick;
140   char *desc;
141
142   GLfloat th;           /* rotation of the root sphere_gear in degrees. */
143
144 } geodesic_configuration;
145
146 static geodesic_configuration *bps = NULL;
147
148 static int timeout;
149 static Bool do_spin;
150 static GLfloat speed;
151 static Bool do_wander;
152 static Bool do_labels;
153 static Bool do_numbers;
154
155 static XrmOptionDescRec opts[] = {
156   { "-spin",      ".spin",   XrmoptionNoArg, "True"  },
157   { "+spin",      ".spin",   XrmoptionNoArg, "False" },
158   { "-speed",     ".speed",  XrmoptionSepArg, 0      },
159   { "-wander",    ".wander", XrmoptionNoArg, "True"  },
160   { "+wander",    ".wander", XrmoptionNoArg, "False" },
161   { "-labels",    ".labels", XrmoptionNoArg, "True"  },
162   { "+labels",    ".labels", XrmoptionNoArg, "False" },
163   { "-numbers",   ".numbers",XrmoptionNoArg, "True"  },
164   { "+numbers",   ".numbers",XrmoptionNoArg, "False" },
165   { "-timeout",   ".timeout",XrmoptionSepArg, 0 },
166 };
167
168 static argtype vars[] = {
169   {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_Bool},
170   {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
171   {&speed,     "speed",  "Speed",  DEF_SPEED,  t_Float},
172   {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
173   {&do_numbers,"numbers","Numbers",DEF_NUMBERS,t_Bool},
174   {&timeout,   "timeout","Seconds",DEF_TIMEOUT,t_Int},
175 };
176
177 ENTRYPOINT ModeSpecOpt geodesic_opts = {
178   countof(opts), opts, countof(vars), vars, NULL};
179
180
181 #undef BELLRAND
182 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
183
184
185
186 static XYZ
187 cross_product (XYZ a, XYZ b)
188 {
189   XYZ c;
190   c.x = (a.y * b.z) - (a.z * b.y);
191   c.y = (a.z * b.x) - (a.x * b.z);
192   c.z = (a.x * b.y) - (a.y * b.x);
193   return c;
194 }
195
196
197 static GLfloat
198 dot_product (XYZ a, XYZ b)
199 {
200   return (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
201 }
202
203
204 static XYZ
205 normalize (XYZ v)
206 {
207   GLfloat d = sqrt ((v.x * v.x) + (v.y * v.y) + (v.z * v.z));
208   if (d == 0)
209     v.x = v.y = v.z = 0;
210   else
211     {
212       v.x /= d;
213       v.y /= d;
214       v.z /= d;
215     }
216   return v;
217 }
218
219
220 static XYZ
221 polar_to_cartesian (LL v)
222 {
223   XYZ p;
224   p.x = cos (v.a) * cos (v.o);
225   p.y = cos (v.a) * sin (v.o);
226   p.z = sin (v.a);
227   return p;
228 }
229
230
231
232
233 static gear *
234 add_gear_shape (ModeInfo *mi, GLfloat radius, int teeth)
235 {
236   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
237   int wire = MI_IS_WIREFRAME(mi);
238   gear *g;
239   int i;
240
241   if (bp->nshapes >= bp->shapes_size - 1)
242     {
243       bp->shapes_size = bp->shapes_size * 1.2 + 4;
244       bp->shapes = (gear *)
245         realloc (bp->shapes, bp->shapes_size * sizeof(*bp->shapes));
246     }
247   g = &bp->shapes[bp->nshapes++];
248
249   memset (g, 0, sizeof(*g));
250
251   g->r = radius;
252   g->nteeth = teeth;
253   g->ratio = 1;
254
255   g->tooth_h = g->r / (teeth * 0.4);
256
257   if (g->tooth_h > 0.06)  /* stubbier teeth when small tooth count. */
258     g->tooth_h *= 0.6;
259
260   g->thickness  = 0.05 + BELLRAND(0.15);
261   g->thickness2 = g->thickness / 4;
262   g->thickness3 = g->thickness;
263   g->size       = wire ? INVOLUTE_SMALL : INVOLUTE_LARGE;
264
265   /* Move the disc's origin inward to make the edge of the disc be tangent
266      to the unit sphere. */
267   g->z = 1 - sqrt (1 - (g->r * g->r));
268
269   /* #### This isn't quite right */
270   g->tooth_slope = 1 + ((g->z * 2) / g->r);
271
272
273   /* Decide on shape of gear interior:
274      - just a ring with teeth;
275      - that, plus a thinner in-set "plate" in the middle;
276      - that, plus a thin raised "lip" on the inner plate;
277      - or, a wide lip (really, a thicker third inner plate.)
278    */
279   if (wire)
280     ;
281   else if ((random() % 10) == 0)
282     {
283       /* inner_r can go all the way in; there's no inset disc. */
284       g->inner_r = (g->r * 0.3) + frand((g->r - g->tooth_h/2) * 0.6);
285       g->inner_r2 = 0;
286       g->inner_r3 = 0;
287     }
288   else
289     {
290       /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
291       g->inner_r  = (g->r * 0.5)  + frand((g->r - g->tooth_h) * 0.4);
292       g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
293       g->inner_r3 = 0;
294
295       if (g->inner_r2 > (g->r * 0.2))
296         {
297           int nn = (random() % 10);
298           if (nn <= 2)
299             g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
300           else if (nn <= 7 && g->inner_r2 >= 0.1)
301             g->inner_r3 = g->inner_r2 - 0.01;
302         }
303     }
304
305   /* If we have three discs, sometimes make the middle disc be spokes.
306    */
307   if (g->inner_r3 && ((random() % 5) == 0))
308     {
309       g->spokes = 2 + BELLRAND (5);
310       g->spoke_thickness = 1 + frand(7.0);
311       if (g->spokes == 2 && g->spoke_thickness < 2)
312         g->spoke_thickness += 1;
313     }
314
315   /* Sometimes add little nubbly bits, if there is room.
316    */
317   if (!wire && g->nteeth > 5)
318     {
319       double size = 0;
320       involute_biggest_ring (g, 0, &size, 0);
321       if (size > g->r * 0.2 && (random() % 5) == 0)
322         {
323           g->nubs = 1 + (random() % 16);
324           if (g->nubs > 8) g->nubs = 1;
325         }
326     }
327
328   /* Decide how complex the polygon model should be.
329    */
330   {
331     double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
332     if (pix <= 4)        g->size = INVOLUTE_SMALL;
333     else if (pix <= 8)   g->size = INVOLUTE_MEDIUM;
334     else if (pix <= 30)  g->size = INVOLUTE_LARGE;
335     else                 g->size = INVOLUTE_HUGE;
336   }
337
338   if (g->inner_r3 > g->inner_r2) abort();
339   if (g->inner_r2 > g->inner_r) abort();
340   if (g->inner_r  > g->r) abort();
341
342   i = random() % bp->ncolors;
343   g->color[0] = bp->colors[i].red    / 65536.0;
344   g->color[1] = bp->colors[i].green  / 65536.0;
345   g->color[2] = bp->colors[i].blue   / 65536.0;
346   g->color[3] = 1;
347
348   i = (i + bp->ncolors / 2) % bp->ncolors;
349   g->color2[0] = bp->colors[i].red    / 65536.0;
350   g->color2[1] = bp->colors[i].green  / 65536.0;
351   g->color2[2] = bp->colors[i].blue   / 65536.0;
352   g->color2[3] = 1;
353
354   g->dlist = glGenLists (1);
355   glNewList (g->dlist, GL_COMPILE);
356
357 #if 1
358   {
359     gear G, *g2 = &G;
360     *g2 = *g;
361
362     /* Move the gear inward so that its outer edge is on the disc, instead
363        of its midpoint. */
364     g2->z += g2->thickness/2;
365
366     /* 'radius' is at the surface but 'g->r' is at the center, so we need
367        to reverse the slope computation that involute.c does. */
368     g2->r /= (1 + (g2->thickness * g2->tooth_slope / 2));
369
370     glPushMatrix();
371     glTranslatef(g2->x, g2->y, -g2->z);
372
373     /* Line up the center of the point of tooth 0 with "up". */
374     glRotatef (90, 0, 0, 1);
375     glRotatef (180, 0, 1, 0);
376     glRotatef (-360.0 / g2->nteeth / 4, 0, 0, 1);
377
378     g->polygons = draw_involute_gear (g2, wire);
379     glPopMatrix();
380   }
381 # else  /* draw discs */
382   {
383     glPushMatrix();
384     glTranslatef(g->x, g->y, -g->z);
385     glLineWidth (2);
386     glFrontFace (GL_CCW);
387     glNormal3f(0, 0, 1);
388     glColor3f(0, 0, 0);
389     glDisable (GL_LIGHTING);
390
391     glBegin(GL_LINES);
392     glVertex3f (0, 0, 0);
393     glVertex3f (0, radius, 0);
394     glEnd();
395
396     glColor3f(0.5, 0.5, 0.5);
397     glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
398     {
399       GLfloat th;
400       GLfloat step = M_PI * 2 / 128;
401       /* radius *= 1.005; */
402       glVertex3f (0, 0, 0);
403       for (th = 0; th < M_PI * 2 + step; th += step)
404         {
405           GLfloat x = cos(th) * radius;
406           GLfloat y = sin(th) * radius;
407           glVertex3f (x, y, 0);
408         }
409     }
410     glEnd();
411     if (!wire) glEnable(GL_LIGHTING);
412     glPopMatrix();
413   }
414 # endif /* 0 */
415
416   glEndList ();
417
418   return g;
419 }
420
421
422 static void
423 add_sphere_gear (ModeInfo *mi, gear *g, XYZ axis)
424 {
425   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
426   sphere_gear *gg;
427   int i;
428
429   axis = normalize (axis);
430
431   /* If there's already a gear on this axis, don't duplicate it. */
432   for (i = 0; i < bp->ngears; i++)
433     {
434       XYZ o = bp->gears[i].axis;
435       if (o.x == axis.x && o.y == axis.y && o.z == axis.z)
436         return;
437     }
438
439   if (bp->ngears >= bp->gears_size - 1)
440     {
441       bp->gears_size = bp->gears_size * 1.2 + 10;
442       bp->gears = (sphere_gear *)
443         realloc (bp->gears, bp->gears_size * sizeof(*bp->gears));
444     }
445
446   gg = &bp->gears[bp->ngears];
447   memset (gg, 0, sizeof(*gg));
448   gg->id = bp->ngears;
449   gg->axis = axis;
450   gg->direction = 0;
451   gg->g = g;
452   bp->ngears++;
453 }
454
455
456 static void
457 free_sphere_gears (ModeInfo *mi)
458 {
459   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
460   int i;
461   for (i = 0; i < bp->nshapes; i++)
462     {
463       if (bp->shapes[i].dlist)
464         glDeleteLists (bp->shapes[i].dlist, 1);
465     }
466   free (bp->shapes);
467   bp->nshapes = 0;
468   bp->shapes_size = 0;
469   bp->shapes = 0;
470
471   for (i = 0; i < bp->ngears; i++)
472     {
473       sphere_gear *g = &bp->gears[i];
474       if (g->children)
475         free (g->children);
476       if (g->neighbors)
477         free (g->neighbors);
478     }
479   free (bp->gears);
480   bp->ngears = 0;
481   bp->gears_size = 0;
482   bp->gears = 0;
483 }
484
485
486
487 /* Is the gear a member of the list?
488  */
489 static Bool
490 gear_list_member (sphere_gear *g, sphere_gear **list, int count)
491 {
492   int i;
493   for (i = 0; i < count; i++)
494     if (list[i] == g) return True;
495   return False;
496 }
497
498
499 /* Add the gear to the list, resizing it as needed.
500  */
501 static void
502 gear_list_push (sphere_gear *g,
503                 sphere_gear ***listP,
504                 int *countP, int *sizeP)
505 {
506   if (*countP >= (*sizeP) - 1)
507     {
508       *sizeP = (*sizeP) * 1.2 + 4;
509       *listP = (sphere_gear **) realloc (*listP, (*sizeP) * sizeof(**listP));
510     }
511   (*listP)[*countP] = g;
512   (*countP)++;
513 }
514
515
516 /* Mark child and parent as being mutual neighbors.
517  */
518 static void
519 link_neighbors (sphere_gear *parent, sphere_gear *child)
520 {
521   if (child == parent) abort();
522
523   /* Add child to parent's list of neighbors */
524   if (! gear_list_member (child, parent->neighbors, parent->nneighbors))
525     {
526       gear_list_push (child,
527                       &parent->neighbors,
528                       &parent->nneighbors,
529                       &parent->neighbors_size);
530       /* fprintf(stderr, "neighbor %2d -> %2d  (%d)\n", parent->id, child->id,
531                 parent->nneighbors); */
532     }
533
534   /* Add parent to child's list of neighbors */
535   if (! gear_list_member (parent, child->neighbors, child->nneighbors))
536     {
537       gear_list_push (parent,
538                       &child->neighbors,
539                       &child->nneighbors,
540                       &child->neighbors_size);
541       /* fprintf(stderr, "neighbor %2d <- %2d\n", parent->id, child->id); */
542     }
543 }
544
545 /* Mark child as having parent, and vice versa.
546  */
547 static void
548 link_child (sphere_gear *parent, sphere_gear *child)
549 {
550   if (child == parent) abort();
551   if (child->parent) return;
552
553   gear_list_push (child,
554                   &parent->children,
555                   &parent->nchildren,
556                   &parent->children_size);
557   child->parent = parent;
558   /* fprintf(stderr, "child    %2d -> %2d  (%d)\n", parent->id, child->id,
559             parent->nchildren); */
560 }
561
562
563
564 static void link_children (sphere_gear *);
565
566 static void
567 link_children (sphere_gear *parent)
568 {
569   int i;
570 # if 1    /* depth first */
571   for (i = 0; i < parent->nneighbors; i++)
572     {
573       sphere_gear *child = parent->neighbors[i];
574       if (! child->parent)
575         {
576           link_child (parent, child);
577           link_children (child);
578         }
579     }
580 # else   /* breadth first */
581   for (i = 0; i < parent->nneighbors; i++)
582     {
583       sphere_gear *child = parent->neighbors[i];
584       if (! child->parent)
585         link_child (parent, child);
586     }
587   for (i = 0; i < parent->nchildren; i++)
588     {
589       sphere_gear *child = parent->children[i];
590       link_children (child);
591     }
592 # endif
593 }
594
595
596
597 /* Whether the two gears touch.
598  */
599 static Bool
600 gears_touch_p (ModeInfo *mi, sphere_gear *a, sphere_gear *b)
601 {
602   /* We need to know if the two discs on the surface overlap.
603
604      Find the angle between the axis of each disc, and a point on its edge:
605      the axis between the hypotenuse and adjacent of a right triangle between
606      the disc's radius and the origin.
607
608                       R
609                     _____
610                    |_|  /
611                    |   /
612                 1  |  /
613                    |t/    t = asin(R)
614                    |/
615
616      Find the angle between the axes of the two discs.
617
618                   |
619                   |    /    angle = acos (v1 dot v2)
620                 1 |   /     axis  = v1 cross v2
621                   |  /  1
622                   | /
623                   |/
624
625      If the sum of the first two angles is less than the third angle,
626      they touch.
627    */
628   XYZ p1 = a->axis;
629   XYZ p2 = b->axis;
630   double t1 = asin (a->g->r);
631   double t2 = asin (b->g->r);
632   double th = acos (dot_product (p1, p2));
633
634   return (t1 + t2 >= th);
635 }
636
637
638 /* Set the rotation direction for the gear and its kids.
639  */
640 static void
641 orient_gears (ModeInfo *mi, sphere_gear *g)
642 {
643   int i;
644   if (g->parent)
645     g->direction = -g->parent->direction;
646   for (i = 0; i < g->nchildren; i++)
647     orient_gears (mi, g->children[i]);
648 }
649
650
651 /* Returns the global model coordinates of the given tooth of a gear.
652  */
653 static XYZ
654 tooth_coords (const sphere_gear *s, int tooth)
655 {
656   const gear *g = s->g;
657   GLfloat off = s->offset * (M_PI / 180) * g->ratio * s->direction;
658   GLfloat th = (tooth * M_PI * 2 / g->nteeth) - off;
659   XYZ axis;
660   GLfloat angle;
661   XYZ from = { 0, 1, 0 };
662   XYZ to = s->axis;
663   XYZ p0, p1, p2;
664   GLfloat x, y, z, C, S, m[4][4];
665
666   axis  = cross_product (from, to);
667   angle = acos (dot_product (from, to));
668
669   p0 = normalize (axis);
670   x = p0.x;
671   y = p0.y;
672   z = p0.z;
673   C = cos(angle);
674   S = sin(angle);
675
676   /* this is what glRotatef does */
677   m[0][0] = x*x * (1 - C) + C;
678   m[1][0] = x*y * (1 - C) - z*S;
679   m[2][0] = x*z * (1 - C) + y*S;
680   m[3][0] = 0;
681
682   m[0][1] = y*x * (1 - C) + z*S;
683   m[1][1] = y*y * (1 - C) + C;
684   m[2][1] = y*z * (1 - C) - x*S;
685   m[3][1] = 0;
686
687   m[0][2] = x*z * (1 - C) - y*S;
688   m[1][2] = y*z * (1 - C) + x*S;
689   m[2][2] = z*z * (1 - C) + C;
690   m[3][2] = 0;
691
692   m[0][3] = 0;
693   m[1][3] = 0;
694   m[2][3] = 0;
695   m[3][3] = 1;
696
697   /* The point to transform */
698   p1.x = g->r * sin (th);
699   p1.z = g->r * cos (th);
700   p1.y = 1 - g->z;
701   p1 = normalize (p1);
702
703   /* transformation result */
704   p2.x = p1.x * m[0][0] + p1.y * m[1][0] + p1.z * m[2][0] + m[3][0];
705   p2.y = p1.x * m[0][1] + p1.y * m[1][1] + p1.z * m[2][1] + m[3][1];
706   p2.z = p1.x * m[0][2] + p1.y * m[1][2] + p1.z * m[2][2] + m[3][2];
707
708   return p2;
709 }
710
711
712 /* Returns the number of the tooth of the first gear that is closest
713    to any tooth of its parent.  Also the position of the parent tooth.
714  */
715 static int
716 parent_tooth (const sphere_gear *s, XYZ *parent)
717 {
718   const sphere_gear *s2 = s->parent;
719   int i, j;
720   GLfloat min_dist = 99999;
721   int min_tooth = 0;
722   XYZ min_parent = { 0, 0, 0 };
723
724   if (s2)
725     for (i = 0; i < s->g->nteeth; i++)
726       {
727         XYZ p1 = tooth_coords (s, i);
728         for (j = 0; j < s2->g->nteeth; j++)
729           {
730             XYZ p2 = tooth_coords (s2, j);
731             XYZ d;
732             GLfloat dist;
733             d.x = p1.x - p2.x;
734             d.y = p1.y - p2.y;
735             d.z = p1.z - p2.z;
736
737             dist = sqrt (d.x*d.x + d.y*d.y + d.z*d.z);
738             if (dist < min_dist)
739               {
740                 min_dist = dist;
741                 min_parent = p2;
742                 min_tooth = i;
743               }
744           }
745       }
746   *parent = min_parent;
747   return min_tooth;
748 }
749
750
751 /* Make all of the gear's children's teeth mesh properly.
752  */
753 static void align_gear_teeth (sphere_gear *s);
754 static void
755 align_gear_teeth (sphere_gear *s)
756 {
757   int i;
758   XYZ pc;
759
760   if (s->parent)
761     {
762       /* Iterate this gear's offset until we find a value for it that
763          minimizes the distance between this gear's parent-pointing
764          tooth, and the corresponding tooth on the parent.
765       */
766       int pt = parent_tooth (s, &pc);
767       GLfloat range = 360 / s->g->nteeth;
768       GLfloat steps = 64;
769       GLfloat min_dist = 999999;
770       GLfloat min_off = 0;
771       GLfloat off;
772
773       for (off = -range/2; off < range/2; off += range/steps)
774         {
775           XYZ tc, d;
776           GLfloat dist;
777           s->offset = off;
778           tc = tooth_coords (s, pt);
779           d.x = pc.x - tc.x;
780           d.y = pc.y - tc.y;
781           d.z = pc.z - tc.z;
782           dist = sqrt (d.x*d.x + d.y*d.y + d.z*d.z);
783           if (dist < min_dist)
784             {
785               min_dist = dist;
786               min_off = off;
787             }
788         }
789
790       s->offset = min_off;
791     }
792
793   /* Now do the children.  We have to do it in parent/child order because
794      the offset we just computed for the parent affects everyone downstream.
795    */
796   for (i = 0; i < s->nchildren; i++)
797     align_gear_teeth (s->children[i]);
798 }
799
800
801
802 static void
803 describe_gears (ModeInfo *mi)
804 {
805   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
806   int gears_per_teeth[1000];
807   int i;
808   int lines = 0;
809   memset (gears_per_teeth, 0, sizeof(gears_per_teeth));
810   for (i = 0; i < bp->ngears; i++)
811     gears_per_teeth[bp->gears[i].g->nteeth]++;
812   if (bp->desc) free (bp->desc);
813   bp->desc = (char *) malloc (80 * bp->ngears);
814   *bp->desc = 0;
815   for (i = 0; i < countof(gears_per_teeth); i++)
816     if (gears_per_teeth[i])
817       {
818         sprintf (bp->desc + strlen(bp->desc),
819                  "%s%d gears with %d teeth",
820                  (lines > 0 ? ",\n" : ""),
821                  gears_per_teeth[i], i);
822         lines++;
823       }
824   if (lines > 1)
825     sprintf (bp->desc + strlen(bp->desc), ",\n%d gears total", bp->ngears);
826   strcat (bp->desc, ".");
827 }
828
829
830 /* Takes the gears and makes an arbitrary DAG of them in order to compute
831    direction and gear ratios.
832  */
833 static void
834 sort_gears (ModeInfo *mi)
835 {
836   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
837   sphere_gear *root = 0;
838   int i, j;
839
840   /* For each gear, compare it against every other gear.
841      If they touch, mark them as being each others' neighbors.
842    */
843   for (i = 0; i < bp->ngears; i++)
844     {
845       sphere_gear *a = &bp->gears[i];
846       for (j = 0; j < bp->ngears; j++)
847         {
848           sphere_gear *b = &bp->gears[j];
849           if (a == b) continue;
850           if (gears_touch_p (mi, a, b))
851             link_neighbors (a, b);
852         }
853     }
854
855   bp->gears[0].parent = &bp->gears[0]; /* don't give this one a parent */
856   link_children (&bp->gears[0]);
857   bp->gears[0].parent = 0;
858
859
860 # if 0
861   for (i = 0; i < bp->ngears; i++)
862     {
863       fprintf (stderr, "%2d: p = %2d; k(%d, %d) = ",
864                i,
865                bp->gears[i].parent ? bp->gears[i].parent->id : -1,
866                bp->gears[i].nneighbors,
867                bp->gears[i].nchildren);
868       for (j = 0; j < bp->gears[i].nneighbors; j++)
869         fprintf (stderr, "%2d ", (int) bp->gears[i].neighbors[j]->id);
870       fprintf (stderr, "\t\t");
871       if (j < 5) fprintf (stderr, "\t");
872       for (j = 0; j < bp->gears[i].nchildren; j++)
873         fprintf (stderr, "%2d ", (int) bp->gears[i].children[j]->id);
874       fprintf (stderr,"\n");
875     }
876   fprintf (stderr,"\n");
877 # endif /* 0 */
878
879
880   /* If there is more than one gear with no parent, we fucked up. */
881
882   root = 0;
883   for (i = 0; i < bp->ngears; i++)
884     {
885       sphere_gear *g = &bp->gears[i];
886       if (!g->parent)
887         root = g;
888     }
889
890   if (! root) abort();
891
892   root->direction = 1;
893   orient_gears (mi, root);
894
895   /* If there are any gears with no direction, they aren't reachable. */
896   for (i = 0; i < bp->ngears; i++)
897     {
898       sphere_gear *g = &bp->gears[i];
899       if (g->direction == 0)
900         fprintf(stderr,"INTERNAL ERROR: unreachable: %d\n", g->id);
901     }
902
903   align_gear_teeth (root);
904   describe_gears (mi);
905 }
906
907
908 /* Create 5 identical gears arranged on the faces of a uniform
909    triangular prism.
910  */
911 static void
912 make_prism (ModeInfo *mi)
913 {
914   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
915   gear *g;
916   XYZ a;
917   int i;
918   int teeth = 4 * (4 + (int) (BELLRAND(20)));
919   if (teeth % 4) abort();  /* must be a multiple of 4 */
920
921   g = add_gear_shape (mi, 0.7075, teeth);
922
923   a.x = 0; a.y = 0; a.z = 1;
924   add_sphere_gear (mi, g, a);
925   a.z = -1;
926   add_sphere_gear (mi, g, a);
927
928   a.z = 0;
929   for (i = 0; i < 3; i++)
930     {
931       GLfloat th = i * M_PI * 2 / 3;
932       a.x = cos (th);
933       a.y = sin (th);
934       add_sphere_gear (mi, g, a);
935     }
936
937   if (bp->ngears != 5) abort();
938 }
939
940
941 /* Create 8 identical gears arranged on the faces of an octohedron
942    (or alternately, arranged on the diagonals of a cube.)
943  */
944 static void
945 make_octo (ModeInfo *mi)
946 {
947   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
948   static const XYZ verts[] = {{ -1, -1, -1 },
949                               { -1, -1,  1 },
950                               { -1,  1,  1 },
951                               { -1,  1, -1 },
952                               {  1, -1,  1 },
953                               {  1, -1, -1 },
954                               {  1,  1, -1 },
955                               {  1,  1,  1 }};
956   gear *g;
957   int i;
958   int teeth = 4 * (4 + (int) (BELLRAND(20)));
959   if (teeth % 4) abort();  /* must be a multiple of 4 */
960
961   g = add_gear_shape (mi, 0.578, teeth);
962   for (i = 0; i < countof(verts); i++)
963     add_sphere_gear (mi, g, verts[i]);
964
965   if (bp->ngears != 8) abort();
966 }
967
968
969 /* Create 10 identical gears arranged on the faces of ... something.
970    I'm not sure what polyhedron is the basis of this.
971  */
972 static void
973 make_deca (ModeInfo *mi)
974 {
975   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
976   gear *g;
977   XYZ a;
978   int i, j;
979   int teeth = 4 * (4 + (int) (BELLRAND(15)));
980   if (teeth % 4) abort();  /* must be a multiple of 4 */
981
982   g = add_gear_shape (mi, 0.5415, teeth);
983
984   a.x = 0; a.y = 0; a.z = 1;
985   add_sphere_gear (mi, g, a);
986   a.z = -1;
987   add_sphere_gear (mi, g, a);
988
989   for (j = -1; j <= 1; j += 2)
990     {
991       GLfloat off = (j < 0 ? 0 : M_PI / 4);
992       LL v;
993       v.a = j * M_PI * 0.136;  /* #### Empirical. What is this? */
994       for (i = 0; i < 4; i++)
995         {
996           v.o = i * M_PI / 2 + off;
997           a = polar_to_cartesian (v);
998           add_sphere_gear (mi, g, a);
999         }
1000     }
1001   if (bp->ngears != 10) abort();
1002 }
1003
1004
1005 /* Create 14 identical gears arranged on the faces of ... something.
1006    I'm not sure what polyhedron is the basis of this.
1007  */
1008 static void
1009 make_14 (ModeInfo *mi)
1010 {
1011   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1012   gear *g;
1013   XYZ a;
1014   int i;
1015   GLfloat r = 0.4610;
1016   int teeth = 6 * (2 + (int) (BELLRAND(4)));
1017   if (teeth % 6) abort();  /* must be a multiple of 6. I think? */
1018   /* mismeshes: 24 30 34 36 42 48 54 60 */
1019
1020   /* North, south */
1021   g = add_gear_shape (mi, r, teeth);
1022   a.x = 0; a.y = 0; a.z = 1;
1023   add_sphere_gear (mi, g, a);
1024   a.z = -1;
1025   add_sphere_gear (mi, g, a);
1026
1027   /* Equator */
1028   a.z = 0;
1029   for (i = 0; i < 4; i++)
1030     {
1031       GLfloat th = i * M_PI * 2 / 4 + (M_PI / 4);
1032       a.x = cos(th);
1033       a.y = sin(th);
1034       add_sphere_gear (mi, g, a);
1035     }
1036
1037   /* The other 8 */
1038   g = add_gear_shape (mi, r, teeth);
1039
1040   for (i = 0; i < 4; i++)
1041     {
1042       LL v;
1043       v.a = M_PI * 0.197;  /* #### Empirical.  Also, wrong.  What is this? */
1044       v.o = i * M_PI * 2 / 4;
1045       a = polar_to_cartesian (v);
1046       add_sphere_gear (mi, g, a);
1047       v.a = -v.a;
1048       a = polar_to_cartesian (v);
1049       add_sphere_gear (mi, g, a);
1050     }
1051
1052   if (bp->ngears != 14) abort();
1053 }
1054
1055
1056 /* Create 18 identical gears arranged on the faces of ... something.
1057    I'm not sure what polyhedron is the basis of this.
1058  */
1059 static void
1060 make_18 (ModeInfo *mi)
1061 {
1062   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1063   gear *g, *g2;
1064   XYZ a;
1065   int i;
1066   GLfloat r = 0.3830;
1067   int sizes[] = { 8, 12, 16, 20 };  /* 10, 14, 18, 26 and 34 don't work */
1068   int teeth = sizes[random() % countof(sizes)] * (1 + (random() % 4));
1069
1070   /* North, south */
1071   g = add_gear_shape (mi, r, teeth);
1072   a.x = 0; a.y = 0; a.z = 1;
1073   add_sphere_gear (mi, g, a);
1074   a.z = -1;
1075   add_sphere_gear (mi, g, a);
1076
1077   /* Equator */
1078   g2 = add_gear_shape (mi, r, teeth);
1079   a.z = 0;
1080   for (i = 0; i < 8; i++)
1081     {
1082       GLfloat th = i * M_PI * 2 / 8 + (M_PI / 4);
1083       a.x = cos(th);
1084       a.y = sin(th);
1085       add_sphere_gear (mi, (i & 1 ? g : g2), a);
1086     }
1087
1088   /* The other 16 */
1089   g = add_gear_shape (mi, r, teeth);
1090
1091   for (i = 0; i < 4; i++)
1092     {
1093       LL v;
1094       v.a = M_PI * 0.25;
1095       v.o = i * M_PI * 2 / 4;
1096       a = polar_to_cartesian (v);
1097       add_sphere_gear (mi, g, a);
1098       v.a = -v.a;
1099       a = polar_to_cartesian (v);
1100       add_sphere_gear (mi, g, a);
1101     }
1102
1103   if (bp->ngears != 18) abort();
1104 }
1105
1106
1107 /* Create 32 gears arranged along a truncated icosahedron:
1108    One gear on each of the 20 faces, and one on each of the 12 vertices.
1109  */
1110 static void
1111 make_32 (ModeInfo *mi, const GLfloat *args)
1112 {
1113   /* http://bugman123.com/Gears/32GearSpheres/ */
1114   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1115   GLfloat th0 = atan (0.5);  /* lat division: 26.57 deg */
1116   GLfloat s = M_PI / 5;      /* lon division: 72 deg    */
1117   int i;
1118
1119   int teeth1 = args[0];
1120   int teeth2 = args[1];
1121   GLfloat r1 = args[2];
1122   GLfloat ratio = teeth2 / (GLfloat) teeth1;
1123   GLfloat r2 = r1 * ratio;
1124
1125   gear *gear1, *gear2;
1126
1127   if (teeth1 % 5) abort();
1128   if (teeth2 % 6) abort();
1129
1130   gear1 = add_gear_shape (mi, r1, teeth1);
1131   gear2 = add_gear_shape (mi, r2, teeth2);
1132   gear2->ratio = 1 / ratio;
1133
1134   {
1135     XYZ a = { 0, 0, 1 };
1136     XYZ b = { 0, 0, -1 };
1137     add_sphere_gear (mi, gear1, a);
1138     add_sphere_gear (mi, gear1, b);
1139   }
1140
1141   for (i = 0; i < 10; i++)
1142     {
1143       GLfloat th1 = s * i;
1144       GLfloat th2 = s * (i+1);
1145       GLfloat th3 = s * (i+2);
1146       LL v1, v2, v3, vc;
1147       XYZ p1, p2, p3, pc, pc2;
1148       v1.a = th0;    v1.o = th1;
1149       v2.a = th0;    v2.o = th3;
1150       v3.a = -th0;   v3.o = th2;
1151       vc.a = M_PI/2; vc.o = th2;
1152
1153       if (! (i & 1)) /* southern hemisphere */
1154         {
1155           v1.a = -v1.a;
1156           v2.a = -v2.a;
1157           v3.a = -v3.a;
1158           vc.a = -vc.a;
1159         }
1160
1161       p1 = polar_to_cartesian (v1);
1162       p2 = polar_to_cartesian (v2);
1163       p3 = polar_to_cartesian (v3);
1164       pc = polar_to_cartesian (vc);
1165
1166       /* Two faces: 123 and 12c. */
1167
1168       add_sphere_gear (mi, gear1, p1);  /* left shared point of 2 triangles */
1169
1170       pc2.x = (p1.x + p2.x + p3.x) / 3; /* center of bottom triangle */
1171       pc2.y = (p1.y + p2.y + p3.y) / 3;
1172       pc2.z = (p1.z + p2.z + p3.z) / 3;
1173       add_sphere_gear (mi, gear2, pc2);
1174
1175       pc2.x = (p1.x + p2.x + pc.x) / 3; /* center of top triangle */
1176       pc2.y = (p1.y + p2.y + pc.y) / 3;
1177       pc2.z = (p1.z + p2.z + pc.z) / 3;
1178       add_sphere_gear (mi, gear2, pc2);
1179     }
1180
1181   if (bp->ngears != 32) abort();
1182 }
1183
1184
1185 /* Create 92 gears arranged along a geodesic sphere: 20 + 12 + 60.
1186    (frequency 3v, class-I geodesic tessellation of an icosahedron)
1187  */
1188 static void
1189 make_92 (ModeInfo *mi, const GLfloat *args)
1190 {
1191   /* http://bugman123.com/Gears/92GearSpheres/ */
1192   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1193   GLfloat th0 = atan (0.5);  /* lat division: 26.57 deg */
1194   GLfloat s = M_PI / 5;      /* lon division: 72 deg    */
1195   int i;
1196
1197   int tscale = 2;       /* These don't mesh properly, so let's increase the
1198                            number of teeth so that it's not so obvious. */
1199
1200   int teeth1 = args[0] * tscale;
1201   int teeth2 = args[1] * tscale;
1202   int teeth3 = args[2] * tscale;
1203   GLfloat r1 = args[3];
1204   GLfloat ratio2 = teeth2 / (GLfloat) teeth1;
1205   GLfloat ratio3 = teeth3 / (GLfloat) teeth2;
1206   GLfloat r2 = r1 * ratio2;
1207   GLfloat r3 = r2 * ratio3;
1208
1209   GLfloat r4 = args[4]; /* #### Empirical. Not sure what its basis is. */
1210   GLfloat r5 = 1 - r4;
1211
1212   gear *gear1, *gear2, *gear3;
1213
1214   gear1 = add_gear_shape (mi, r1, teeth1);
1215   gear2 = add_gear_shape (mi, r2, teeth2);
1216   gear3 = add_gear_shape (mi, r3, teeth3);
1217   gear2->ratio = 1 / ratio2;
1218   gear3->ratio = 1 / ratio3;
1219
1220   {
1221     XYZ a = { 0, 0, 1 };
1222     XYZ b = { 0, 0, -1 };
1223     add_sphere_gear (mi, gear1, a);
1224     add_sphere_gear (mi, gear1, b);
1225   }
1226
1227   for (i = 0; i < 10; i++)
1228     {
1229       GLfloat th1 = s * i;
1230       GLfloat th2 = s * (i+1);
1231       GLfloat th3 = s * (i+2);
1232       LL v1, v2, v3, vc;
1233       XYZ p1, p2, p3, pc, pc2;
1234       v1.a = th0;    v1.o = th1;
1235       v2.a = th0;    v2.o = th3;
1236       v3.a = -th0;   v3.o = th2;
1237       vc.a = M_PI/2; vc.o = th2;
1238
1239       if (! (i & 1)) /* southern hemisphere */
1240         {
1241           v1.a = -v1.a;
1242           v2.a = -v2.a;
1243           v3.a = -v3.a;
1244           vc.a = -vc.a;
1245         }
1246
1247       p1 = polar_to_cartesian (v1);
1248       p2 = polar_to_cartesian (v2);
1249       p3 = polar_to_cartesian (v3);
1250       pc = polar_to_cartesian (vc);
1251
1252       /* Two faces: 123 and 12c. */
1253
1254       add_sphere_gear (mi, gear1, p1);  /* left shared point of 2 triangles */
1255
1256       pc2.x = (p1.x + p2.x + p3.x) / 3; /* center of bottom triangle */
1257       pc2.y = (p1.y + p2.y + p3.y) / 3;
1258       pc2.z = (p1.z + p2.z + p3.z) / 3;
1259       add_sphere_gear (mi, gear2, pc2);
1260
1261       pc2.x = (p1.x + p2.x + pc.x) / 3; /* center of top triangle */
1262       pc2.y = (p1.y + p2.y + pc.y) / 3;
1263       pc2.z = (p1.z + p2.z + pc.z) / 3;
1264       add_sphere_gear (mi, gear2, pc2);
1265
1266       /* left edge of bottom triangle, 1/3 in */
1267       pc2.x = p1.x + (p3.x - p1.x) * r4;
1268       pc2.y = p1.y + (p3.y - p1.y) * r4;
1269       pc2.z = p1.z + (p3.z - p1.z) * r4;
1270       add_sphere_gear (mi, gear3, pc2);
1271
1272       /* left edge of bottom triangle, 2/3 in */
1273       pc2.x = p1.x + (p3.x - p1.x) * r5;
1274       pc2.y = p1.y + (p3.y - p1.y) * r5;
1275       pc2.z = p1.z + (p3.z - p1.z) * r5;
1276       add_sphere_gear (mi, gear3, pc2);
1277
1278       /* left edge of top triangle, 1/3 in */
1279       pc2.x = p1.x + (pc.x - p1.x) * r4;
1280       pc2.y = p1.y + (pc.y - p1.y) * r4;
1281       pc2.z = p1.z + (pc.z - p1.z) * r4;
1282       add_sphere_gear (mi, gear3, pc2);
1283
1284       /* left edge of top triangle, 2/3 in */
1285       pc2.x = p1.x + (pc.x - p1.x) * r5;
1286       pc2.y = p1.y + (pc.y - p1.y) * r5;
1287       pc2.z = p1.z + (pc.z - p1.z) * r5;
1288       add_sphere_gear (mi, gear3, pc2);
1289
1290       /* center of shared edge, 1/3 in */
1291       pc2.x = p1.x + (p2.x - p1.x) * r4;
1292       pc2.y = p1.y + (p2.y - p1.y) * r4;
1293       pc2.z = p1.z + (p2.z - p1.z) * r4;
1294       add_sphere_gear (mi, gear3, pc2);
1295
1296       /* center of shared edge, 2/3 in */
1297       pc2.x = p1.x + (p2.x - p1.x) * r5;
1298       pc2.y = p1.y + (p2.y - p1.y) * r5;
1299       pc2.z = p1.z + (p2.z - p1.z) * r5;
1300       add_sphere_gear (mi, gear3, pc2);
1301     }
1302
1303   if (bp->ngears != 92) abort();
1304 }
1305
1306 static void
1307 make_182 (ModeInfo *mi, const GLfloat *args)
1308 {
1309   /* #### TODO: http://bugman123.com/Gears/182GearSpheres/ */
1310   abort();
1311 }
1312
1313
1314 /* Window management, etc
1315  */
1316 ENTRYPOINT void
1317 reshape_geodesic (ModeInfo *mi, int width, int height)
1318 {
1319   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1320   GLfloat h = (GLfloat) height / (GLfloat) width;
1321
1322   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1323
1324   glViewport (0, 0, (GLint) width, (GLint) height);
1325
1326   glMatrixMode(GL_PROJECTION);
1327   glLoadIdentity();
1328   gluPerspective (30.0, 1/h, 1.0, 100.0);
1329
1330   glMatrixMode(GL_MODELVIEW);
1331   glLoadIdentity();
1332   gluLookAt( 0.0, 0.0, 30.0,
1333              0.0, 0.0, 0.0,
1334              0.0, 1.0, 0.0);
1335
1336 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
1337   {
1338     int o = (int) current_device_rotation();
1339     if (o != 0 && o != 180 && o != -180)
1340       glScalef (1/h, 1/h, 1/h);
1341   }
1342 # endif
1343
1344   glClear(GL_COLOR_BUFFER_BIT);
1345 }
1346
1347
1348 static void
1349 pick_shape (ModeInfo *mi, time_t last)
1350 {
1351   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1352   int count = countof (gear_templates);
1353
1354   if (bp->colors)
1355     free (bp->colors);
1356
1357   bp->ncolors = 1024;
1358   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
1359   make_smooth_colormap (0, 0, 0,
1360                         bp->colors, &bp->ncolors,
1361                         False, 0, False);
1362
1363   free_sphere_gears (mi);
1364
1365   if (last == 0)
1366     {
1367       bp->which = random() % count;
1368     }
1369   else if (bp->next < 0)
1370     {
1371       bp->which--;
1372       if (bp->which < 0) bp->which = count-1;
1373       bp->next = 0;
1374     }
1375   else if (bp->next > 0)
1376     {
1377       bp->which++;
1378       if (bp->which >= count) bp->which = 0;
1379       bp->next = 0;
1380     }
1381   else
1382     {
1383       int n = bp->which;
1384       while (n == bp->which)
1385         n = random() % count;
1386       bp->which = n;
1387     }
1388
1389   switch (gear_templates[bp->which].type) {
1390   case PRISM: make_prism (mi); break;
1391   case OCTO:  make_octo (mi); break;
1392   case DECA:  make_deca (mi); break;
1393   case G14:   make_14 (mi); break;
1394   case G18:   make_18 (mi); break;
1395   case G32:   make_32 (mi, gear_templates[bp->which].args); break;
1396   case G92:   make_92 (mi, gear_templates[bp->which].args); break;
1397   case G182:  make_182(mi, gear_templates[bp->which].args); break;
1398   default: abort(); break;
1399   }
1400
1401   sort_gears (mi);
1402 }
1403
1404
1405 static void free_geodesic (ModeInfo *mi);
1406
1407 ENTRYPOINT void 
1408 init_geodesic (ModeInfo *mi)
1409 {
1410   geodesic_configuration *bp;
1411   int wire = MI_IS_WIREFRAME(mi);
1412
1413   MI_INIT (mi, bps, free_geodesic);
1414
1415   bp = &bps[MI_SCREEN(mi)];
1416
1417   bp->glx_context = init_GL(mi);
1418
1419   reshape_geodesic (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1420
1421   {
1422     static GLfloat cspec[4] = {1.0, 1.0, 1.0, 1.0};
1423     static const GLfloat shiny = 128.0;
1424
1425     static GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
1426     static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1427     static GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1428     static GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
1429
1430     glLightfv(GL_LIGHT0, GL_POSITION, pos);
1431     glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1432     glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1433     glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1434
1435     glMaterialfv (GL_FRONT, GL_SPECULAR,  cspec);
1436     glMateriali  (GL_FRONT, GL_SHININESS, shiny);
1437   }
1438
1439   if (! wire)
1440     {
1441       glEnable (GL_DEPTH_TEST);
1442       glEnable (GL_BLEND);
1443       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1444       glEnable (GL_LIGHTING);
1445       glEnable (GL_LIGHT0);
1446     }
1447
1448   if (! bp->rot)
1449   {
1450     double spin_speed   = 0.25 * speed;
1451     double wander_speed = 0.01 * speed;
1452     double spin_accel   = 0.2;
1453
1454     bp->rot = make_rotator (do_spin ? spin_speed : 0,
1455                             do_spin ? spin_speed : 0,
1456                             do_spin ? spin_speed : 0,
1457                             spin_accel,
1458                             do_wander ? wander_speed : 0,
1459                             True);
1460     bp->trackball = gltrackball_init (True);
1461   }
1462
1463   bp->font = load_texture_font (MI_DISPLAY(mi), "font");
1464
1465   pick_shape (mi, 0);
1466 }
1467
1468
1469 ENTRYPOINT Bool
1470 geodesic_handle_event (ModeInfo *mi, XEvent *event)
1471 {
1472   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1473
1474   if (gltrackball_event_handler (event, bp->trackball,
1475                                  MI_WIDTH (mi), MI_HEIGHT (mi),
1476                                  &bp->button_down_p))
1477     return True;
1478   else
1479     {
1480       if (event->xany.type == KeyPress)
1481         {
1482           KeySym keysym;
1483           char c = 0;
1484           XLookupString (&event->xkey, &c, 1, &keysym, 0);
1485           if (c == '<' || c == ',' || c == '-' || c == '_' ||
1486               keysym == XK_Left || keysym == XK_Up || keysym == XK_Prior)
1487             {
1488               bp->next = -1;
1489               goto SWITCH;
1490             }
1491           else if (c == '>' || c == '.' || c == '=' || c == '+' ||
1492                    keysym == XK_Right || keysym == XK_Down ||
1493                    keysym == XK_Next)
1494             {
1495               bp->next = 1;
1496               goto SWITCH;
1497             }
1498         }
1499
1500       if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
1501         {
1502         SWITCH:
1503           bp->mode = 1;
1504           bp->mode_tick = 4;
1505           return True;
1506         }
1507     }
1508
1509   return False;
1510 }
1511
1512
1513 ENTRYPOINT void
1514 draw_geodesic (ModeInfo *mi)
1515 {
1516   time_t now = time ((time_t *) 0);
1517   int wire = MI_IS_WIREFRAME(mi);
1518   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1519   Display *dpy = MI_DISPLAY(mi);
1520   Window window = MI_WINDOW(mi);
1521
1522   if (!bp->glx_context)
1523     return;
1524
1525   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1526
1527
1528   if (bp->draw_time == 0)
1529     {
1530       pick_shape (mi, bp->draw_time);
1531       bp->draw_time = now;
1532     }
1533   else if (bp->mode == 0)
1534     {
1535       if (bp->draw_tick++ > 10)
1536         {
1537           if (bp->draw_time == 0) bp->draw_time = now;
1538           bp->draw_tick = 0;
1539
1540           if (!bp->button_down_p &&
1541               bp->draw_time + timeout <= now)
1542             {
1543               /* randomize every -timeout seconds */
1544               bp->mode = 1;    /* go out */
1545               bp->mode_tick = 10 / speed;
1546               bp->draw_time = now;
1547             }
1548         }
1549     }
1550   else if (bp->mode == 1)   /* out */
1551     {
1552       if (--bp->mode_tick <= 0)
1553         {
1554           bp->mode_tick = 10 / speed;
1555           bp->mode = 2;  /* go in */
1556           pick_shape (mi, bp->draw_time);
1557           bp->draw_time = now;
1558         }
1559     }
1560   else if (bp->mode == 2)   /* in */
1561     {
1562       if (--bp->mode_tick <= 0)
1563         bp->mode = 0;  /* normal */
1564     }
1565   else
1566     abort();
1567
1568
1569   if (! wire)
1570     glShadeModel(GL_SMOOTH);
1571
1572   glEnable(GL_DEPTH_TEST);
1573   glEnable(GL_NORMALIZE);
1574   glEnable(GL_CULL_FACE);
1575
1576   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1577
1578   glPushMatrix ();
1579
1580   {
1581     double x, y, z;
1582     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
1583     glTranslatef((x - 0.5) * 8,
1584                  (y - 0.5) * 8,
1585                  (z - 0.5) * 17);
1586
1587     gltrackball_rotate (bp->trackball);
1588
1589     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
1590     glRotatef (x * 360, 1.0, 0.0, 0.0);
1591     glRotatef (y * 360, 0.0, 1.0, 0.0);
1592     glRotatef (z * 360, 0.0, 0.0, 1.0);
1593   }
1594
1595   mi->polygon_count = 0;
1596
1597   glScalef (6, 6, 6);
1598
1599   if (bp->ngears < 14)
1600     glScalef (0.8, 0.8, 0.8);  /* make these a little easier to see */
1601
1602   if (bp->mode != 0)
1603     {
1604       GLfloat s = (bp->mode == 1
1605                    ? bp->mode_tick / (10 / speed)
1606                    : ((10 / speed) - bp->mode_tick + 1) / (10 / speed));
1607       glScalef (s, s, s);
1608     }
1609
1610
1611   {
1612     int i;
1613     for (i = 0; i < bp->ngears; i++)
1614       {
1615         const sphere_gear *s = &bp->gears[i];
1616         const gear *g = s->g;
1617
1618         XYZ axis;
1619         XYZ from = { 0, 1, 0 };
1620         XYZ to = s->axis;
1621         GLfloat angle;
1622         GLfloat off = s->offset;
1623
1624         /* If an even number of teeth, offset by 1/2 tooth width. */
1625         if (s->direction > 0 && !(g->nteeth & 1))
1626           off += 360 / g->nteeth / 2;
1627
1628         axis  = cross_product (from, to);
1629         angle = acos (dot_product (from, to));
1630
1631         glPushMatrix();
1632         glTranslatef (to.x, to.y, to.z);
1633         glRotatef (angle / M_PI * 180, axis.x, axis.y, axis.z);
1634         glRotatef (-90, 1, 0, 0);
1635         glRotatef(180, 0, 0, 1);
1636         glRotatef ((bp->th - off) * g->ratio * s->direction,
1637                    0, 0, 1);
1638
1639         glCallList (g->dlist);
1640         mi->polygon_count += g->polygons;
1641         glPopMatrix();
1642
1643 #if 0
1644         {                               /* Draw tooth vectors */
1645           GLfloat r = 1 - g->z;
1646           XYZ pc;
1647           int pt = parent_tooth (s, &pc);
1648           int t;
1649           glDisable(GL_LIGHTING);
1650           glLineWidth (8);
1651           for (t = 0; t < g->nteeth; t++)
1652             {
1653               XYZ p = tooth_coords (s, t);
1654               XYZ p2;
1655               p2.x = (r * s->axis.x + p.x) / 2;
1656               p2.y = (r * s->axis.y + p.y) / 2;
1657               p2.z = (r * s->axis.z + p.z) / 2;
1658
1659               if (t == pt)
1660                 glColor3f(1,0,1);
1661               else
1662                 glColor3f(0,1,1);
1663               glBegin(GL_LINES);
1664               glVertex3f (p.x, p.y, p.z);
1665               glVertex3f (p2.x, p2.y, p2.z);
1666               glEnd();
1667             }
1668           if (!wire) glEnable(GL_LIGHTING);
1669           glLineWidth (1);
1670         }
1671 #endif
1672
1673 #if 0
1674         {                               /* Draw the parent/child DAG */
1675           GLfloat s1 = 1.1;
1676           GLfloat s2 = s->parent ? s1 : 1.5;
1677           GLfloat s3 = 1.0;
1678           XYZ p1 = s->axis;
1679           XYZ p2 = s->parent ? s->parent->axis : p1;
1680           glDisable(GL_LIGHTING);
1681           glColor3f(0,0,1);
1682           glBegin(GL_LINES);
1683           glVertex3f (s1 * p1.x, s1 * p1.y, s1 * p1.z);
1684           glVertex3f (s2 * p2.x, s2 * p2.y, s2 * p2.z);
1685           glVertex3f (s1 * p1.x, s1 * p1.y, s1 * p1.z);
1686           glVertex3f (s3 * p1.x, s3 * p1.y, s3 * p1.z);
1687           glEnd();
1688           if (!wire) glEnable(GL_LIGHTING);
1689         }
1690 #endif
1691       }
1692
1693     /* We need to draw the fonts in a second pass in order to make
1694        transparency (as a result of anti-aliasing) work properly.
1695       */
1696     if (do_numbers && bp->mode == 0)
1697       for (i = 0; i < bp->ngears; i++)
1698         {
1699           const sphere_gear *s = &bp->gears[i];
1700           const gear *g = s->g;
1701
1702           XYZ axis;
1703           XYZ from = { 0, 1, 0 };
1704           XYZ to = s->axis;
1705           GLfloat angle;
1706           GLfloat off = s->offset;
1707
1708           int w, h, j;
1709           char buf[100];
1710           XCharStruct e;
1711
1712           /* If an even number of teeth, offset by 1/2 tooth width. */
1713           if (s->direction > 0 /* && !(g->nteeth & 1) */)
1714             off += 360 / g->nteeth / 2;
1715
1716           axis  = cross_product (from, to);
1717           angle = acos (dot_product (from, to));
1718
1719           glPushMatrix();
1720           glTranslatef(to.x, to.y, to.z);
1721           glRotatef (angle / M_PI * 180, axis.x, axis.y, axis.z);
1722           glRotatef (-90, 1, 0, 0);
1723           glRotatef(180, 0, 0, 1);
1724           glRotatef ((bp->th - off) * g->ratio * s->direction,
1725                      0, 0, 1);
1726
1727           glDisable (GL_LIGHTING);
1728           glColor3f(1, 1, 0);
1729           glPushMatrix();
1730           glScalef(0.005, 0.005, 0.005);
1731           sprintf (buf, "%d", i);
1732           texture_string_metrics (bp->font, buf, &e, 0, 0);
1733           w = e.width;
1734           h = e.ascent + e.descent;
1735           glTranslatef (-w/2, -h/2, 0);
1736           print_texture_string (bp->font, buf);
1737           glPopMatrix();
1738
1739 # if 1
1740           for (j = 0; j < g->nteeth; j++)          /* Number the teeth */
1741             {
1742               GLfloat ss = 0.08 * g->r / g->nteeth;
1743               GLfloat r = g->r * 0.88;
1744               GLfloat th = M_PI - (j * M_PI * 2 / g->nteeth + M_PI/2);
1745
1746
1747               glPushMatrix();
1748               glTranslatef (r * cos(th), r * sin(th), -g->z + 0.01);
1749               glScalef(ss, ss, ss);
1750               sprintf (buf, "%d", j + 1);
1751               texture_string_metrics (bp->font, buf, &e, 0, 0);
1752               w = e.width;
1753               h = e.ascent + e.descent;
1754               glTranslatef (-w/2, -h/2, 0);
1755               print_texture_string (bp->font, buf);
1756               glPopMatrix();
1757             }
1758 # endif
1759           glPopMatrix();
1760           if (!wire) glEnable(GL_LIGHTING);
1761         }
1762
1763     bp->th += 0.7 * speed;    /* Don't mod this to 360 - causes glitches. */
1764   }
1765
1766   if (do_labels && bp->mode == 0)
1767     {
1768       glColor3f (1, 1, 0);
1769       print_texture_label (mi->dpy, bp->font,
1770                            mi->xgwa.width, mi->xgwa.height,
1771                            1, bp->desc);
1772     }
1773
1774   glPopMatrix ();
1775
1776   if (mi->fps_p) do_fps (mi);
1777   glFinish();
1778
1779   glXSwapBuffers(dpy, window);
1780 }
1781
1782 static void
1783 free_geodesic (ModeInfo *mi)
1784 {
1785   geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1786   if (!bp->glx_context)
1787     return;
1788   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1789   free_texture_font (bp->font);
1790   free (bp->colors);
1791   free_sphere_gears (mi);
1792   if (bp->desc) free (bp->desc);
1793 }
1794
1795
1796 XSCREENSAVER_MODULE_2 ("GeodesicGears", geodesicgears, geodesic)
1797
1798 #endif /* USE_GL */