5efec83a7569ba9f704fdc83321592da9ad1a64c
[xscreensaver] / hacks / glx / polyhedra-gl.c
1 /* polyhedra, Copyright (c) 2004-2006 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     {
229       gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
230                               !!event->xbutton.state);
231       return True;
232     }
233   else if (event->xany.type == KeyPress)
234     {
235       KeySym keysym;
236       char c = 0;
237       XLookupString (&event->xkey, &c, 1, &keysym, 0);
238
239       bp->change_to = -1;
240       if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
241         bp->change_to = random() % bp->npolyhedra;
242       else if (c == '>' || c == '.' || c == '+' || c == '=')
243         bp->change_to = (bp->which + 1) % bp->npolyhedra;
244       else if (c == '<' || c == ',' || c == '-' || c == '_' ||
245                c == '\010' || c == '\177')
246         bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
247
248       if (bp->change_to != -1)
249         return True;
250     }
251   else if (event->xany.type == MotionNotify &&
252            bp->button_down_p)
253     {
254       gltrackball_track (bp->trackball,
255                          event->xmotion.x, event->xmotion.y,
256                          MI_WIDTH (mi), MI_HEIGHT (mi));
257       return True;
258     }
259
260   return False;
261 }
262
263
264 static void
265 new_label (ModeInfo *mi)
266 {
267   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
268   polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
269
270   glNewList (bp->title_list, GL_COMPILE);
271   if (p && do_titles)
272     {
273       char label[1024];
274       char name2[255];
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       {
297         XFontStruct *f;
298         GLuint fl;
299         if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
300           f = bp->xfont1, fl = bp->font1_dlist;                /* big font */
301         else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
302           f = bp->xfont2, fl = bp->font2_dlist;                /* small font */
303         else
304           f = bp->xfont3, fl = bp->font3_dlist;                /* tiny font */
305
306         glColor3f (0.8, 0.8, 0);
307         print_gl_string (mi->dpy, f, fl,
308                          mi->xgwa.width, mi->xgwa.height,
309                          10, mi->xgwa.height - 10,
310                          label);
311       }
312     }
313   glEndList ();
314 }
315
316
317 static void
318 tess_error (GLenum errorCode)
319 {
320   fprintf (stderr, "%s: tesselation error: %s\n",
321            progname, gluErrorString(errorCode));
322   exit (0);
323 }
324
325 static void
326 new_polyhedron (ModeInfo *mi)
327 {
328   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
329   polyhedron *p;
330   int wire = MI_IS_WIREFRAME(mi);
331   int i;
332
333   /* Use the GLU polygon tesselator so that nonconvex faces are displayed
334      correctly (e.g., for the "pentagrammic concave deltohedron").
335    */
336   GLUtesselator *tobj = gluNewTess();
337   gluTessCallback (tobj, GLU_TESS_BEGIN,  (void (*) (void)) &glBegin);
338   gluTessCallback (tobj, GLU_TESS_END,    (void (*) (void)) &glEnd);
339   gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
340   gluTessCallback (tobj, GLU_TESS_ERROR,  (void (*) (void)) &tess_error);
341
342   mi->polygon_count = 0;
343
344   bp->ncolors = 128;
345   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
346   make_random_colormap (0, 0, 0,
347                         bp->colors, &bp->ncolors,
348                         True, False, 0, False);
349
350   if (do_which >= bp->npolyhedra)
351     do_which = -1;
352
353   bp->which = (bp->change_to != -1 ? bp->change_to :
354                do_which >= 0 ? do_which :
355                (random() % bp->npolyhedra));
356   bp->change_to = -1;
357   p = bp->polyhedra[bp->which];
358
359   new_label (mi);
360
361   if (wire)
362     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
363
364   glNewList (bp->object_list, GL_COMPILE);
365   for (i = 0; i < p->nfaces; i++)
366     {
367       int j;
368       face *f = &p->faces[i];
369
370       if (f->color > 64 || f->color < 0) abort();
371       if (wire)
372         glColor3f (0, 1, 0);
373       else
374         {
375           GLfloat bcolor[4];
376           bcolor[0] = bp->colors[f->color].red   / 65536.0;
377           bcolor[1] = bp->colors[f->color].green / 65536.0;
378           bcolor[2] = bp->colors[f->color].blue  / 65536.0;
379           bcolor[2] = 1.0;
380           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
381         }
382
383       kludge_normal (f->npoints, f->points, p->points);
384       
385       gluTessBeginPolygon (tobj, 0);
386       gluTessBeginContour (tobj);
387       for (j = 0; j < f->npoints; j++)
388         {
389           point *pp = &p->points[f->points[j]];
390           gluTessVertex (tobj, &pp->x, &pp->x);
391         }
392       gluTessEndContour (tobj);
393       gluTessEndPolygon (tobj);
394     }
395   glEndList ();
396
397   mi->polygon_count += p->nfaces;
398   gluDeleteTess (tobj);
399 }
400
401
402 ENTRYPOINT void 
403 init_polyhedra (ModeInfo *mi)
404 {
405   polyhedra_configuration *bp;
406   int wire = MI_IS_WIREFRAME(mi);
407
408   if (!bps) {
409     bps = (polyhedra_configuration *)
410       calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
411     if (!bps) {
412       fprintf(stderr, "%s: out of memory\n", progname);
413       exit(1);
414     }
415
416     bp = &bps[MI_SCREEN(mi)];
417   }
418
419   bp = &bps[MI_SCREEN(mi)];
420
421   bp->glx_context = init_GL(mi);
422
423   bp->which = -1;
424   load_fonts (mi);
425   startup_blurb (mi);
426
427   reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
428
429   if (!wire)
430     {
431       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
432       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
433       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
434       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
435
436       glEnable(GL_LIGHTING);
437       glEnable(GL_LIGHT0);
438       glEnable(GL_DEPTH_TEST);
439       /* glEnable(GL_CULL_FACE); */
440
441       /* We need two-sided lighting for polyhedra where both sides of
442          a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
443        */
444       glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
445
446       glLightfv(GL_LIGHT0, GL_POSITION, pos);
447       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
448       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
449       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
450     }
451
452   {
453     double spin_speed   = 2.0;
454     double wander_speed = 0.05;
455     double spin_accel   = 0.2;
456
457     bp->rot = make_rotator (do_spin ? spin_speed : 0,
458                             do_spin ? spin_speed : 0,
459                             do_spin ? spin_speed : 0,
460                             spin_accel,
461                             do_wander ? wander_speed : 0,
462                             True);
463     bp->trackball = gltrackball_init ();
464   }
465
466   bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
467
468   bp->object_list = glGenLists (1);
469   bp->title_list  = glGenLists (1);
470   bp->change_to = -1;
471
472   {
473     int x;
474     char c;
475     do_which = -1;
476     if (!strcasecmp (do_which_str, "random"))
477       ;
478     else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
479       do_which = x;
480     else if (*do_which_str)
481       {
482         char *s;
483         for (s = do_which_str; *s; s++)
484           if (*s == '-' || *s == '_') *s = ' ';
485
486         for (x = 0; x < bp->npolyhedra; x++)
487           if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
488               !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
489               !strcasecmp (do_which_str, bp->polyhedra[x]->config))
490             {
491               do_which = x;
492               break;
493             }
494         if (do_which < 0)
495           {
496             fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
497                      progname, do_which_str);
498             exit (1);
499           }
500       }
501   }
502
503   new_polyhedron (mi);
504 }
505
506
507 ENTRYPOINT void
508 draw_polyhedra (ModeInfo *mi)
509 {
510   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
511   Display *dpy = MI_DISPLAY(mi);
512   Window window = MI_WINDOW(mi);
513
514   static const GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
515   GLfloat bshiny    = 128.0;
516
517   if (!bp->glx_context)
518     return;
519
520   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
521
522   if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
523     ;
524   else if (bp->mode == 0)
525     {
526       if (bp->change_to >= 0)
527         bp->change_tick = 999, bp->last_change_time = 1;
528       if (bp->change_tick++ > 10)
529         {
530           time_t now = time((time_t *) 0);
531           if (bp->last_change_time == 0) bp->last_change_time = now;
532           bp->change_tick = 0;
533           if (!bp->button_down_p && now - bp->last_change_time >= duration)
534             {
535               bp->mode = 1;    /* go out */
536               bp->mode_tick = 20 * speed;
537               bp->last_change_time = now;
538             }
539         }
540     }
541   else if (bp->mode == 1)   /* out */
542     {
543       if (--bp->mode_tick <= 0)
544         {
545           new_polyhedron (mi);
546           bp->mode_tick = 20 * speed;
547           bp->mode = 2;  /* go in */
548         }
549     }
550   else if (bp->mode == 2)   /* in */
551     {
552       if (--bp->mode_tick <= 0)
553         bp->mode = 0;  /* normal */
554     }
555   else
556     abort();
557
558   glShadeModel(GL_FLAT);
559   glEnable(GL_NORMALIZE);
560
561   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
562
563   glPushMatrix ();
564
565   glScalef(1.1, 1.1, 1.1);
566
567   {
568     double x, y, z;
569     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
570     glTranslatef((x - 0.5) * 8,
571                  (y - 0.5) * 8,
572                  (z - 0.5) * 15);
573
574     gltrackball_rotate (bp->trackball);
575
576     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
577     glRotatef (x * 360, 1.0, 0.0, 0.0);
578     glRotatef (y * 360, 0.0, 1.0, 0.0);
579     glRotatef (z * 360, 0.0, 0.0, 1.0);
580   }
581
582   glScalef (2.0, 2.0, 2.0);
583
584   glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  bspec);
585   glMateriali  (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
586
587   if (bp->mode != 0)
588     {
589       GLfloat s = (bp->mode == 1
590                    ? bp->mode_tick / (20 * speed)
591                    : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
592       glScalef (s, s, s);
593     }
594
595   glScalef (2, 2, 2);
596   glCallList (bp->object_list);
597   if (bp->mode == 0 && !bp->button_down_p)
598     glCallList (bp->title_list);
599
600   glPopMatrix ();
601
602   if (mi->fps_p) do_fps (mi);
603   glFinish();
604
605   glXSwapBuffers(dpy, window);
606 }
607
608 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)
609
610 #endif /* USE_GL */