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