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);
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));
561 draw_polyhedra (ModeInfo *mi)
563 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
564 Display *dpy = MI_DISPLAY(mi);
565 Window window = MI_WINDOW(mi);
567 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
568 GLfloat bshiny = 128.0;
570 if (!bp->glx_context)
573 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
575 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
577 else if (bp->mode == 0)
579 if (bp->change_to >= 0)
580 bp->change_tick = 999, bp->last_change_time = 1;
581 if (bp->change_tick++ > 10)
583 time_t now = time((time_t *) 0);
584 if (bp->last_change_time == 0) bp->last_change_time = now;
586 if (!bp->button_down_p && now - bp->last_change_time >= duration)
588 bp->mode = 1; /* go out */
589 bp->mode_tick = 20 * speed;
590 bp->last_change_time = now;
594 else if (bp->mode == 1) /* out */
596 if (--bp->mode_tick <= 0)
599 bp->mode_tick = 20 * speed;
600 bp->mode = 2; /* go in */
603 else if (bp->mode == 2) /* in */
605 if (--bp->mode_tick <= 0)
606 bp->mode = 0; /* normal */
611 glShadeModel(GL_FLAT);
612 glEnable(GL_NORMALIZE);
614 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
618 glScalef(1.1, 1.1, 1.1);
622 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
623 glTranslatef((x - 0.5) * 8,
627 gltrackball_rotate (bp->trackball);
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);
635 glScalef (2.0, 2.0, 2.0);
637 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
638 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
642 GLfloat s = (bp->mode == 1
643 ? bp->mode_tick / (20 * speed)
644 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
649 glCallList (bp->object_list);
650 if (bp->mode == 0 && !bp->button_down_p)
651 glCallList (bp->title_list);
655 if (mi->fps_p) do_fps (mi);
658 glXSwapBuffers(dpy, window);
661 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)