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"
36 # include <X11/Xlib.h>
43 #endif /* HAVE_JWZGLES */
45 #define DEF_SPIN "True"
46 #define DEF_WANDER "True"
47 #define DEF_SPEED "1.0"
48 #define DEF_TITLES "True"
49 #define DEF_DURATION "12"
50 #define DEF_WHICH "random"
54 #include "polyhedra.h"
57 #include "gltrackball.h"
61 # define XK_MISCELLANY
62 # include <X11/keysymdef.h>
65 #ifdef USE_GL /* whole file */
68 GLXContext *glx_context;
70 trackball_state *trackball;
74 polyhedron **polyhedra;
81 int mode; /* 0 = normal, 1 = out, 2 = in */
88 XFontStruct *xfont1, *xfont2, *xfont3;
89 GLuint font1_dlist, font2_dlist, font3_dlist;
91 texture_font_data *font1_data, *font2_data, *font3_data;
94 time_t last_change_time;
97 } polyhedra_configuration;
99 static polyhedra_configuration *bps = NULL;
102 static GLfloat speed;
103 static Bool do_wander;
104 static Bool do_titles;
107 static char *do_which_str;
109 static XrmOptionDescRec opts[] = {
110 { "-spin", ".spin", XrmoptionNoArg, "True" },
111 { "+spin", ".spin", XrmoptionNoArg, "False" },
112 { "-speed", ".speed", XrmoptionSepArg, 0 },
113 { "-wander", ".wander", XrmoptionNoArg, "True" },
114 { "+wander", ".wander", XrmoptionNoArg, "False" },
115 { "-titles", ".titles", XrmoptionNoArg, "True" },
116 { "+titles", ".titles", XrmoptionNoArg, "False" },
117 { "-duration",".duration",XrmoptionSepArg, 0 },
118 { "-which", ".which", XrmoptionSepArg, 0 },
121 static argtype vars[] = {
122 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
123 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
124 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
125 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
126 {&duration,"duration","Duration",DEF_DURATION,t_Int},
127 {&do_which_str,"which", "Which", DEF_WHICH, t_String},
130 ENTRYPOINT ModeSpecOpt polyhedra_opts = {countof(opts), opts, countof(vars), vars, NULL};
134 /* Calculate the normals at each vertex of a face, and use the sum to
135 decide which normal to assign to the entire face. This also solves
136 problems caused by nonconvex faces, in most (but not all) cases.
139 kludge_normal (int n, const int *indices, const point *points)
141 XYZ normal = { 0, 0, 0 };
145 for (i = 0; i < n; ++i) {
147 int i2 = indices[(i + 1) % n];
148 int i3 = indices[(i + 2) % n];
151 p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
152 p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
153 p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
155 p = calc_normal (p1, p2, p3);
161 /*normalize(&normal);*/
162 if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
163 glNormal3f (p.x, p.y, p.z);
165 glNormal3f (normal.x, normal.y, normal.z);
171 load_fonts (ModeInfo *mi)
173 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
174 # ifdef HAVE_GLBITMAP
175 load_font (mi->dpy, "titleFont", &bp->xfont1, &bp->font1_dlist);
176 load_font (mi->dpy, "titleFont2", &bp->xfont2, &bp->font2_dlist);
177 load_font (mi->dpy, "titleFont3", &bp->xfont3, &bp->font3_dlist);
178 # else /* !HAVE_GLBITMAP */
179 bp->font1_data = load_texture_font (mi->dpy, "titleFont");
180 bp->font2_data = load_texture_font (mi->dpy, "titleFont2");
181 bp->font3_data = load_texture_font (mi->dpy, "titleFont3");
182 # endif /* !HAVE_GLBITMAP */
188 startup_blurb (ModeInfo *mi)
190 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
191 const char *s = "Computing polyhedra...";
192 # ifdef HAVE_GLBITMAP
193 XFontStruct *f = bp->xfont1;
194 # else /* !HAVE_GLBITMAP */
195 texture_font_data *f = bp->font1_data;
196 # endif /* !HAVE_GLBITMAP */
198 glColor3f (0.8, 0.8, 0);
199 print_gl_string (mi->dpy,
200 # ifdef HAVE_GLBITMAP
201 bp->xfont1, bp->font1_dlist,
202 # else /* !HAVE_GLBITMAP */
204 # endif /* !HAVE_GLBITMAP */
205 mi->xgwa.width, mi->xgwa.height,
207 # ifdef HAVE_GLBITMAP
208 string_width (f, s, 0)
209 # else /* !HAVE_GLBITMAP */
210 texture_string_width (f, s, 0)
211 # endif /* !HAVE_GLBITMAP */
213 mi->xgwa.height - 10,
216 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
221 /* Window management, etc
223 static void new_label (ModeInfo *mi);
226 reshape_polyhedra (ModeInfo *mi, int width, int height)
228 GLfloat h = (GLfloat) height / (GLfloat) width;
230 glViewport (0, 0, (GLint) width, (GLint) height);
232 glMatrixMode(GL_PROJECTION);
234 gluPerspective (30.0, 1/h, 1.0, 100.0);
236 glMatrixMode(GL_MODELVIEW);
238 gluLookAt( 0.0, 0.0, 30.0,
242 glClear(GL_COLOR_BUFFER_BIT);
244 /* need to re-render the text when the window size changes */
250 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
252 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
254 if (event->xany.type == ButtonPress &&
255 event->xbutton.button == Button1)
257 bp->button_down_p = True;
258 gltrackball_start (bp->trackball,
259 event->xbutton.x, event->xbutton.y,
260 MI_WIDTH (mi), MI_HEIGHT (mi));
263 else if (event->xany.type == ButtonRelease &&
264 event->xbutton.button == Button1)
266 bp->button_down_p = False;
269 else if (event->xany.type == ButtonPress &&
270 (event->xbutton.button == Button4 ||
271 event->xbutton.button == Button5 ||
272 event->xbutton.button == Button6 ||
273 event->xbutton.button == Button7))
275 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
276 !!event->xbutton.state);
279 else if (event->xany.type == KeyPress)
283 XLookupString (&event->xkey, &c, 1, &keysym, 0);
286 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
287 bp->change_to = random() % bp->npolyhedra;
288 else if (c == '>' || c == '.' || c == '+' || c == '=' ||
289 keysym == XK_Right || keysym == XK_Up)
290 bp->change_to = (bp->which + 1) % bp->npolyhedra;
291 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
292 c == '\010' || c == '\177' ||
293 keysym == XK_Left || keysym == XK_Down)
294 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
296 if (bp->change_to != -1)
299 else if (event->xany.type == MotionNotify &&
302 gltrackball_track (bp->trackball,
303 event->xmotion.x, event->xmotion.y,
304 MI_WIDTH (mi), MI_HEIGHT (mi));
313 new_label (ModeInfo *mi)
315 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
316 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
318 glNewList (bp->title_list, GL_COMPILE);
323 strcpy (name2, p->name);
325 sprintf (name2 + strlen(name2), " (%s)", p->class);
328 "Polyhedron %d: \t%s\n\n"
329 "Wythoff Symbol:\t%s\n"
330 "Vertex Configuration:\t%s\n"
331 "Symmetry Group:\t%s\n"
332 /* "Dual of: \t%s\n" */
339 bp->which, name2, p->wythoff, p->config, p->group,
341 p->logical_faces, p->nedges, p->logical_vertices,
342 p->density, (p->chi < 0 ? "" : " "), p->chi);
345 # ifdef HAVE_GLBITMAP
348 # else /* !HAVE_GLBITMAP */
349 texture_font_data *f;
350 # endif /* !HAVE_GLBITMAP */
351 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
352 # ifdef HAVE_GLBITMAP
353 f = bp->xfont1, fl = bp->font1_dlist; /* big font */
354 # else /* !HAVE_GLBITMAP */
356 # endif /* !HAVE_GLBITMAP */
357 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
358 # ifdef HAVE_GLBITMAP
359 f = bp->xfont2, fl = bp->font2_dlist; /* small font */
360 # else /* !HAVE_GLBITMAP */
361 f = bp->font2_data; /* small font */
362 # endif /* !HAVE_GLBITMAP */
364 # ifdef HAVE_GLBITMAP
365 f = bp->xfont3, fl = bp->font3_dlist; /* tiny font */
366 # else /* !HAVE_GLBITMAP */
367 f = bp->font3_data; /* tiny font */
368 # endif /* !HAVE_GLBITMAP */
370 glColor3f (0.8, 0.8, 0);
371 print_gl_string (mi->dpy, f,
372 # ifdef HAVE_GLBITMAP
374 # endif /* HAVE_GLBITMAP */
375 mi->xgwa.width, mi->xgwa.height,
376 10, mi->xgwa.height - 10,
385 tess_error (GLenum errorCode)
387 fprintf (stderr, "%s: tesselation error: %s\n",
388 progname, gluErrorString(errorCode));
393 new_polyhedron (ModeInfo *mi)
395 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
397 int wire = MI_IS_WIREFRAME(mi);
400 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
401 correctly (e.g., for the "pentagrammic concave deltohedron").
403 GLUtesselator *tobj = gluNewTess();
404 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
405 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
406 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
407 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
409 mi->polygon_count = 0;
412 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
413 make_random_colormap (0, 0, 0,
414 bp->colors, &bp->ncolors,
415 True, False, 0, False);
417 if (do_which >= bp->npolyhedra)
420 bp->which = (bp->change_to != -1 ? bp->change_to :
421 do_which >= 0 ? do_which :
422 (random() % bp->npolyhedra));
424 p = bp->polyhedra[bp->which];
429 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
431 glNewList (bp->object_list, GL_COMPILE);
432 if (bp->which == bp->npolyhedra-1)
434 glScalef (0.8, 0.8, 0.8);
435 p->nfaces = unit_teapot (6, wire);
436 p->nedges = p->nfaces * 2; /* #### is this right? */
437 p->npoints = p->nfaces / 3; /* #### is this right? */
438 p->logical_faces = p->nfaces;
439 p->logical_vertices = p->npoints;
443 glFrontFace (GL_CCW);
444 for (i = 0; i < p->nfaces; i++)
447 face *f = &p->faces[i];
449 if (f->color > 64 || f->color < 0) abort();
455 bcolor[0] = bp->colors[f->color].red / 65536.0;
456 bcolor[1] = bp->colors[f->color].green / 65536.0;
457 bcolor[2] = bp->colors[f->color].blue / 65536.0;
459 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
462 kludge_normal (f->npoints, f->points, p->points);
464 gluTessBeginPolygon (tobj, 0);
465 gluTessBeginContour (tobj);
466 for (j = 0; j < f->npoints; j++)
468 point *pp = &p->points[f->points[j]];
469 gluTessVertex (tobj, &pp->x, &pp->x);
471 gluTessEndContour (tobj);
472 gluTessEndPolygon (tobj);
477 mi->polygon_count += p->nfaces;
478 gluDeleteTess (tobj);
483 construct_teapot (ModeInfo *mi)
485 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
486 int n = bp->npolyhedra-1;
487 polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
489 p->wythoff = strdup("X00398|1984");
490 p->name = strdup("Teapot");
491 p->dual = strdup("");
492 p->config = strdup("Melitta");
493 p->group = strdup("Teapotahedral (Newell[1975])");
494 p->class = strdup("Utah Teapotahedron");
495 bp->polyhedra[n] = p;
500 init_polyhedra (ModeInfo *mi)
502 polyhedra_configuration *bp;
503 int wire = MI_IS_WIREFRAME(mi);
506 bps = (polyhedra_configuration *)
507 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
509 fprintf(stderr, "%s: out of memory\n", progname);
514 bp = &bps[MI_SCREEN(mi)];
516 bp->glx_context = init_GL(mi);
524 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
525 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
526 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
527 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
529 glEnable(GL_LIGHTING);
531 glEnable(GL_DEPTH_TEST);
532 /* glEnable(GL_CULL_FACE); */
534 /* We need two-sided lighting for polyhedra where both sides of
535 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
537 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
539 glLightfv(GL_LIGHT0, GL_POSITION, pos);
540 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
541 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
542 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
546 double spin_speed = 2.0;
547 double wander_speed = 0.05;
548 double spin_accel = 0.2;
550 bp->rot = make_rotator (do_spin ? spin_speed : 0,
551 do_spin ? spin_speed : 0,
552 do_spin ? spin_speed : 0,
554 do_wander ? wander_speed : 0,
556 bp->trackball = gltrackball_init ();
559 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
560 construct_teapot (mi);
562 bp->object_list = glGenLists (1);
563 bp->title_list = glGenLists (1);
570 if (!strcasecmp (do_which_str, "random"))
572 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
574 if (x >= 0 && x < bp->npolyhedra)
578 "%s: polyhedron %d does not exist: there are only %d.\n",
579 progname, x, bp->npolyhedra-1);
581 else if (*do_which_str)
584 for (s = do_which_str; *s; s++)
585 if (*s == '-' || *s == '_') *s = ' ';
587 for (x = 0; x < bp->npolyhedra; x++)
588 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
589 !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
590 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
591 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
598 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
599 progname, do_which_str);
606 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
607 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
613 draw_polyhedra (ModeInfo *mi)
615 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
616 Display *dpy = MI_DISPLAY(mi);
617 Window window = MI_WINDOW(mi);
619 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
620 GLfloat bshiny = 128.0;
622 if (!bp->glx_context)
625 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
627 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
629 else if (bp->mode == 0)
631 if (bp->change_to >= 0)
632 bp->change_tick = 999, bp->last_change_time = 1;
633 if (bp->change_tick++ > 10)
635 time_t now = time((time_t *) 0);
636 if (bp->last_change_time == 0) bp->last_change_time = now;
638 if (!bp->button_down_p && now - bp->last_change_time >= duration)
640 bp->mode = 1; /* go out */
641 bp->mode_tick = 20 * speed;
642 bp->last_change_time = now;
646 else if (bp->mode == 1) /* out */
648 if (--bp->mode_tick <= 0)
651 bp->mode_tick = 20 * speed;
652 bp->mode = 2; /* go in */
655 else if (bp->mode == 2) /* in */
657 if (--bp->mode_tick <= 0)
658 bp->mode = 0; /* normal */
663 glShadeModel(GL_FLAT);
664 glEnable(GL_NORMALIZE);
666 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
670 glScalef(1.1, 1.1, 1.1);
674 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
675 glTranslatef((x - 0.5) * 8,
679 gltrackball_rotate (bp->trackball);
681 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
682 glRotatef (x * 360, 1.0, 0.0, 0.0);
683 glRotatef (y * 360, 0.0, 1.0, 0.0);
684 glRotatef (z * 360, 0.0, 0.0, 1.0);
687 glScalef (2.0, 2.0, 2.0);
689 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
690 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
694 GLfloat s = (bp->mode == 1
695 ? bp->mode_tick / (20 * speed)
696 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
701 glCallList (bp->object_list);
702 if (bp->mode == 0 && !bp->button_down_p)
703 glCallList (bp->title_list);
707 if (mi->fps_p) do_fps (mi);
710 glXSwapBuffers(dpy, window);
713 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)