1 /* geodesicgears, Copyright (c) 2014-2015 Jamie Zawinski <jwz@jwz.org>
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
11 * Inspired by http://bugman123.com/Gears/
12 * and by http://kennethsnelson.net/PortraitOfAnAtom.pdf
15 #define DEFAULTS "*delay: 30000 \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" \
23 # define refresh_geodesic 0
24 # define release_geodesic 0
26 #define countof(x) (sizeof((x))/sizeof((*x)))
28 #include "xlockmore.h"
33 #include "gltrackball.h"
38 #ifdef USE_GL /* whole file */
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"
49 typedef struct { double a, o; } LL; /* latitude + longitude */
51 /* 10:6 is a mismesh. */
54 enum { PRISM, OCTO, DECA, G14, G18, G32, G92, G182 } type;
55 const GLfloat args[5];
56 } gear_templates[] = {
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 */
86 /* These all have phase errors and don't always mesh properly.
87 Maybe we should just omit them? */
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 }},
104 typedef struct sphere_gear 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) */
120 GLXContext *glx_context;
122 trackball_state *trackball;
126 GLfloat color1[4], color2[4];
127 texture_font_data *font;
129 int nshapes, shapes_size; /* how many 'gear' objects there are */
130 int ngears, gears_size; /* how many 'sphere_gear' objects there are */
135 int mode; /* 0 = normal, 1 = out, 2 = in */
137 int next; /* 0 = random, -1 = back, 1 = forward */
142 GLfloat th; /* rotation of the root sphere_gear in degrees. */
144 } geodesic_configuration;
146 static geodesic_configuration *bps = NULL;
150 static GLfloat speed;
151 static Bool do_wander;
152 static Bool do_labels;
153 static Bool do_numbers;
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 },
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},
177 ENTRYPOINT ModeSpecOpt geodesic_opts = {
178 countof(opts), opts, countof(vars), vars, NULL};
182 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
187 cross_product (XYZ a, XYZ b)
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);
198 dot_product (XYZ a, XYZ b)
200 return (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
207 GLfloat d = sqrt ((v.x * v.x) + (v.y * v.y) + (v.z * v.z));
221 polar_to_cartesian (LL v)
224 p.x = cos (v.a) * cos (v.o);
225 p.y = cos (v.a) * sin (v.o);
234 add_gear_shape (ModeInfo *mi, GLfloat radius, int teeth)
236 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
237 int wire = MI_IS_WIREFRAME(mi);
241 if (bp->nshapes >= bp->shapes_size - 1)
243 bp->shapes_size = bp->shapes_size * 1.2 + 4;
244 bp->shapes = (gear *)
245 realloc (bp->shapes, bp->shapes_size * sizeof(*bp->shapes));
247 g = &bp->shapes[bp->nshapes++];
249 memset (g, 0, sizeof(*g));
255 g->tooth_h = g->r / (teeth * 0.4);
257 if (g->tooth_h > 0.06) /* stubbier teeth when small tooth count. */
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;
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));
269 /* #### This isn't quite right */
270 g->tooth_slope = 1 + ((g->z * 2) / g->r);
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.)
281 else if ((random() % 10) == 0)
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);
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);
295 if (g->inner_r2 > (g->r * 0.2))
297 int nn = (random() % 10);
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;
305 /* If we have three discs, sometimes make the middle disc be spokes.
307 if (g->inner_r3 && ((random() % 5) == 0))
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;
315 /* Sometimes add little nubbly bits, if there is room.
317 if (!wire && g->nteeth > 5)
320 involute_biggest_ring (g, 0, &size, 0);
321 if (size > g->r * 0.2 && (random() % 5) == 0)
323 g->nubs = 1 + (random() % 16);
324 if (g->nubs > 8) g->nubs = 1;
328 /* Decide how complex the polygon model should be.
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;
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();
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;
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;
354 g->dlist = glGenLists (1);
355 glNewList (g->dlist, GL_COMPILE);
362 /* Move the gear inward so that its outer edge is on the disc, instead
364 g2->z += g2->thickness/2;
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));
371 glTranslatef(g2->x, g2->y, -g2->z);
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);
378 g->polygons = draw_involute_gear (g2, wire);
381 # else /* draw discs */
384 glTranslatef(g->x, g->y, -g->z);
386 glFrontFace (GL_CCW);
389 glDisable (GL_LIGHTING);
392 glVertex3f (0, 0, 0);
393 glVertex3f (0, radius, 0);
396 glColor3f(0.5, 0.5, 0.5);
397 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
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)
405 GLfloat x = cos(th) * radius;
406 GLfloat y = sin(th) * radius;
407 glVertex3f (x, y, 0);
411 if (!wire) glEnable(GL_LIGHTING);
423 add_sphere_gear (ModeInfo *mi, gear *g, XYZ axis)
425 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
429 axis = normalize (axis);
431 /* If there's already a gear on this axis, don't duplicate it. */
432 for (i = 0; i < bp->ngears; i++)
434 XYZ o = bp->gears[i].axis;
435 if (o.x == axis.x && o.y == axis.y && o.z == axis.z)
439 if (bp->ngears >= bp->gears_size - 1)
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));
446 gg = &bp->gears[bp->ngears];
447 memset (gg, 0, sizeof(*gg));
457 free_sphere_gears (ModeInfo *mi)
459 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
461 for (i = 0; i < bp->nshapes; i++)
463 if (bp->shapes[i].dlist)
464 glDeleteLists (bp->shapes[i].dlist, 1);
471 for (i = 0; i < bp->ngears; i++)
473 sphere_gear *g = &bp->gears[i];
487 /* Is the gear a member of the list?
490 gear_list_member (sphere_gear *g, sphere_gear **list, int count)
493 for (i = 0; i < count; i++)
494 if (list[i] == g) return True;
499 /* Add the gear to the list, resizing it as needed.
502 gear_list_push (sphere_gear *g,
503 sphere_gear ***listP,
504 int *countP, int *sizeP)
506 if (*countP >= (*sizeP) - 1)
508 *sizeP = (*sizeP) * 1.2 + 4;
509 *listP = (sphere_gear **) realloc (*listP, (*sizeP) * sizeof(**listP));
511 (*listP)[*countP] = g;
516 /* Mark child and parent as being mutual neighbors.
519 link_neighbors (sphere_gear *parent, sphere_gear *child)
521 if (child == parent) abort();
523 /* Add child to parent's list of neighbors */
524 if (! gear_list_member (child, parent->neighbors, parent->nneighbors))
526 gear_list_push (child,
529 &parent->neighbors_size);
530 /* fprintf(stderr, "neighbor %2d -> %2d (%d)\n", parent->id, child->id,
531 parent->nneighbors); */
534 /* Add parent to child's list of neighbors */
535 if (! gear_list_member (parent, child->neighbors, child->nneighbors))
537 gear_list_push (parent,
540 &child->neighbors_size);
541 /* fprintf(stderr, "neighbor %2d <- %2d\n", parent->id, child->id); */
545 /* Mark child as having parent, and vice versa.
548 link_child (sphere_gear *parent, sphere_gear *child)
550 if (child == parent) abort();
551 if (child->parent) return;
553 gear_list_push (child,
556 &parent->children_size);
557 child->parent = parent;
558 /* fprintf(stderr, "child %2d -> %2d (%d)\n", parent->id, child->id,
559 parent->nchildren); */
564 static void link_children (sphere_gear *);
567 link_children (sphere_gear *parent)
570 # if 1 /* depth first */
571 for (i = 0; i < parent->nneighbors; i++)
573 sphere_gear *child = parent->neighbors[i];
576 link_child (parent, child);
577 link_children (child);
580 # else /* breadth first */
581 for (i = 0; i < parent->nneighbors; i++)
583 sphere_gear *child = parent->neighbors[i];
585 link_child (parent, child);
587 for (i = 0; i < parent->nchildren; i++)
589 sphere_gear *child = parent->children[i];
590 link_children (child);
597 /* Whether the two gears touch.
600 gears_touch_p (ModeInfo *mi, sphere_gear *a, sphere_gear *b)
602 /* We need to know if the two discs on the surface overlap.
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.
616 Find the angle between the axes of the two discs.
619 | / angle = acos (v1 dot v2)
620 1 | / axis = v1 cross v2
625 If the sum of the first two angles is less than the third angle,
630 double t1 = asin (a->g->r);
631 double t2 = asin (b->g->r);
632 double th = acos (dot_product (p1, p2));
634 return (t1 + t2 >= th);
638 /* Set the rotation direction for the gear and its kids.
641 orient_gears (ModeInfo *mi, sphere_gear *g)
645 g->direction = -g->parent->direction;
646 for (i = 0; i < g->nchildren; i++)
647 orient_gears (mi, g->children[i]);
651 /* Returns the global model coordinates of the given tooth of a gear.
654 tooth_coords (const sphere_gear *s, int tooth)
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;
661 XYZ from = { 0, 1, 0 };
664 GLfloat x, y, z, C, S, m[4][4];
666 axis = cross_product (from, to);
667 angle = acos (dot_product (from, to));
669 p0 = normalize (axis);
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;
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;
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;
697 /* The point to transform */
698 p1.x = g->r * sin (th);
699 p1.z = g->r * cos (th);
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];
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.
716 parent_tooth (const sphere_gear *s, XYZ *parent)
718 const sphere_gear *s2 = s->parent;
720 GLfloat min_dist = 99999;
722 XYZ min_parent = { 0, 0, 0 };
725 for (i = 0; i < s->g->nteeth; i++)
727 XYZ p1 = tooth_coords (s, i);
728 for (j = 0; j < s2->g->nteeth; j++)
730 XYZ p2 = tooth_coords (s2, j);
737 dist = sqrt (d.x*d.x + d.y*d.y + d.z*d.z);
746 *parent = min_parent;
751 /* Make all of the gear's children's teeth mesh properly.
753 static void align_gear_teeth (sphere_gear *s);
755 align_gear_teeth (sphere_gear *s)
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.
766 int pt = parent_tooth (s, &pc);
767 GLfloat range = 360 / s->g->nteeth;
769 GLfloat min_dist = 999999;
773 for (off = -range/2; off < range/2; off += range/steps)
778 tc = tooth_coords (s, pt);
782 dist = sqrt (d.x*d.x + d.y*d.y + d.z*d.z);
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.
796 for (i = 0; i < s->nchildren; i++)
797 align_gear_teeth (s->children[i]);
803 describe_gears (ModeInfo *mi)
805 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
806 int gears_per_teeth[1000];
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);
815 for (i = 0; i < countof(gears_per_teeth); i++)
816 if (gears_per_teeth[i])
818 sprintf (bp->desc + strlen(bp->desc),
819 "%s%d gears with %d teeth",
820 (lines > 0 ? ",\n" : ""),
821 gears_per_teeth[i], i);
825 sprintf (bp->desc + strlen(bp->desc), ",\n%d gears total", bp->ngears);
826 strcat (bp->desc, ".");
830 /* Takes the gears and makes an arbitrary DAG of them in order to compute
831 direction and gear ratios.
834 sort_gears (ModeInfo *mi)
836 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
837 sphere_gear *root = 0;
840 /* For each gear, compare it against every other gear.
841 If they touch, mark them as being each others' neighbors.
843 for (i = 0; i < bp->ngears; i++)
845 sphere_gear *a = &bp->gears[i];
846 for (j = 0; j < bp->ngears; j++)
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);
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;
861 for (i = 0; i < bp->ngears; i++)
863 fprintf (stderr, "%2d: p = %2d; k(%d, %d) = ",
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");
876 fprintf (stderr,"\n");
880 /* If there is more than one gear with no parent, we fucked up. */
883 for (i = 0; i < bp->ngears; i++)
885 sphere_gear *g = &bp->gears[i];
893 orient_gears (mi, root);
895 /* If there are any gears with no direction, they aren't reachable. */
896 for (i = 0; i < bp->ngears; i++)
898 sphere_gear *g = &bp->gears[i];
899 if (g->direction == 0)
900 fprintf(stderr,"INTERNAL ERROR: unreachable: %d\n", g->id);
903 align_gear_teeth (root);
908 /* Create 5 identical gears arranged on the faces of a uniform
912 make_prism (ModeInfo *mi)
914 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
918 int teeth = 4 * (4 + (int) (BELLRAND(20)));
919 if (teeth % 4) abort(); /* must be a multiple of 4 */
921 g = add_gear_shape (mi, 0.7075, teeth);
923 a.x = 0; a.y = 0; a.z = 1;
924 add_sphere_gear (mi, g, a);
926 add_sphere_gear (mi, g, a);
929 for (i = 0; i < 3; i++)
931 GLfloat th = i * M_PI * 2 / 3;
934 add_sphere_gear (mi, g, a);
937 if (bp->ngears != 5) abort();
941 /* Create 8 identical gears arranged on the faces of an octohedron
942 (or alternately, arranged on the diagonals of a cube.)
945 make_octo (ModeInfo *mi)
947 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
948 static const XYZ verts[] = {{ -1, -1, -1 },
958 int teeth = 4 * (4 + (int) (BELLRAND(20)));
959 if (teeth % 4) abort(); /* must be a multiple of 4 */
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]);
965 if (bp->ngears != 8) abort();
969 /* Create 10 identical gears arranged on the faces of ... something.
970 I'm not sure what polyhedron is the basis of this.
973 make_deca (ModeInfo *mi)
975 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
979 int teeth = 4 * (4 + (int) (BELLRAND(15)));
980 if (teeth % 4) abort(); /* must be a multiple of 4 */
982 g = add_gear_shape (mi, 0.5415, teeth);
984 a.x = 0; a.y = 0; a.z = 1;
985 add_sphere_gear (mi, g, a);
987 add_sphere_gear (mi, g, a);
989 for (j = -1; j <= 1; j += 2)
991 GLfloat off = (j < 0 ? 0 : M_PI / 4);
993 v.a = j * M_PI * 0.136; /* #### Empirical. What is this? */
994 for (i = 0; i < 4; i++)
996 v.o = i * M_PI / 2 + off;
997 a = polar_to_cartesian (v);
998 add_sphere_gear (mi, g, a);
1001 if (bp->ngears != 10) abort();
1005 /* Create 14 identical gears arranged on the faces of ... something.
1006 I'm not sure what polyhedron is the basis of this.
1009 make_14 (ModeInfo *mi)
1011 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
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 */
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);
1025 add_sphere_gear (mi, g, a);
1029 for (i = 0; i < 4; i++)
1031 GLfloat th = i * M_PI * 2 / 4 + (M_PI / 4);
1034 add_sphere_gear (mi, g, a);
1038 g = add_gear_shape (mi, r, teeth);
1040 for (i = 0; i < 4; i++)
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);
1048 a = polar_to_cartesian (v);
1049 add_sphere_gear (mi, g, a);
1052 if (bp->ngears != 14) abort();
1056 /* Create 18 identical gears arranged on the faces of ... something.
1057 I'm not sure what polyhedron is the basis of this.
1060 make_18 (ModeInfo *mi)
1062 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
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));
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);
1075 add_sphere_gear (mi, g, a);
1078 g2 = add_gear_shape (mi, r, teeth);
1080 for (i = 0; i < 8; i++)
1082 GLfloat th = i * M_PI * 2 / 8 + (M_PI / 4);
1085 add_sphere_gear (mi, (i & 1 ? g : g2), a);
1089 g = add_gear_shape (mi, r, teeth);
1091 for (i = 0; i < 4; i++)
1095 v.o = i * M_PI * 2 / 4;
1096 a = polar_to_cartesian (v);
1097 add_sphere_gear (mi, g, a);
1099 a = polar_to_cartesian (v);
1100 add_sphere_gear (mi, g, a);
1103 if (bp->ngears != 18) abort();
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.
1111 make_32 (ModeInfo *mi, const GLfloat *args)
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 */
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;
1125 gear *gear1, *gear2;
1127 if (teeth1 % 5) abort();
1128 if (teeth2 % 6) abort();
1130 gear1 = add_gear_shape (mi, r1, teeth1);
1131 gear2 = add_gear_shape (mi, r2, teeth2);
1132 gear2->ratio = 1 / ratio;
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);
1141 for (i = 0; i < 10; i++)
1143 GLfloat th1 = s * i;
1144 GLfloat th2 = s * (i+1);
1145 GLfloat th3 = s * (i+2);
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;
1153 if (! (i & 1)) /* southern hemisphere */
1161 p1 = polar_to_cartesian (v1);
1162 p2 = polar_to_cartesian (v2);
1163 p3 = polar_to_cartesian (v3);
1164 pc = polar_to_cartesian (vc);
1166 /* Two faces: 123 and 12c. */
1168 add_sphere_gear (mi, gear1, p1); /* left shared point of 2 triangles */
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);
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);
1181 if (bp->ngears != 32) abort();
1185 /* Create 92 gears arranged along a geodesic sphere: 20 + 12 + 60.
1186 (frequency 3v, class-I geodesic tessellation of an icosahedron)
1189 make_92 (ModeInfo *mi, const GLfloat *args)
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 */
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. */
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;
1209 GLfloat r4 = args[4]; /* #### Empirical. Not sure what its basis is. */
1210 GLfloat r5 = 1 - r4;
1212 gear *gear1, *gear2, *gear3;
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;
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);
1227 for (i = 0; i < 10; i++)
1229 GLfloat th1 = s * i;
1230 GLfloat th2 = s * (i+1);
1231 GLfloat th3 = s * (i+2);
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;
1239 if (! (i & 1)) /* southern hemisphere */
1247 p1 = polar_to_cartesian (v1);
1248 p2 = polar_to_cartesian (v2);
1249 p3 = polar_to_cartesian (v3);
1250 pc = polar_to_cartesian (vc);
1252 /* Two faces: 123 and 12c. */
1254 add_sphere_gear (mi, gear1, p1); /* left shared point of 2 triangles */
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);
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);
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);
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);
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);
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);
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);
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);
1303 if (bp->ngears != 92) abort();
1307 make_182 (ModeInfo *mi, const GLfloat *args)
1309 /* #### TODO: http://bugman123.com/Gears/182GearSpheres/ */
1314 /* Window management, etc
1317 reshape_geodesic (ModeInfo *mi, int width, int height)
1319 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1320 GLfloat h = (GLfloat) height / (GLfloat) width;
1322 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1324 glViewport (0, 0, (GLint) width, (GLint) height);
1326 glMatrixMode(GL_PROJECTION);
1328 gluPerspective (30.0, 1/h, 1.0, 100.0);
1330 glMatrixMode(GL_MODELVIEW);
1332 gluLookAt( 0.0, 0.0, 30.0,
1336 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
1338 int o = (int) current_device_rotation();
1339 if (o != 0 && o != 180 && o != -180)
1340 glScalef (1/h, 1/h, 1/h);
1344 glClear(GL_COLOR_BUFFER_BIT);
1349 pick_shape (ModeInfo *mi, time_t last)
1351 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1352 int count = countof (gear_templates);
1358 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
1359 make_smooth_colormap (0, 0, 0,
1360 bp->colors, &bp->ncolors,
1363 free_sphere_gears (mi);
1367 bp->which = random() % count;
1369 else if (bp->next < 0)
1372 if (bp->which < 0) bp->which = count-1;
1375 else if (bp->next > 0)
1378 if (bp->which >= count) bp->which = 0;
1384 while (n == bp->which)
1385 n = random() % count;
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;
1405 static void free_geodesic (ModeInfo *mi);
1408 init_geodesic (ModeInfo *mi)
1410 geodesic_configuration *bp;
1411 int wire = MI_IS_WIREFRAME(mi);
1413 MI_INIT (mi, bps, free_geodesic);
1415 bp = &bps[MI_SCREEN(mi)];
1417 bp->glx_context = init_GL(mi);
1419 reshape_geodesic (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1422 static GLfloat cspec[4] = {1.0, 1.0, 1.0, 1.0};
1423 static const GLfloat shiny = 128.0;
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};
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);
1435 glMaterialfv (GL_FRONT, GL_SPECULAR, cspec);
1436 glMateriali (GL_FRONT, GL_SHININESS, shiny);
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);
1450 double spin_speed = 0.25 * speed;
1451 double wander_speed = 0.01 * speed;
1452 double spin_accel = 0.2;
1454 bp->rot = make_rotator (do_spin ? spin_speed : 0,
1455 do_spin ? spin_speed : 0,
1456 do_spin ? spin_speed : 0,
1458 do_wander ? wander_speed : 0,
1460 bp->trackball = gltrackball_init (True);
1463 bp->font = load_texture_font (MI_DISPLAY(mi), "font");
1470 geodesic_handle_event (ModeInfo *mi, XEvent *event)
1472 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1474 if (gltrackball_event_handler (event, bp->trackball,
1475 MI_WIDTH (mi), MI_HEIGHT (mi),
1476 &bp->button_down_p))
1480 if (event->xany.type == KeyPress)
1484 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1485 if (c == '<' || c == ',' || c == '-' || c == '_' ||
1486 keysym == XK_Left || keysym == XK_Up || keysym == XK_Prior)
1491 else if (c == '>' || c == '.' || c == '=' || c == '+' ||
1492 keysym == XK_Right || keysym == XK_Down ||
1500 if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
1514 draw_geodesic (ModeInfo *mi)
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);
1522 if (!bp->glx_context)
1525 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1528 if (bp->draw_time == 0)
1530 pick_shape (mi, bp->draw_time);
1531 bp->draw_time = now;
1533 else if (bp->mode == 0)
1535 if (bp->draw_tick++ > 10)
1537 if (bp->draw_time == 0) bp->draw_time = now;
1540 if (!bp->button_down_p &&
1541 bp->draw_time + timeout <= now)
1543 /* randomize every -timeout seconds */
1544 bp->mode = 1; /* go out */
1545 bp->mode_tick = 10 / speed;
1546 bp->draw_time = now;
1550 else if (bp->mode == 1) /* out */
1552 if (--bp->mode_tick <= 0)
1554 bp->mode_tick = 10 / speed;
1555 bp->mode = 2; /* go in */
1556 pick_shape (mi, bp->draw_time);
1557 bp->draw_time = now;
1560 else if (bp->mode == 2) /* in */
1562 if (--bp->mode_tick <= 0)
1563 bp->mode = 0; /* normal */
1570 glShadeModel(GL_SMOOTH);
1572 glEnable(GL_DEPTH_TEST);
1573 glEnable(GL_NORMALIZE);
1574 glEnable(GL_CULL_FACE);
1576 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1582 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
1583 glTranslatef((x - 0.5) * 8,
1587 gltrackball_rotate (bp->trackball);
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);
1595 mi->polygon_count = 0;
1599 if (bp->ngears < 14)
1600 glScalef (0.8, 0.8, 0.8); /* make these a little easier to see */
1604 GLfloat s = (bp->mode == 1
1605 ? bp->mode_tick / (10 / speed)
1606 : ((10 / speed) - bp->mode_tick + 1) / (10 / speed));
1613 for (i = 0; i < bp->ngears; i++)
1615 const sphere_gear *s = &bp->gears[i];
1616 const gear *g = s->g;
1619 XYZ from = { 0, 1, 0 };
1622 GLfloat off = s->offset;
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;
1628 axis = cross_product (from, to);
1629 angle = acos (dot_product (from, to));
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,
1639 glCallList (g->dlist);
1640 mi->polygon_count += g->polygons;
1644 { /* Draw tooth vectors */
1645 GLfloat r = 1 - g->z;
1647 int pt = parent_tooth (s, &pc);
1649 glDisable(GL_LIGHTING);
1651 for (t = 0; t < g->nteeth; t++)
1653 XYZ p = tooth_coords (s, t);
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;
1664 glVertex3f (p.x, p.y, p.z);
1665 glVertex3f (p2.x, p2.y, p2.z);
1668 if (!wire) glEnable(GL_LIGHTING);
1674 { /* Draw the parent/child DAG */
1676 GLfloat s2 = s->parent ? s1 : 1.5;
1679 XYZ p2 = s->parent ? s->parent->axis : p1;
1680 glDisable(GL_LIGHTING);
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);
1688 if (!wire) glEnable(GL_LIGHTING);
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.
1696 if (do_numbers && bp->mode == 0)
1697 for (i = 0; i < bp->ngears; i++)
1699 const sphere_gear *s = &bp->gears[i];
1700 const gear *g = s->g;
1703 XYZ from = { 0, 1, 0 };
1706 GLfloat off = s->offset;
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;
1716 axis = cross_product (from, to);
1717 angle = acos (dot_product (from, to));
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,
1727 glDisable (GL_LIGHTING);
1730 glScalef(0.005, 0.005, 0.005);
1731 sprintf (buf, "%d", i);
1732 texture_string_metrics (bp->font, buf, &e, 0, 0);
1734 h = e.ascent + e.descent;
1735 glTranslatef (-w/2, -h/2, 0);
1736 print_texture_string (bp->font, buf);
1740 for (j = 0; j < g->nteeth; j++) /* Number the teeth */
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);
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);
1753 h = e.ascent + e.descent;
1754 glTranslatef (-w/2, -h/2, 0);
1755 print_texture_string (bp->font, buf);
1760 if (!wire) glEnable(GL_LIGHTING);
1763 bp->th += 0.7 * speed; /* Don't mod this to 360 - causes glitches. */
1766 if (do_labels && bp->mode == 0)
1768 glColor3f (1, 1, 0);
1769 print_texture_label (mi->dpy, bp->font,
1770 mi->xgwa.width, mi->xgwa.height,
1776 if (mi->fps_p) do_fps (mi);
1779 glXSwapBuffers(dpy, window);
1783 free_geodesic (ModeInfo *mi)
1785 geodesic_configuration *bp = &bps[MI_SCREEN(mi)];
1786 if (!bp->glx_context)
1788 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1789 free_texture_font (bp->font);
1791 free_sphere_gears (mi);
1792 if (bp->desc) free (bp->desc);
1796 XSCREENSAVER_MODULE_2 ("GeodesicGears", geodesicgears, geodesic)