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