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