From http://www.jwz.org/xscreensaver/xscreensaver-5.38.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 free_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   int y = 0;
205
206   if (width > height * 5) {   /* tiny window: show middle */
207     height = width * 9/16;
208     y = -height/2;
209     h = height / (GLfloat) width;
210   }
211
212   glViewport (0, y, (GLint) width, (GLint) height);
213
214   glMatrixMode(GL_PROJECTION);
215   glLoadIdentity();
216   gluPerspective (30.0, 1/h, 1.0, 100.0);
217
218   glMatrixMode(GL_MODELVIEW);
219   glLoadIdentity();
220   gluLookAt( 0.0, 0.0, 30.0,
221              0.0, 0.0, 0.0,
222              0.0, 1.0, 0.0);
223
224   glClear(GL_COLOR_BUFFER_BIT);
225 }
226
227
228 ENTRYPOINT Bool
229 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
230 {
231   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
232
233   if (gltrackball_event_handler (event, bp->trackball,
234                                  MI_WIDTH (mi), MI_HEIGHT (mi),
235                                  &bp->button_down_p))
236     return True;
237   else if (event->xany.type == KeyPress)
238     {
239       KeySym keysym;
240       char c = 0;
241       XLookupString (&event->xkey, &c, 1, &keysym, 0);
242
243       bp->change_to = -1;
244       if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
245         bp->change_to = random() % bp->npolyhedra;
246       else if (c == '>' || c == '.' || c == '+' || c == '=' ||
247                keysym == XK_Right || keysym == XK_Up || keysym == XK_Next)
248         bp->change_to = (bp->which + 1) % bp->npolyhedra;
249       else if (c == '<' || c == ',' || c == '-' || c == '_' ||
250                c == '\010' || c == '\177' ||
251                keysym == XK_Left || keysym == XK_Down || keysym == XK_Prior)
252         bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
253       else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
254         goto DEF;
255
256       if (bp->change_to != -1)
257         return True;
258     }
259   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
260     {
261     DEF:
262       bp->change_to = random() % bp->npolyhedra;
263       return True;
264     }
265
266   return False;
267 }
268
269
270 static void
271 draw_label (ModeInfo *mi)
272 {
273   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
274   polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
275   char label[1024];
276   char name2[255];
277   GLfloat color[4] = { 0.8, 0.8, 0.8, 1 };
278   texture_font_data *f;
279
280   if (!p || !do_titles) return;
281
282   strcpy (name2, p->name);
283   if (*p->class)
284     sprintf (name2 + strlen(name2), "  (%s)", p->class);
285
286   sprintf (label,
287            "Polyhedron %d:   \t%s\n\n"
288            "Wythoff Symbol:\t%s\n"
289            "Vertex Configuration:\t%s\n"
290            "Symmetry Group:\t%s\n"
291         /* "Dual of:              \t%s\n" */
292            "\n"
293            "Faces:\t  %d\n"
294            "Edges:\t  %d\n"
295            "Vertices:\t  %d\n"
296            "Density:\t  %d\n"
297            "Euler:\t%s%d\n",
298            bp->which, name2, p->wythoff, p->config, p->group,
299         /* p->dual, */
300            p->logical_faces, p->nedges, p->logical_vertices,
301            p->density, (p->chi < 0 ? "" : "  "), p->chi);
302
303   if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
304     f = bp->font1_data;
305   else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
306     f = bp->font2_data;                                /* small font */
307   else
308     f = bp->font3_data;                                /* tiny font */
309
310   glColor4fv (color);
311   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
312   print_texture_label (mi->dpy, f,
313                        mi->xgwa.width, mi->xgwa.height,
314                        1, label);
315 }
316
317
318 #ifdef HAVE_TESS
319 static void
320 tess_error (GLenum errorCode)
321 {
322   fprintf (stderr, "%s: tesselation error: %s\n",
323            progname, gluErrorString(errorCode));
324   abort();
325 }
326 #endif /* HAVE_TESS */
327
328
329 static void
330 new_polyhedron (ModeInfo *mi)
331 {
332   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
333   polyhedron *p;
334   int wire = MI_IS_WIREFRAME(mi);
335   int i;
336
337   /* Use the GLU polygon tesselator so that nonconvex faces are displayed
338      correctly (e.g., for the "pentagrammic concave deltohedron").
339    */
340 # ifdef HAVE_TESS
341   GLUtesselator *tobj = gluNewTess();
342   gluTessCallback (tobj, GLU_TESS_BEGIN,  (void (*) (void)) &glBegin);
343   gluTessCallback (tobj, GLU_TESS_END,    (void (*) (void)) &glEnd);
344   gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
345   gluTessCallback (tobj, GLU_TESS_ERROR,  (void (*) (void)) &tess_error);
346 # endif /* HAVE_TESS */
347
348   mi->polygon_count = 0;
349
350   bp->ncolors = 128;
351   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
352   make_random_colormap (0, 0, 0,
353                         bp->colors, &bp->ncolors,
354                         True, False, 0, False);
355
356   if (do_which >= bp->npolyhedra)
357     do_which = -1;
358
359   bp->which = (bp->change_to != -1 ? bp->change_to :
360                do_which >= 0 ? do_which :
361                (random() % bp->npolyhedra));
362   bp->change_to = -1;
363   p = bp->polyhedra[bp->which];
364
365   if (wire)
366     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
367
368   glNewList (bp->object_list, GL_COMPILE);
369   if (bp->which == bp->npolyhedra-1)
370     {
371       GLfloat bcolor[4];
372       bcolor[0] = bp->colors[0].red   / 65536.0;
373       bcolor[1] = bp->colors[0].green / 65536.0;
374       bcolor[2] = bp->colors[0].blue  / 65536.0;
375       bcolor[3] = 1.0;
376       if (wire)
377         glColor3f (0, 1, 0);
378       else
379         glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
380
381       glScalef (0.8, 0.8, 0.8);
382       p->nfaces = unit_teapot (6, wire);
383       p->nedges = p->nfaces * 3 / 2;
384       p->npoints = p->nfaces * 3;
385       p->logical_faces = p->nfaces;
386       p->logical_vertices = p->npoints;
387     }
388   else
389     {
390       glFrontFace (GL_CCW);
391       for (i = 0; i < p->nfaces; i++)
392         {
393           int j;
394           face *f = &p->faces[i];
395
396           if (f->color > 64 || f->color < 0) abort();
397           if (wire)
398             glColor3f (0, 1, 0);
399           else
400             {
401               GLfloat bcolor[4];
402               bcolor[0] = bp->colors[f->color].red   / 65536.0;
403               bcolor[1] = bp->colors[f->color].green / 65536.0;
404               bcolor[2] = bp->colors[f->color].blue  / 65536.0;
405               bcolor[3] = 1.0;
406               glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
407             }
408
409           kludge_normal (f->npoints, f->points, p->points);
410       
411 # ifdef HAVE_TESS
412           gluTessBeginPolygon (tobj, 0);
413           gluTessBeginContour (tobj);
414           for (j = 0; j < f->npoints; j++)
415             {
416               point *pp = &p->points[f->points[j]];
417               gluTessVertex (tobj, &pp->x, &pp->x);
418             }
419           gluTessEndContour (tobj);
420           gluTessEndPolygon (tobj);
421 # else  /* !HAVE_TESS */
422           glBegin (wire ? GL_LINE_LOOP :
423                    f->npoints == 3 ? GL_TRIANGLES :
424                    f->npoints == 4 ? GL_QUADS :
425                    GL_POLYGON);
426           for (j = 0; j < f->npoints; j++)
427             {
428               point *pp = &p->points[f->points[j]];
429               glVertex3f (pp->x, pp->y, pp->z);
430             }
431           glEnd();
432 # endif /* !HAVE_TESS */
433         }
434     }
435   glEndList ();
436
437   mi->polygon_count += p->nfaces;
438 # ifdef HAVE_TESS
439   gluDeleteTess (tobj);
440 # endif
441 }
442
443
444 static void
445 construct_teapot (ModeInfo *mi)
446 {
447   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
448   int n = bp->npolyhedra-1;
449   polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
450   p->number = n;
451   p->wythoff = strdup("X00398|1984");
452   p->name = strdup("Teapot");
453   p->dual = strdup("");
454   p->config = strdup("Melitta");
455   p->group = strdup("Teapotahedral (Newell[1975])");
456   p->class = strdup("Utah Teapotahedron");
457   bp->polyhedra[n] = p;
458 }
459
460
461 ENTRYPOINT void 
462 init_polyhedra (ModeInfo *mi)
463 {
464   polyhedra_configuration *bp;
465   int wire = MI_IS_WIREFRAME(mi);
466
467 # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
468   MI_IS_WIREFRAME(mi) = 0;
469   wire = 0;
470 # endif
471
472   MI_INIT (mi, bps);
473
474   bp = &bps[MI_SCREEN(mi)];
475
476   bp->glx_context = init_GL(mi);
477
478   bp->which = -1;
479   load_fonts (mi);
480   startup_blurb (mi);
481
482   if (!wire)
483     {
484       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
485       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
486       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
487       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
488
489       glEnable(GL_LIGHTING);
490       glEnable(GL_LIGHT0);
491       glEnable(GL_DEPTH_TEST);
492       /* glEnable(GL_CULL_FACE); */
493
494       /* We need two-sided lighting for polyhedra where both sides of
495          a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
496        */
497       glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
498
499       glLightfv(GL_LIGHT0, GL_POSITION, pos);
500       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
501       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
502       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
503     }
504
505   {
506     double spin_speed   = 2.0;
507     double wander_speed = 0.05;
508     double spin_accel   = 0.2;
509
510     bp->rot = make_rotator (do_spin ? spin_speed : 0,
511                             do_spin ? spin_speed : 0,
512                             do_spin ? spin_speed : 0,
513                             spin_accel,
514                             do_wander ? wander_speed : 0,
515                             True);
516     bp->trackball = gltrackball_init (True);
517   }
518
519   bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
520   construct_teapot (mi);
521
522   bp->object_list = glGenLists (1);
523   bp->change_to = -1;
524
525   {
526     int x;
527     char c;
528     do_which = -1;
529     if (!strcasecmp (do_which_str, "random"))
530       ;
531     else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
532       {
533         if (x >= 0 && x < bp->npolyhedra) 
534           do_which = x;
535         else
536           fprintf (stderr, 
537                    "%s: polyhedron %d does not exist: there are only %d.\n",
538                    progname, x, bp->npolyhedra-1);
539       }
540     else if (*do_which_str)
541       {
542         char *s;
543         for (s = do_which_str; *s; s++)
544           if (*s == '-' || *s == '_') *s = ' ';
545
546         for (x = 0; x < bp->npolyhedra; x++)
547           if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
548               !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
549               !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
550               !strcasecmp (do_which_str, bp->polyhedra[x]->config))
551             {
552               do_which = x;
553               break;
554             }
555         if (do_which < 0)
556           {
557             fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
558                      progname, do_which_str);
559             exit (1);
560           }
561       }
562   }
563
564   new_polyhedron (mi);
565   reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
566   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
567
568 }
569
570
571 ENTRYPOINT void
572 draw_polyhedra (ModeInfo *mi)
573 {
574   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
575   Display *dpy = MI_DISPLAY(mi);
576   Window window = MI_WINDOW(mi);
577
578   static const GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
579   GLfloat bshiny    = 128.0;
580
581   if (!bp->glx_context)
582     return;
583
584   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
585
586   if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
587     ;
588   else if (bp->mode == 0)
589     {
590       if (bp->change_to >= 0)
591         bp->change_tick = 999, bp->last_change_time = 1;
592       if (bp->change_tick++ > 10)
593         {
594           time_t now = time((time_t *) 0);
595           if (bp->last_change_time == 0) bp->last_change_time = now;
596           bp->change_tick = 0;
597           if (!bp->button_down_p && now - bp->last_change_time >= duration)
598             {
599               bp->mode = 1;    /* go out */
600               bp->mode_tick = 20 / speed;
601               bp->last_change_time = now;
602             }
603         }
604     }
605   else if (bp->mode == 1)   /* out */
606     {
607       if (--bp->mode_tick <= 0)
608         {
609           new_polyhedron (mi);
610           bp->mode_tick = 20 / speed;
611           bp->mode = 2;  /* go in */
612         }
613     }
614   else if (bp->mode == 2)   /* in */
615     {
616       if (--bp->mode_tick <= 0)
617         bp->mode = 0;  /* normal */
618     }
619   else
620     abort();
621
622   glShadeModel(GL_FLAT);
623   glEnable(GL_NORMALIZE);
624
625   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
626
627   glPushMatrix ();
628
629 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
630   {
631     GLfloat h = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
632     int o = (int) current_device_rotation();
633     if (o != 0 && o != 180 && o != -180)
634       glScalef (1/h, 1/h, 1/h);
635   }
636 # endif
637
638   glScalef(1.1, 1.1, 1.1);
639
640   {
641     double x, y, z;
642     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
643     glTranslatef((x - 0.5) * 8,
644                  (y - 0.5) * 8,
645                  (z - 0.5) * 15);
646
647     gltrackball_rotate (bp->trackball);
648
649     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
650     glRotatef (x * 360, 1.0, 0.0, 0.0);
651     glRotatef (y * 360, 0.0, 1.0, 0.0);
652     glRotatef (z * 360, 0.0, 0.0, 1.0);
653   }
654
655   glScalef (2.0, 2.0, 2.0);
656
657   glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  bspec);
658   glMateriali  (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
659
660   if (bp->mode != 0)
661     {
662       GLfloat s = (bp->mode == 1
663                    ? bp->mode_tick / (20 / speed)
664                    : ((20 / speed) - bp->mode_tick + 1) / (20 / speed));
665       glScalef (s, s, s);
666     }
667
668   glScalef (2, 2, 2);
669   glCallList (bp->object_list);
670   if (bp->mode == 0 && !bp->button_down_p)
671     draw_label (mi);    /* print_texture_font can't go inside a display list */
672
673   glPopMatrix ();
674
675   if (mi->fps_p) do_fps (mi);
676   glFinish();
677
678   glXSwapBuffers(dpy, window);
679 }
680
681 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)
682
683 #endif /* USE_GL */