From http://www.jwz.org/xscreensaver/xscreensaver-5.35.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   if (!bps) {
466     bps = (polyhedra_configuration *)
467       calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
468     if (!bps) {
469       fprintf(stderr, "%s: out of memory\n", progname);
470       exit(1);
471     }
472   }
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 */