From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / polyhedra-gl.c
1 /* polyhedra, Copyright (c) 2004-2014 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Renders 160 different 3D solids, and displays some information about each.
12  *  A new solid is chosen every few seconds.
13  *
14  * This file contains the OpenGL side; computation of the polyhedra themselves
15  * is in "polyhedra.c".
16  */
17
18 #define DEFAULTS        "*delay:        30000         \n" \
19                         "*showFPS:      False         \n" \
20                         "*wireframe:    False         \n" \
21         "*titleFont:  -*-helvetica-medium-r-normal-*-*-140-*-*-*-*-*-*\n" \
22         "*titleFont2: -*-helvetica-medium-r-normal-*-*-100-*-*-*-*-*-*\n" \
23         "*titleFont3: -*-helvetica-medium-r-normal-*-*-80-*-*-*-*-*-*\n"  \
24         "*suppressRotationAnimation: True\n" \
25
26
27 # define refresh_polyhedra 0
28 # define release_polyhedra 0
29 #undef countof
30 #define countof(x) (sizeof((x))/sizeof((*x)))
31
32 #include "xlockmore.h"
33
34 #ifdef HAVE_JWXYZ
35 # include "jwxyz.h"
36 #else
37 # include <X11/Xlib.h>
38 # include <GL/gl.h>
39 # include <GL/glu.h>
40 #endif
41
42 #ifdef HAVE_JWZGLES
43 # include "jwzgles.h"
44 #endif /* HAVE_JWZGLES */
45
46 #define DEF_SPIN        "True"
47 #define DEF_WANDER      "True"
48 #define DEF_SPEED       "1.0"
49 #define DEF_TITLES      "True"
50 #define DEF_DURATION    "12"
51 #define DEF_WHICH       "random"
52
53 #include "texfont.h"
54 #include "normals.h"
55 #include "polyhedra.h"
56 #include "colors.h"
57 #include "rotator.h"
58 #include "gltrackball.h"
59 #include "teapot.h"
60
61 #ifndef HAVE_JWXYZ
62 # define XK_MISCELLANY
63 # include <X11/keysymdef.h>
64 #endif
65
66 #ifndef HAVE_JWZGLES
67 # define HAVE_TESS
68 #endif
69
70
71 #ifdef USE_GL /* whole file */
72
73 typedef struct {
74   GLXContext *glx_context;
75   rotator *rot;
76   trackball_state *trackball;
77   Bool button_down_p;
78
79   int npolyhedra;
80   polyhedron **polyhedra;
81
82   int which;
83   int change_to;
84   GLuint object_list;
85
86   int mode;  /* 0 = normal, 1 = out, 2 = in */
87   int mode_tick;
88
89   int ncolors;
90   XColor *colors;
91
92   texture_font_data *font1_data, *font2_data, *font3_data;
93
94   time_t last_change_time;
95   int change_tick;
96
97 } polyhedra_configuration;
98
99 static polyhedra_configuration *bps = NULL;
100
101 static Bool do_spin;
102 static GLfloat speed;
103 static Bool do_wander;
104 static Bool do_titles;
105 static int duration;
106 static int do_which;
107 static char *do_which_str;
108
109 static XrmOptionDescRec opts[] = {
110   { "-spin",   ".spin",   XrmoptionNoArg, "True" },
111   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
112   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
113   { "-wander", ".wander", XrmoptionNoArg, "True" },
114   { "+wander", ".wander", XrmoptionNoArg, "False" },
115   { "-titles", ".titles", XrmoptionNoArg, "True" },
116   { "+titles", ".titles", XrmoptionNoArg, "False" },
117   { "-duration",".duration",XrmoptionSepArg, 0 },
118   { "-which",  ".which",   XrmoptionSepArg, 0 },
119 };
120
121 static argtype vars[] = {
122   {&do_spin,   "spin",   "Spin",   DEF_SPIN,    t_Bool},
123   {&do_wander, "wander", "Wander", DEF_WANDER,  t_Bool},
124   {&do_titles, "titles", "Titles", DEF_TITLES,  t_Bool},
125   {&speed,     "speed",  "Speed",  DEF_SPEED,   t_Float},
126   {&duration,"duration","Duration",DEF_DURATION,t_Int},
127   {&do_which_str,"which", "Which", DEF_WHICH,   t_String},
128 };
129
130 ENTRYPOINT ModeSpecOpt polyhedra_opts = {countof(opts), opts, countof(vars), vars, NULL};
131
132
133 \f
134 /* Calculate the normals at each vertex of a face, and use the sum to
135    decide which normal to assign to the entire face.  This also solves
136    problems caused by nonconvex faces, in most (but not all) cases.
137  */
138 static void
139 kludge_normal (int n, const int *indices, const point *points)
140 {
141   XYZ normal = { 0, 0, 0 };
142   XYZ p = { 0, 0, 0 };
143   int i;
144
145   for (i = 0; i < n; ++i) {
146     int i1 = indices[i];
147     int i2 = indices[(i + 1) % n];
148     int i3 = indices[(i + 2) % n];
149     XYZ p1, p2, p3;
150
151     p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
152     p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
153     p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
154
155     p = calc_normal (p1, p2, p3);
156     normal.x += p.x;
157     normal.y += p.y;
158     normal.z += p.z;
159   }
160
161   /*normalize(&normal);*/
162   if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
163     glNormal3f (p.x, p.y, p.z);
164   } else {
165     glNormal3f (normal.x, normal.y, normal.z);
166   }
167 }
168
169
170 static void
171 load_fonts (ModeInfo *mi)
172 {
173   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
174   bp->font1_data = load_texture_font (mi->dpy, "titleFont");
175   bp->font2_data = load_texture_font (mi->dpy, "titleFont2");
176   bp->font3_data = load_texture_font (mi->dpy, "titleFont3");
177 }
178
179
180
181 static void
182 startup_blurb (ModeInfo *mi)
183 {
184   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
185   const char *s = "Computing polyhedra...";
186   texture_font_data *f = bp->font1_data;
187
188   glColor3f (0.8, 0.8, 0);
189   print_texture_label (mi->dpy, f,
190                        mi->xgwa.width, mi->xgwa.height,
191                        0, s);
192   glFinish();
193   glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
194 }
195
196
197
198 /* Window management, etc
199  */
200 ENTRYPOINT void
201 reshape_polyhedra (ModeInfo *mi, int width, int height)
202 {
203   GLfloat h = (GLfloat) height / (GLfloat) width;
204
205   glViewport (0, 0, (GLint) width, (GLint) height);
206
207   glMatrixMode(GL_PROJECTION);
208   glLoadIdentity();
209   gluPerspective (30.0, 1/h, 1.0, 100.0);
210
211   glMatrixMode(GL_MODELVIEW);
212   glLoadIdentity();
213   gluLookAt( 0.0, 0.0, 30.0,
214              0.0, 0.0, 0.0,
215              0.0, 1.0, 0.0);
216
217   glClear(GL_COLOR_BUFFER_BIT);
218 }
219
220
221 ENTRYPOINT Bool
222 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
223 {
224   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
225
226   if (gltrackball_event_handler (event, bp->trackball,
227                                  MI_WIDTH (mi), MI_HEIGHT (mi),
228                                  &bp->button_down_p))
229     return True;
230   else if (event->xany.type == KeyPress)
231     {
232       KeySym keysym;
233       char c = 0;
234       XLookupString (&event->xkey, &c, 1, &keysym, 0);
235
236       bp->change_to = -1;
237       if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
238         bp->change_to = random() % bp->npolyhedra;
239       else if (c == '>' || c == '.' || c == '+' || c == '=' ||
240                keysym == XK_Right || keysym == XK_Up || keysym == XK_Next)
241         bp->change_to = (bp->which + 1) % bp->npolyhedra;
242       else if (c == '<' || c == ',' || c == '-' || c == '_' ||
243                c == '\010' || c == '\177' ||
244                keysym == XK_Left || keysym == XK_Down || keysym == XK_Prior)
245         bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
246       else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
247         goto DEF;
248
249       if (bp->change_to != -1)
250         return True;
251     }
252   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
253     {
254     DEF:
255       bp->change_to = random() % bp->npolyhedra;
256       return True;
257     }
258
259   return False;
260 }
261
262
263 static void
264 draw_label (ModeInfo *mi)
265 {
266   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
267   polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
268   char label[1024];
269   char name2[255];
270   GLfloat color[4] = { 0.8, 0.8, 0.8, 1 };
271   texture_font_data *f;
272
273   if (!p || !do_titles) return;
274
275   strcpy (name2, p->name);
276   if (*p->class)
277     sprintf (name2 + strlen(name2), "  (%s)", p->class);
278
279   sprintf (label,
280            "Polyhedron %d:   \t%s\n\n"
281            "Wythoff Symbol:\t%s\n"
282            "Vertex Configuration:\t%s\n"
283            "Symmetry Group:\t%s\n"
284         /* "Dual of:              \t%s\n" */
285            "\n"
286            "Faces:\t  %d\n"
287            "Edges:\t  %d\n"
288            "Vertices:\t  %d\n"
289            "Density:\t  %d\n"
290            "Euler:\t%s%d\n",
291            bp->which, name2, p->wythoff, p->config, p->group,
292         /* p->dual, */
293            p->logical_faces, p->nedges, p->logical_vertices,
294            p->density, (p->chi < 0 ? "" : "  "), p->chi);
295
296   if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
297     f = bp->font1_data;
298   else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
299     f = bp->font2_data;                                /* small font */
300   else
301     f = bp->font3_data;                                /* tiny font */
302
303   glColor4fv (color);
304   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
305   print_texture_label (mi->dpy, f,
306                        mi->xgwa.width, mi->xgwa.height,
307                        1, label);
308 }
309
310
311 #ifdef HAVE_TESS
312 static void
313 tess_error (GLenum errorCode)
314 {
315   fprintf (stderr, "%s: tesselation error: %s\n",
316            progname, gluErrorString(errorCode));
317   abort();
318 }
319 #endif /* HAVE_TESS */
320
321
322 static void
323 new_polyhedron (ModeInfo *mi)
324 {
325   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
326   polyhedron *p;
327   int wire = MI_IS_WIREFRAME(mi);
328   int i;
329
330   /* Use the GLU polygon tesselator so that nonconvex faces are displayed
331      correctly (e.g., for the "pentagrammic concave deltohedron").
332    */
333 # ifdef HAVE_TESS
334   GLUtesselator *tobj = gluNewTess();
335   gluTessCallback (tobj, GLU_TESS_BEGIN,  (void (*) (void)) &glBegin);
336   gluTessCallback (tobj, GLU_TESS_END,    (void (*) (void)) &glEnd);
337   gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
338   gluTessCallback (tobj, GLU_TESS_ERROR,  (void (*) (void)) &tess_error);
339 # endif /* HAVE_TESS */
340
341   mi->polygon_count = 0;
342
343   bp->ncolors = 128;
344   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
345   make_random_colormap (0, 0, 0,
346                         bp->colors, &bp->ncolors,
347                         True, False, 0, False);
348
349   if (do_which >= bp->npolyhedra)
350     do_which = -1;
351
352   bp->which = (bp->change_to != -1 ? bp->change_to :
353                do_which >= 0 ? do_which :
354                (random() % bp->npolyhedra));
355   bp->change_to = -1;
356   p = bp->polyhedra[bp->which];
357
358   if (wire)
359     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
360
361   glNewList (bp->object_list, GL_COMPILE);
362   if (bp->which == bp->npolyhedra-1)
363     {
364       GLfloat bcolor[4];
365       bcolor[0] = bp->colors[0].red   / 65536.0;
366       bcolor[1] = bp->colors[0].green / 65536.0;
367       bcolor[2] = bp->colors[0].blue  / 65536.0;
368       bcolor[3] = 1.0;
369       if (wire)
370         glColor3f (0, 1, 0);
371       else
372         glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
373
374       glScalef (0.8, 0.8, 0.8);
375       p->nfaces = unit_teapot (6, wire);
376       p->nedges = p->nfaces * 3 / 2;
377       p->npoints = p->nfaces * 3;
378       p->logical_faces = p->nfaces;
379       p->logical_vertices = p->npoints;
380     }
381   else
382     {
383       glFrontFace (GL_CCW);
384       for (i = 0; i < p->nfaces; i++)
385         {
386           int j;
387           face *f = &p->faces[i];
388
389           if (f->color > 64 || f->color < 0) abort();
390           if (wire)
391             glColor3f (0, 1, 0);
392           else
393             {
394               GLfloat bcolor[4];
395               bcolor[0] = bp->colors[f->color].red   / 65536.0;
396               bcolor[1] = bp->colors[f->color].green / 65536.0;
397               bcolor[2] = bp->colors[f->color].blue  / 65536.0;
398               bcolor[3] = 1.0;
399               glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
400             }
401
402           kludge_normal (f->npoints, f->points, p->points);
403       
404 # ifdef HAVE_TESS
405           gluTessBeginPolygon (tobj, 0);
406           gluTessBeginContour (tobj);
407           for (j = 0; j < f->npoints; j++)
408             {
409               point *pp = &p->points[f->points[j]];
410               gluTessVertex (tobj, &pp->x, &pp->x);
411             }
412           gluTessEndContour (tobj);
413           gluTessEndPolygon (tobj);
414 # else  /* !HAVE_TESS */
415           glBegin (wire ? GL_LINE_LOOP :
416                    f->npoints == 3 ? GL_TRIANGLES :
417                    f->npoints == 4 ? GL_QUADS :
418                    GL_POLYGON);
419           for (j = 0; j < f->npoints; j++)
420             {
421               point *pp = &p->points[f->points[j]];
422               glVertex3f (pp->x, pp->y, pp->z);
423             }
424           glEnd();
425 # endif /* !HAVE_TESS */
426         }
427     }
428   glEndList ();
429
430   mi->polygon_count += p->nfaces;
431 # ifdef HAVE_TESS
432   gluDeleteTess (tobj);
433 # endif
434 }
435
436
437 static void
438 construct_teapot (ModeInfo *mi)
439 {
440   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
441   int n = bp->npolyhedra-1;
442   polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
443   p->number = n;
444   p->wythoff = strdup("X00398|1984");
445   p->name = strdup("Teapot");
446   p->dual = strdup("");
447   p->config = strdup("Melitta");
448   p->group = strdup("Teapotahedral (Newell[1975])");
449   p->class = strdup("Utah Teapotahedron");
450   bp->polyhedra[n] = p;
451 }
452
453
454 ENTRYPOINT void 
455 init_polyhedra (ModeInfo *mi)
456 {
457   polyhedra_configuration *bp;
458   int wire = MI_IS_WIREFRAME(mi);
459
460 # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
461   MI_IS_WIREFRAME(mi) = 0;
462   wire = 0;
463 # endif
464
465   MI_INIT (mi, bps, NULL);
466
467   bp = &bps[MI_SCREEN(mi)];
468
469   bp->glx_context = init_GL(mi);
470
471   bp->which = -1;
472   load_fonts (mi);
473   startup_blurb (mi);
474
475   if (!wire)
476     {
477       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
478       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
479       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
480       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
481
482       glEnable(GL_LIGHTING);
483       glEnable(GL_LIGHT0);
484       glEnable(GL_DEPTH_TEST);
485       /* glEnable(GL_CULL_FACE); */
486
487       /* We need two-sided lighting for polyhedra where both sides of
488          a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
489        */
490       glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
491
492       glLightfv(GL_LIGHT0, GL_POSITION, pos);
493       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
494       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
495       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
496     }
497
498   {
499     double spin_speed   = 2.0;
500     double wander_speed = 0.05;
501     double spin_accel   = 0.2;
502
503     bp->rot = make_rotator (do_spin ? spin_speed : 0,
504                             do_spin ? spin_speed : 0,
505                             do_spin ? spin_speed : 0,
506                             spin_accel,
507                             do_wander ? wander_speed : 0,
508                             True);
509     bp->trackball = gltrackball_init (True);
510   }
511
512   bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
513   construct_teapot (mi);
514
515   bp->object_list = glGenLists (1);
516   bp->change_to = -1;
517
518   {
519     int x;
520     char c;
521     do_which = -1;
522     if (!strcasecmp (do_which_str, "random"))
523       ;
524     else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
525       {
526         if (x >= 0 && x < bp->npolyhedra) 
527           do_which = x;
528         else
529           fprintf (stderr, 
530                    "%s: polyhedron %d does not exist: there are only %d.\n",
531                    progname, x, bp->npolyhedra-1);
532       }
533     else if (*do_which_str)
534       {
535         char *s;
536         for (s = do_which_str; *s; s++)
537           if (*s == '-' || *s == '_') *s = ' ';
538
539         for (x = 0; x < bp->npolyhedra; x++)
540           if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
541               !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
542               !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
543               !strcasecmp (do_which_str, bp->polyhedra[x]->config))
544             {
545               do_which = x;
546               break;
547             }
548         if (do_which < 0)
549           {
550             fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
551                      progname, do_which_str);
552             exit (1);
553           }
554       }
555   }
556
557   new_polyhedron (mi);
558   reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
559   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
560
561 }
562
563
564 ENTRYPOINT void
565 draw_polyhedra (ModeInfo *mi)
566 {
567   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
568   Display *dpy = MI_DISPLAY(mi);
569   Window window = MI_WINDOW(mi);
570
571   static const GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
572   GLfloat bshiny    = 128.0;
573
574   if (!bp->glx_context)
575     return;
576
577   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
578
579   if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
580     ;
581   else if (bp->mode == 0)
582     {
583       if (bp->change_to >= 0)
584         bp->change_tick = 999, bp->last_change_time = 1;
585       if (bp->change_tick++ > 10)
586         {
587           time_t now = time((time_t *) 0);
588           if (bp->last_change_time == 0) bp->last_change_time = now;
589           bp->change_tick = 0;
590           if (!bp->button_down_p && now - bp->last_change_time >= duration)
591             {
592               bp->mode = 1;    /* go out */
593               bp->mode_tick = 20 / speed;
594               bp->last_change_time = now;
595             }
596         }
597     }
598   else if (bp->mode == 1)   /* out */
599     {
600       if (--bp->mode_tick <= 0)
601         {
602           new_polyhedron (mi);
603           bp->mode_tick = 20 / speed;
604           bp->mode = 2;  /* go in */
605         }
606     }
607   else if (bp->mode == 2)   /* in */
608     {
609       if (--bp->mode_tick <= 0)
610         bp->mode = 0;  /* normal */
611     }
612   else
613     abort();
614
615   glShadeModel(GL_FLAT);
616   glEnable(GL_NORMALIZE);
617
618   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
619
620   glPushMatrix ();
621
622 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
623   {
624     GLfloat h = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
625     int o = (int) current_device_rotation();
626     if (o != 0 && o != 180 && o != -180)
627       glScalef (1/h, 1/h, 1/h);
628   }
629 # endif
630
631   glScalef(1.1, 1.1, 1.1);
632
633   {
634     double x, y, z;
635     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
636     glTranslatef((x - 0.5) * 8,
637                  (y - 0.5) * 8,
638                  (z - 0.5) * 15);
639
640     gltrackball_rotate (bp->trackball);
641
642     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
643     glRotatef (x * 360, 1.0, 0.0, 0.0);
644     glRotatef (y * 360, 0.0, 1.0, 0.0);
645     glRotatef (z * 360, 0.0, 0.0, 1.0);
646   }
647
648   glScalef (2.0, 2.0, 2.0);
649
650   glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  bspec);
651   glMateriali  (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
652
653   if (bp->mode != 0)
654     {
655       GLfloat s = (bp->mode == 1
656                    ? bp->mode_tick / (20 / speed)
657                    : ((20 / speed) - bp->mode_tick + 1) / (20 / speed));
658       glScalef (s, s, s);
659     }
660
661   glScalef (2, 2, 2);
662   glCallList (bp->object_list);
663   if (bp->mode == 0 && !bp->button_down_p)
664     draw_label (mi);    /* print_texture_font can't go inside a display list */
665
666   glPopMatrix ();
667
668   if (mi->fps_p) do_fps (mi);
669   glFinish();
670
671   glXSwapBuffers(dpy, window);
672 }
673
674 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)
675
676 #endif /* USE_GL */