fe3205fff41311e088b165a249bc0df4818c8674
[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
463   bp = &bps[MI_SCREEN(mi)];
464
465   bp->glx_context = init_GL(mi);
466
467   bp->which = -1;
468   load_fonts (mi);
469   startup_blurb (mi);
470
471   if (!wire)
472     {
473       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
474       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
475       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
476       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
477
478       glEnable(GL_LIGHTING);
479       glEnable(GL_LIGHT0);
480       glEnable(GL_DEPTH_TEST);
481       /* glEnable(GL_CULL_FACE); */
482
483       /* We need two-sided lighting for polyhedra where both sides of
484          a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
485        */
486       glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
487
488       glLightfv(GL_LIGHT0, GL_POSITION, pos);
489       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
490       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
491       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
492     }
493
494   {
495     double spin_speed   = 2.0;
496     double wander_speed = 0.05;
497     double spin_accel   = 0.2;
498
499     bp->rot = make_rotator (do_spin ? spin_speed : 0,
500                             do_spin ? spin_speed : 0,
501                             do_spin ? spin_speed : 0,
502                             spin_accel,
503                             do_wander ? wander_speed : 0,
504                             True);
505     bp->trackball = gltrackball_init ();
506   }
507
508   bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
509   construct_teapot (mi);
510
511   bp->object_list = glGenLists (1);
512   bp->title_list  = glGenLists (1);
513   bp->change_to = -1;
514
515   {
516     int x;
517     char c;
518     do_which = -1;
519     if (!strcasecmp (do_which_str, "random"))
520       ;
521     else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
522       {
523         if (x >= 0 && x < bp->npolyhedra) 
524           do_which = x;
525         else
526           fprintf (stderr, 
527                    "%s: polyhedron %d does not exist: there are only %d.\n",
528                    progname, x, bp->npolyhedra-1);
529       }
530     else if (*do_which_str)
531       {
532         char *s;
533         for (s = do_which_str; *s; s++)
534           if (*s == '-' || *s == '_') *s = ' ';
535
536         for (x = 0; x < bp->npolyhedra; x++)
537           if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
538               !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
539               !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
540               !strcasecmp (do_which_str, bp->polyhedra[x]->config))
541             {
542               do_which = x;
543               break;
544             }
545         if (do_which < 0)
546           {
547             fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
548                      progname, do_which_str);
549             exit (1);
550           }
551       }
552   }
553
554   new_polyhedron (mi);
555   reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
556
557 }
558
559
560 ENTRYPOINT void
561 draw_polyhedra (ModeInfo *mi)
562 {
563   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
564   Display *dpy = MI_DISPLAY(mi);
565   Window window = MI_WINDOW(mi);
566
567   static const GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
568   GLfloat bshiny    = 128.0;
569
570   if (!bp->glx_context)
571     return;
572
573   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
574
575   if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
576     ;
577   else if (bp->mode == 0)
578     {
579       if (bp->change_to >= 0)
580         bp->change_tick = 999, bp->last_change_time = 1;
581       if (bp->change_tick++ > 10)
582         {
583           time_t now = time((time_t *) 0);
584           if (bp->last_change_time == 0) bp->last_change_time = now;
585           bp->change_tick = 0;
586           if (!bp->button_down_p && now - bp->last_change_time >= duration)
587             {
588               bp->mode = 1;    /* go out */
589               bp->mode_tick = 20 * speed;
590               bp->last_change_time = now;
591             }
592         }
593     }
594   else if (bp->mode == 1)   /* out */
595     {
596       if (--bp->mode_tick <= 0)
597         {
598           new_polyhedron (mi);
599           bp->mode_tick = 20 * speed;
600           bp->mode = 2;  /* go in */
601         }
602     }
603   else if (bp->mode == 2)   /* in */
604     {
605       if (--bp->mode_tick <= 0)
606         bp->mode = 0;  /* normal */
607     }
608   else
609     abort();
610
611   glShadeModel(GL_FLAT);
612   glEnable(GL_NORMALIZE);
613
614   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
615
616   glPushMatrix ();
617
618   glScalef(1.1, 1.1, 1.1);
619
620   {
621     double x, y, z;
622     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
623     glTranslatef((x - 0.5) * 8,
624                  (y - 0.5) * 8,
625                  (z - 0.5) * 15);
626
627     gltrackball_rotate (bp->trackball);
628
629     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
630     glRotatef (x * 360, 1.0, 0.0, 0.0);
631     glRotatef (y * 360, 0.0, 1.0, 0.0);
632     glRotatef (z * 360, 0.0, 0.0, 1.0);
633   }
634
635   glScalef (2.0, 2.0, 2.0);
636
637   glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  bspec);
638   glMateriali  (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
639
640   if (bp->mode != 0)
641     {
642       GLfloat s = (bp->mode == 1
643                    ? bp->mode_tick / (20 * speed)
644                    : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
645       glScalef (s, s, s);
646     }
647
648   glScalef (2, 2, 2);
649   glCallList (bp->object_list);
650   if (bp->mode == 0 && !bp->button_down_p)
651     glCallList (bp->title_list);
652
653   glPopMatrix ();
654
655   if (mi->fps_p) do_fps (mi);
656   glFinish();
657
658   glXSwapBuffers(dpy, window);
659 }
660
661 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)
662
663 #endif /* USE_GL */