1 /* polyhedra, Copyright (c) 2004-2008 Jamie Zawinski <jwz@jwz.org>
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
11 * Renders 160 different 3D solids, and displays some information about each.
12 * A new solid is chosen every few seconds.
14 * This file contains the OpenGL side; computation of the polyhedra themselves
15 * is in "polyhedra.c".
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" \
26 # define refresh_polyhedra 0
27 # define release_polyhedra 0
29 #define countof(x) (sizeof((x))/sizeof((*x)))
31 #include "xlockmore.h"
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"
43 #include "polyhedra.h"
46 #include "gltrackball.h"
50 # define XK_MISCELLANY
51 # include <X11/keysymdef.h>
54 #ifdef USE_GL /* whole file */
57 GLXContext *glx_context;
59 trackball_state *trackball;
63 polyhedron **polyhedra;
70 int mode; /* 0 = normal, 1 = out, 2 = in */
76 XFontStruct *xfont1, *xfont2, *xfont3;
77 GLuint font1_dlist, font2_dlist, font3_dlist;
79 time_t last_change_time;
82 } polyhedra_configuration;
84 static polyhedra_configuration *bps = NULL;
88 static Bool do_wander;
89 static Bool do_titles;
92 static char *do_which_str;
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 },
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},
115 ENTRYPOINT ModeSpecOpt polyhedra_opts = {countof(opts), opts, countof(vars), vars, NULL};
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.
124 kludge_normal (int n, const int *indices, const point *points)
126 XYZ normal = { 0, 0, 0 };
130 for (i = 0; i < n; ++i) {
132 int i2 = indices[(i + 1) % n];
133 int i3 = indices[(i + 2) % n];
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;
140 p = calc_normal (p1, p2, p3);
146 /*normalize(&normal);*/
147 if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
148 glNormal3f (p.x, p.y, p.z);
150 glNormal3f (normal.x, normal.y, normal.z);
156 load_fonts (ModeInfo *mi)
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);
167 startup_blurb (ModeInfo *mi)
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,
178 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
183 /* Window management, etc
185 static void new_label (ModeInfo *mi);
188 reshape_polyhedra (ModeInfo *mi, int width, int height)
190 GLfloat h = (GLfloat) height / (GLfloat) width;
192 glViewport (0, 0, (GLint) width, (GLint) height);
194 glMatrixMode(GL_PROJECTION);
196 gluPerspective (30.0, 1/h, 1.0, 100.0);
198 glMatrixMode(GL_MODELVIEW);
200 gluLookAt( 0.0, 0.0, 30.0,
204 glClear(GL_COLOR_BUFFER_BIT);
206 /* need to re-render the text when the window size changes */
212 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
214 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
216 if (event->xany.type == ButtonPress &&
217 event->xbutton.button == Button1)
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));
225 else if (event->xany.type == ButtonRelease &&
226 event->xbutton.button == Button1)
228 bp->button_down_p = False;
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))
237 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
238 !!event->xbutton.state);
241 else if (event->xany.type == KeyPress)
245 XLookupString (&event->xkey, &c, 1, &keysym, 0);
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;
264 if (bp->change_to != -1)
267 else if (event->xany.type == MotionNotify &&
270 gltrackball_track (bp->trackball,
271 event->xmotion.x, event->xmotion.y,
272 MI_WIDTH (mi), MI_HEIGHT (mi));
281 new_label (ModeInfo *mi)
283 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
284 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
286 glNewList (bp->title_list, GL_COMPILE);
291 strcpy (name2, p->name);
293 sprintf (name2 + strlen(name2), " (%s)", p->class);
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" */
307 bp->which, name2, p->wythoff, p->config, p->group,
309 p->logical_faces, p->nedges, p->logical_vertices,
310 p->density, (p->chi < 0 ? "" : " "), p->chi);
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 */
320 f = bp->xfont3, fl = bp->font3_dlist; /* tiny font */
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,
334 tess_error (GLenum errorCode)
336 fprintf (stderr, "%s: tesselation error: %s\n",
337 progname, gluErrorString(errorCode));
342 new_polyhedron (ModeInfo *mi)
344 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
346 int wire = MI_IS_WIREFRAME(mi);
349 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
350 correctly (e.g., for the "pentagrammic concave deltohedron").
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);
358 mi->polygon_count = 0;
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);
366 if (do_which >= bp->npolyhedra)
369 bp->which = (bp->change_to != -1 ? bp->change_to :
370 do_which >= 0 ? do_which :
371 (random() % bp->npolyhedra));
373 p = bp->polyhedra[bp->which];
378 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
380 glNewList (bp->object_list, GL_COMPILE);
381 if (bp->which == bp->npolyhedra-1)
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;
392 glFrontFace (GL_CCW);
393 for (i = 0; i < p->nfaces; i++)
396 face *f = &p->faces[i];
398 if (f->color > 64 || f->color < 0) abort();
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;
408 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
411 kludge_normal (f->npoints, f->points, p->points);
413 gluTessBeginPolygon (tobj, 0);
414 gluTessBeginContour (tobj);
415 for (j = 0; j < f->npoints; j++)
417 point *pp = &p->points[f->points[j]];
418 gluTessVertex (tobj, &pp->x, &pp->x);
420 gluTessEndContour (tobj);
421 gluTessEndPolygon (tobj);
426 mi->polygon_count += p->nfaces;
427 gluDeleteTess (tobj);
432 construct_teapot (ModeInfo *mi)
434 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
435 int n = bp->npolyhedra-1;
436 polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
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;
449 init_polyhedra (ModeInfo *mi)
451 polyhedra_configuration *bp;
452 int wire = MI_IS_WIREFRAME(mi);
455 bps = (polyhedra_configuration *)
456 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
458 fprintf(stderr, "%s: out of memory\n", progname);
462 bp = &bps[MI_SCREEN(mi)];
465 bp = &bps[MI_SCREEN(mi)];
467 bp->glx_context = init_GL(mi);
473 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
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};
482 glEnable(GL_LIGHTING);
484 glEnable(GL_DEPTH_TEST);
485 /* glEnable(GL_CULL_FACE); */
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".)
490 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
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);
499 double spin_speed = 2.0;
500 double wander_speed = 0.05;
501 double spin_accel = 0.2;
503 bp->rot = make_rotator (do_spin ? spin_speed : 0,
504 do_spin ? spin_speed : 0,
505 do_spin ? spin_speed : 0,
507 do_wander ? wander_speed : 0,
509 bp->trackball = gltrackball_init ();
512 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
513 construct_teapot (mi);
515 bp->object_list = glGenLists (1);
516 bp->title_list = glGenLists (1);
523 if (!strcasecmp (do_which_str, "random"))
525 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
527 if (x >= 0 && x < bp->npolyhedra)
531 "%s: polyhedron %d does not exist: there are only %d.\n",
532 progname, x, bp->npolyhedra-1);
534 else if (*do_which_str)
537 for (s = do_which_str; *s; s++)
538 if (*s == '-' || *s == '_') *s = ' ';
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))
551 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
552 progname, do_which_str);
563 draw_polyhedra (ModeInfo *mi)
565 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
566 Display *dpy = MI_DISPLAY(mi);
567 Window window = MI_WINDOW(mi);
569 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
570 GLfloat bshiny = 128.0;
572 if (!bp->glx_context)
575 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
577 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
579 else if (bp->mode == 0)
581 if (bp->change_to >= 0)
582 bp->change_tick = 999, bp->last_change_time = 1;
583 if (bp->change_tick++ > 10)
585 time_t now = time((time_t *) 0);
586 if (bp->last_change_time == 0) bp->last_change_time = now;
588 if (!bp->button_down_p && now - bp->last_change_time >= duration)
590 bp->mode = 1; /* go out */
591 bp->mode_tick = 20 * speed;
592 bp->last_change_time = now;
596 else if (bp->mode == 1) /* out */
598 if (--bp->mode_tick <= 0)
601 bp->mode_tick = 20 * speed;
602 bp->mode = 2; /* go in */
605 else if (bp->mode == 2) /* in */
607 if (--bp->mode_tick <= 0)
608 bp->mode = 0; /* normal */
613 glShadeModel(GL_FLAT);
614 glEnable(GL_NORMALIZE);
616 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
620 glScalef(1.1, 1.1, 1.1);
624 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
625 glTranslatef((x - 0.5) * 8,
629 gltrackball_rotate (bp->trackball);
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);
637 glScalef (2.0, 2.0, 2.0);
639 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
640 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
644 GLfloat s = (bp->mode == 1
645 ? bp->mode_tick / (20 * speed)
646 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
651 glCallList (bp->object_list);
652 if (bp->mode == 0 && !bp->button_down_p)
653 glCallList (bp->title_list);
657 if (mi->fps_p) do_fps (mi);
660 glXSwapBuffers(dpy, window);
663 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)