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