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