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