1 /* polyhedra, Copyright (c) 2004-2011 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);
463 bp = &bps[MI_SCREEN(mi)];
465 bp->glx_context = init_GL(mi);
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};
478 glEnable(GL_LIGHTING);
480 glEnable(GL_DEPTH_TEST);
481 /* glEnable(GL_CULL_FACE); */
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".)
486 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
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);
495 double spin_speed = 2.0;
496 double wander_speed = 0.05;
497 double spin_accel = 0.2;
499 bp->rot = make_rotator (do_spin ? spin_speed : 0,
500 do_spin ? spin_speed : 0,
501 do_spin ? spin_speed : 0,
503 do_wander ? wander_speed : 0,
505 bp->trackball = gltrackball_init ();
508 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
509 construct_teapot (mi);
511 bp->object_list = glGenLists (1);
512 bp->title_list = glGenLists (1);
519 if (!strcasecmp (do_which_str, "random"))
521 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
523 if (x >= 0 && x < bp->npolyhedra)
527 "%s: polyhedron %d does not exist: there are only %d.\n",
528 progname, x, bp->npolyhedra-1);
530 else if (*do_which_str)
533 for (s = do_which_str; *s; s++)
534 if (*s == '-' || *s == '_') *s = ' ';
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))
547 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
548 progname, do_which_str);
555 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
556 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
562 draw_polyhedra (ModeInfo *mi)
564 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
565 Display *dpy = MI_DISPLAY(mi);
566 Window window = MI_WINDOW(mi);
568 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
569 GLfloat bshiny = 128.0;
571 if (!bp->glx_context)
574 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
576 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
578 else if (bp->mode == 0)
580 if (bp->change_to >= 0)
581 bp->change_tick = 999, bp->last_change_time = 1;
582 if (bp->change_tick++ > 10)
584 time_t now = time((time_t *) 0);
585 if (bp->last_change_time == 0) bp->last_change_time = now;
587 if (!bp->button_down_p && now - bp->last_change_time >= duration)
589 bp->mode = 1; /* go out */
590 bp->mode_tick = 20 * speed;
591 bp->last_change_time = now;
595 else if (bp->mode == 1) /* out */
597 if (--bp->mode_tick <= 0)
600 bp->mode_tick = 20 * speed;
601 bp->mode = 2; /* go in */
604 else if (bp->mode == 2) /* in */
606 if (--bp->mode_tick <= 0)
607 bp->mode = 0; /* normal */
612 glShadeModel(GL_FLAT);
613 glEnable(GL_NORMALIZE);
615 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
619 glScalef(1.1, 1.1, 1.1);
623 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
624 glTranslatef((x - 0.5) * 8,
628 gltrackball_rotate (bp->trackball);
630 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
631 glRotatef (x * 360, 1.0, 0.0, 0.0);
632 glRotatef (y * 360, 0.0, 1.0, 0.0);
633 glRotatef (z * 360, 0.0, 0.0, 1.0);
636 glScalef (2.0, 2.0, 2.0);
638 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
639 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
643 GLfloat s = (bp->mode == 1
644 ? bp->mode_tick / (20 * speed)
645 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
650 glCallList (bp->object_list);
651 if (bp->mode == 0 && !bp->button_down_p)
652 glCallList (bp->title_list);
656 if (mi->fps_p) do_fps (mi);
659 glXSwapBuffers(dpy, window);
662 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)