b919ca1830d4f822c985e2dfb96ba2af03b8fe5d
[xscreensaver] / hacks / glx / polyhedra-gl.c
1 /* polyhedra, Copyright (c) 2004 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 #include <X11/Intrinsic.h>
19
20 extern XtAppContext app;
21
22 #define PROGCLASS       "Polyhedra"
23 #define HACK_INIT       init_polyhedra
24 #define HACK_DRAW       draw_polyhedra
25 #define HACK_RESHAPE    reshape_polyhedra
26 #define HACK_HANDLE_EVENT polyhedra_handle_event
27 #define EVENT_MASK      PointerMotionMask
28 #define sws_opts        xlockmore_opts
29
30 #define DEF_SPIN        "True"
31 #define DEF_WANDER      "True"
32 #define DEF_SPEED       "1.0"
33 #define DEF_TITLES      "True"
34 #define DEF_DURATION    "12"
35 #define DEF_WHICH       "-1"
36
37 #define DEFAULTS        "*delay:        30000         \n" \
38                         "*showFPS:      False         \n" \
39                         "*wireframe:    False         \n" \
40                         "*titleFont:  -*-times-bold-r-normal-*-180-*\n" \
41                         "*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
42                         "*titleFont3: -*-times-bold-r-normal-*-80-*\n"  \
43
44
45 #undef countof
46 #define countof(x) (sizeof((x))/sizeof((*x)))
47
48 #include "xlockmore.h"
49 #include <GL/glu.h>
50
51 #include "glxfonts.h"
52 #include "normals.h"
53 #include "polyhedra.h"
54 #include "colors.h"
55 #include "rotator.h"
56 #include "gltrackball.h"
57 #include <ctype.h>
58
59 #ifdef USE_GL /* whole file */
60
61 #include <GL/glu.h>
62
63 typedef struct {
64   GLXContext *glx_context;
65   rotator *rot;
66   trackball_state *trackball;
67   Bool button_down_p;
68
69   int npolyhedra;
70   polyhedron **polyhedra;
71
72   int which;
73   int change_to;
74   GLuint object_list;
75   GLuint title_list;
76
77   int mode;  /* 0 = normal, 1 = out, 2 = in */
78   int mode_tick;
79
80   int ncolors;
81   XColor *colors;
82
83   XFontStruct *xfont1, *xfont2, *xfont3;
84   GLuint font1_dlist, font2_dlist, font3_dlist;
85
86 } polyhedra_configuration;
87
88 static polyhedra_configuration *bps = NULL;
89
90 static Bool do_spin;
91 static GLfloat speed;
92 static Bool do_wander;
93 static Bool do_titles;
94 static int duration;
95 static int do_which;
96 static char *do_which_str;
97
98 static XrmOptionDescRec opts[] = {
99   { "-spin",   ".spin",   XrmoptionNoArg, "True" },
100   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
101   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
102   { "-wander", ".wander", XrmoptionNoArg, "True" },
103   { "+wander", ".wander", XrmoptionNoArg, "False" },
104   { "-titles", ".titles", XrmoptionNoArg, "True" },
105   { "+titles", ".titles", XrmoptionNoArg, "False" },
106   { "-duration",".duration",XrmoptionSepArg, 0 },
107   { "-which",  ".which",   XrmoptionSepArg, 0 },
108 };
109
110 static argtype vars[] = {
111   {&do_spin,   "spin",   "Spin",   DEF_SPIN,    t_Bool},
112   {&do_wander, "wander", "Wander", DEF_WANDER,  t_Bool},
113   {&do_titles, "titles", "Titles", DEF_TITLES,  t_Bool},
114   {&speed,     "speed",  "Speed",  DEF_SPEED,   t_Float},
115   {&duration,"duration","Duration",DEF_DURATION,t_Int},
116   {&do_which_str,"which", "Which", DEF_WHICH,   t_String},
117 };
118
119 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
120
121
122 \f
123 /* Calculate the normals at each vertex of a face, and use the sum to
124    decide which normal to assign to the entire face.  This also solves
125    problems caused by nonconvex faces, in most (but not all) cases.
126  */
127 static void
128 kludge_normal (int n, const int *indices, const point *points)
129 {
130   XYZ normal = { 0, 0, 0 };
131   XYZ p;
132   int i;
133
134   for (i = 0; i < n; ++i) {
135     int i1 = indices[i];
136     int i2 = indices[(i + 1) % n];
137     int i3 = indices[(i + 2) % n];
138     XYZ p1, p2, p3;
139
140     p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
141     p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
142     p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
143
144     p = calc_normal (p1, p2, p3);
145     normal.x += p.x;
146     normal.y += p.y;
147     normal.z += p.z;
148   }
149
150   /*normalize(&normal);*/
151   if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
152     glNormal3f (p.x, p.y, p.z);
153   } else {
154     glNormal3f (normal.x, normal.y, normal.z);
155   }
156 }
157
158
159 static void
160 load_fonts (ModeInfo *mi)
161 {
162   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
163   load_font (mi->dpy, "titleFont",  &bp->xfont1, &bp->font1_dlist);
164   load_font (mi->dpy, "titleFont2", &bp->xfont2, &bp->font2_dlist);
165   load_font (mi->dpy, "titleFont3", &bp->xfont3, &bp->font3_dlist);
166 }
167
168
169
170 static void
171 startup_blurb (ModeInfo *mi)
172 {
173   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
174   const char *s = "Computing polyhedra...";
175   glColor3f (0.8, 0.8, 0);
176   print_gl_string (mi->dpy, bp->xfont1, bp->font1_dlist,
177                    mi->xgwa.width, mi->xgwa.height,
178                    mi->xgwa.width - (string_width (bp->xfont1, s) + 40),
179                    mi->xgwa.height - 10,
180                    s);
181   glFinish();
182   glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
183 }
184
185
186
187 /* Window management, etc
188  */
189 static void new_label (ModeInfo *mi);
190
191 void
192 reshape_polyhedra (ModeInfo *mi, int width, int height)
193 {
194   GLfloat h = (GLfloat) height / (GLfloat) width;
195
196   glViewport (0, 0, (GLint) width, (GLint) height);
197
198   glMatrixMode(GL_PROJECTION);
199   glLoadIdentity();
200   gluPerspective (30.0, 1/h, 1.0, 100.0);
201
202   glMatrixMode(GL_MODELVIEW);
203   glLoadIdentity();
204   gluLookAt( 0.0, 0.0, 30.0,
205              0.0, 0.0, 0.0,
206              0.0, 1.0, 0.0);
207
208   glClear(GL_COLOR_BUFFER_BIT);
209
210   /* need to re-render the text when the window size changes */
211   new_label (mi);
212 }
213
214
215 Bool
216 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
217 {
218   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
219
220   if (event->xany.type == ButtonPress &&
221       event->xbutton.button == Button1)
222     {
223       bp->button_down_p = True;
224       gltrackball_start (bp->trackball,
225                          event->xbutton.x, event->xbutton.y,
226                          MI_WIDTH (mi), MI_HEIGHT (mi));
227       return True;
228     }
229   else if (event->xany.type == ButtonRelease &&
230            event->xbutton.button == Button1)
231     {
232       bp->button_down_p = False;
233       return True;
234     }
235   else if (event->xany.type == ButtonPress &&
236            (event->xbutton.button == Button4 ||
237             event->xbutton.button == Button5))
238     {
239       gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
240                               !!event->xbutton.state);
241       return True;
242     }
243   else if (event->xany.type == KeyPress)
244     {
245       KeySym keysym;
246       char c = 0;
247       XLookupString (&event->xkey, &c, 1, &keysym, 0);
248
249       bp->change_to = -1;
250       if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
251         bp->change_to = random() % bp->npolyhedra;
252       else if (c == '>' || c == '.' || c == '+' || c == '=')
253         bp->change_to = (bp->which + 1) % bp->npolyhedra;
254       else if (c == '<' || c == ',' || c == '-' || c == '_' ||
255                c == '\010' || c == '\177')
256         bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
257
258       if (bp->change_to != -1)
259         return True;
260     }
261   else if (event->xany.type == MotionNotify &&
262            bp->button_down_p)
263     {
264       gltrackball_track (bp->trackball,
265                          event->xmotion.x, event->xmotion.y,
266                          MI_WIDTH (mi), MI_HEIGHT (mi));
267       return True;
268     }
269
270   return False;
271 }
272
273
274 static void
275 new_label (ModeInfo *mi)
276 {
277   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
278   polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
279
280   glNewList (bp->title_list, GL_COMPILE);
281   if (p && do_titles)
282     {
283       char label[1024];
284       char name2[255];
285       strcpy (name2, p->name);
286       if (*p->class)
287         sprintf (name2 + strlen(name2), "  (%s)", p->class);
288
289       sprintf (label,
290                "Polyhedron %d:   \t%s\n\n"
291                "Wythoff Symbol:\t%s\n"
292                "Vertex Configuration:\t%s\n"
293                "Symmetry Group:\t%s\n"
294             /* "Dual of:              \t%s\n" */
295                "\n"
296                "Faces:\t  %d\n"
297                "Edges:\t  %d\n"
298                "Vertices:\t  %d\n"
299                "Density:\t  %d\n"
300                "Euler:\t%s%d\n",
301                bp->which, name2, p->wythoff, p->config, p->group,
302             /* p->dual, */
303                p->logical_faces, p->nedges, p->logical_vertices,
304                p->density, (p->chi < 0 ? "" : "  "), p->chi);
305
306       {
307         XFontStruct *f;
308         GLuint fl;
309         if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
310           f = bp->xfont1, fl = bp->font1_dlist;                /* big font */
311         else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
312           f = bp->xfont2, fl = bp->font2_dlist;                /* small font */
313         else
314           f = bp->xfont3, fl = bp->font3_dlist;                /* tiny font */
315
316         glColor3f (0.8, 0.8, 0);
317         print_gl_string (mi->dpy, f, fl,
318                          mi->xgwa.width, mi->xgwa.height,
319                          10, mi->xgwa.height - 10,
320                          label);
321       }
322     }
323   glEndList ();
324 }
325
326
327 static void
328 tess_error (GLenum errorCode)
329 {
330   fprintf (stderr, "%s: tesselation error: %s\n",
331            progname, gluErrorString(errorCode));
332   exit (0);
333 }
334
335 static void
336 new_polyhedron (ModeInfo *mi)
337 {
338   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
339   polyhedron *p;
340   int wire = MI_IS_WIREFRAME(mi);
341   static GLfloat bcolor[4] = {0.0, 0.0, 0.0, 1.0};
342   int i;
343
344   /* Use the GLU polygon tesselator so that nonconvex faces are displayed
345      correctly (e.g., for the "pentagrammic concave deltohedron").
346    */
347   GLUtesselator *tobj = gluNewTess();
348   gluTessCallback (tobj, GLU_TESS_BEGIN,  (void (*) (void)) &glBegin);
349   gluTessCallback (tobj, GLU_TESS_END,    (void (*) (void)) &glEnd);
350   gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
351   gluTessCallback (tobj, GLU_TESS_ERROR,  (void (*) (void)) &tess_error);
352
353   mi->polygon_count = 0;
354
355   bp->ncolors = 128;
356   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
357   make_random_colormap (0, 0, 0,
358                         bp->colors, &bp->ncolors,
359                         True, False, 0, False);
360
361   if (do_which >= bp->npolyhedra)
362     do_which = -1;
363
364   bp->which = (bp->change_to != -1 ? bp->change_to :
365                do_which >= 0 ? do_which :
366                (random() % bp->npolyhedra));
367   bp->change_to = -1;
368   p = bp->polyhedra[bp->which];
369
370   new_label (mi);
371
372   glNewList (bp->object_list, GL_COMPILE);
373   for (i = 0; i < p->nfaces; i++)
374     {
375       int j;
376       face *f = &p->faces[i];
377
378       if (f->color > 64 || f->color < 0) abort();
379       if (wire)
380         glColor3f (0, 1, 0);
381       else
382         {
383           bcolor[0] = bp->colors[f->color].red   / 65536.0;
384           bcolor[1] = bp->colors[f->color].green / 65536.0;
385           bcolor[2] = bp->colors[f->color].blue  / 65536.0;
386           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
387         }
388
389       kludge_normal (f->npoints, f->points, p->points);
390       
391       gluTessBeginPolygon (tobj, 0);
392       gluTessBeginContour (tobj);
393       for (j = 0; j < f->npoints; j++)
394         {
395           point *pp = &p->points[f->points[j]];
396           gluTessVertex (tobj, &pp->x, &pp->x);
397         }
398       gluTessEndContour (tobj);
399       gluTessEndPolygon (tobj);
400     }
401   glEndList ();
402
403   mi->polygon_count += p->nfaces;
404   gluDeleteTess (tobj);
405 }
406
407
408 void 
409 init_polyhedra (ModeInfo *mi)
410 {
411   polyhedra_configuration *bp;
412   int wire = MI_IS_WIREFRAME(mi);
413
414   if (!bps) {
415     bps = (polyhedra_configuration *)
416       calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
417     if (!bps) {
418       fprintf(stderr, "%s: out of memory\n", progname);
419       exit(1);
420     }
421
422     bp = &bps[MI_SCREEN(mi)];
423   }
424
425   bp = &bps[MI_SCREEN(mi)];
426
427   bp->glx_context = init_GL(mi);
428
429   bp->which = -1;
430   load_fonts (mi);
431   startup_blurb (mi);
432
433   reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
434
435   if (!wire)
436     {
437       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
438       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
439       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
440       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
441
442       glEnable(GL_LIGHTING);
443       glEnable(GL_LIGHT0);
444       glEnable(GL_DEPTH_TEST);
445       /* glEnable(GL_CULL_FACE); */
446
447       /* We need two-sided lighting for polyhedra where both sides of
448          a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
449        */
450       glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
451
452       glLightfv(GL_LIGHT0, GL_POSITION, pos);
453       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
454       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
455       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
456     }
457
458   {
459     double spin_speed   = 2.0;
460     double wander_speed = 0.05;
461     double spin_accel   = 0.2;
462
463     bp->rot = make_rotator (do_spin ? spin_speed : 0,
464                             do_spin ? spin_speed : 0,
465                             do_spin ? spin_speed : 0,
466                             spin_accel,
467                             do_wander ? wander_speed : 0,
468                             True);
469     bp->trackball = gltrackball_init ();
470   }
471
472   bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
473
474   bp->object_list = glGenLists (1);
475   bp->title_list  = glGenLists (1);
476   bp->change_to = -1;
477
478   {
479     int x;
480     char c;
481     do_which = -1;
482     if (1 == sscanf (do_which_str, " %d %c", &x, &c))
483       do_which = x;
484     else if (*do_which_str)
485       {
486         char *s;
487         for (s = do_which_str; *s; s++)
488           if (*s == '-' || *s == '_') *s = ' ';
489
490         for (x = 0; x < bp->npolyhedra; x++)
491           if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
492               !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
493               !strcasecmp (do_which_str, bp->polyhedra[x]->config))
494             {
495               do_which = x;
496               break;
497             }
498         if (do_which < 0)
499           {
500             fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
501                      progname, do_which_str);
502             exit (1);
503           }
504       }
505   }
506
507   new_polyhedron (mi);
508 }
509
510
511 void
512 draw_polyhedra (ModeInfo *mi)
513 {
514   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
515   Display *dpy = MI_DISPLAY(mi);
516   Window window = MI_WINDOW(mi);
517
518   static time_t last_time = 0;
519
520   static GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
521   static GLfloat bshiny    = 128.0;
522
523   if (!bp->glx_context)
524     return;
525
526   if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
527     ;
528   else if (bp->mode == 0)
529     {
530       static int tick = 0;
531
532       if (bp->change_to >= 0)
533         tick = 999, last_time = 1;
534       if (tick++ > 10)
535         {
536           time_t now = time((time_t *) 0);
537           if (last_time == 0) last_time = now;
538           tick = 0;
539           if (!bp->button_down_p && now - last_time >= duration)
540             {
541               bp->mode = 1;    /* go out */
542               bp->mode_tick = 20 * speed;
543               last_time = now;
544             }
545         }
546     }
547   else if (bp->mode == 1)   /* out */
548     {
549       if (--bp->mode_tick <= 0)
550         {
551           new_polyhedron (mi);
552           bp->mode_tick = 20 * speed;
553           bp->mode = 2;  /* go in */
554         }
555     }
556   else if (bp->mode == 2)   /* in */
557     {
558       if (--bp->mode_tick <= 0)
559         bp->mode = 0;  /* normal */
560     }
561   else
562     abort();
563
564   glShadeModel(GL_FLAT);
565   glEnable(GL_NORMALIZE);
566
567   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
568
569   glPushMatrix ();
570
571   glScalef(1.1, 1.1, 1.1);
572
573   {
574     double x, y, z;
575     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
576     glTranslatef((x - 0.5) * 8,
577                  (y - 0.5) * 8,
578                  (z - 0.5) * 15);
579
580     gltrackball_rotate (bp->trackball);
581
582     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
583     glRotatef (x * 360, 1.0, 0.0, 0.0);
584     glRotatef (y * 360, 0.0, 1.0, 0.0);
585     glRotatef (z * 360, 0.0, 0.0, 1.0);
586   }
587
588   glScalef (2.0, 2.0, 2.0);
589
590   glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  bspec);
591   glMateriali  (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
592
593   if (bp->mode != 0)
594     {
595       GLfloat s = (bp->mode == 1
596                    ? bp->mode_tick / (20 * speed)
597                    : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
598       glScalef (s, s, s);
599     }
600
601   glScalef (2, 2, 2);
602   glCallList (bp->object_list);
603   if (bp->mode == 0 && !bp->button_down_p)
604     glCallList (bp->title_list);
605
606   glPopMatrix ();
607
608   if (mi->fps_p) do_fps (mi);
609   glFinish();
610
611   glXSwapBuffers(dpy, window);
612 }
613
614 #endif /* USE_GL */