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