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>
70 #ifdef USE_GL /* whole file */
73 GLXContext *glx_context;
75 trackball_state *trackball;
79 polyhedron **polyhedra;
86 int mode; /* 0 = normal, 1 = out, 2 = in */
93 XFontStruct *xfont1, *xfont2, *xfont3;
94 GLuint font1_dlist, font2_dlist, font3_dlist;
96 texture_font_data *font1_data, *font2_data, *font3_data;
99 time_t last_change_time;
102 } polyhedra_configuration;
104 static polyhedra_configuration *bps = NULL;
107 static GLfloat speed;
108 static Bool do_wander;
109 static Bool do_titles;
112 static char *do_which_str;
114 static XrmOptionDescRec opts[] = {
115 { "-spin", ".spin", XrmoptionNoArg, "True" },
116 { "+spin", ".spin", XrmoptionNoArg, "False" },
117 { "-speed", ".speed", XrmoptionSepArg, 0 },
118 { "-wander", ".wander", XrmoptionNoArg, "True" },
119 { "+wander", ".wander", XrmoptionNoArg, "False" },
120 { "-titles", ".titles", XrmoptionNoArg, "True" },
121 { "+titles", ".titles", XrmoptionNoArg, "False" },
122 { "-duration",".duration",XrmoptionSepArg, 0 },
123 { "-which", ".which", XrmoptionSepArg, 0 },
126 static argtype vars[] = {
127 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
128 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
129 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
130 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
131 {&duration,"duration","Duration",DEF_DURATION,t_Int},
132 {&do_which_str,"which", "Which", DEF_WHICH, t_String},
135 ENTRYPOINT ModeSpecOpt polyhedra_opts = {countof(opts), opts, countof(vars), vars, NULL};
139 /* Calculate the normals at each vertex of a face, and use the sum to
140 decide which normal to assign to the entire face. This also solves
141 problems caused by nonconvex faces, in most (but not all) cases.
144 kludge_normal (int n, const int *indices, const point *points)
146 XYZ normal = { 0, 0, 0 };
150 for (i = 0; i < n; ++i) {
152 int i2 = indices[(i + 1) % n];
153 int i3 = indices[(i + 2) % n];
156 p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
157 p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
158 p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
160 p = calc_normal (p1, p2, p3);
166 /*normalize(&normal);*/
167 if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
168 glNormal3f (p.x, p.y, p.z);
170 glNormal3f (normal.x, normal.y, normal.z);
176 load_fonts (ModeInfo *mi)
178 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
179 # ifdef HAVE_GLBITMAP
180 load_font (mi->dpy, "titleFont", &bp->xfont1, &bp->font1_dlist);
181 load_font (mi->dpy, "titleFont2", &bp->xfont2, &bp->font2_dlist);
182 load_font (mi->dpy, "titleFont3", &bp->xfont3, &bp->font3_dlist);
183 # else /* !HAVE_GLBITMAP */
184 bp->font1_data = load_texture_font (mi->dpy, "titleFont");
185 bp->font2_data = load_texture_font (mi->dpy, "titleFont2");
186 bp->font3_data = load_texture_font (mi->dpy, "titleFont3");
187 # endif /* !HAVE_GLBITMAP */
193 startup_blurb (ModeInfo *mi)
195 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
196 const char *s = "Computing polyhedra...";
197 # ifdef HAVE_GLBITMAP
198 XFontStruct *f = bp->xfont1;
199 # else /* !HAVE_GLBITMAP */
200 texture_font_data *f = bp->font1_data;
201 # endif /* !HAVE_GLBITMAP */
203 glColor3f (0.8, 0.8, 0);
204 print_gl_string (mi->dpy,
205 # ifdef HAVE_GLBITMAP
206 bp->xfont1, bp->font1_dlist,
207 # else /* !HAVE_GLBITMAP */
209 # endif /* !HAVE_GLBITMAP */
210 mi->xgwa.width, mi->xgwa.height,
212 # ifdef HAVE_GLBITMAP
213 string_width (f, s, 0)
214 # else /* !HAVE_GLBITMAP */
215 texture_string_width (f, s, 0)
216 # endif /* !HAVE_GLBITMAP */
218 mi->xgwa.height - 10,
221 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
226 /* Window management, etc
228 static void new_label (ModeInfo *mi);
231 reshape_polyhedra (ModeInfo *mi, int width, int height)
233 GLfloat h = (GLfloat) height / (GLfloat) width;
235 glViewport (0, 0, (GLint) width, (GLint) height);
237 glMatrixMode(GL_PROJECTION);
239 gluPerspective (30.0, 1/h, 1.0, 100.0);
241 glMatrixMode(GL_MODELVIEW);
243 gluLookAt( 0.0, 0.0, 30.0,
247 glClear(GL_COLOR_BUFFER_BIT);
249 /* need to re-render the text when the window size changes */
255 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
257 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
259 if (event->xany.type == ButtonPress &&
260 event->xbutton.button == Button1)
262 bp->button_down_p = True;
263 gltrackball_start (bp->trackball,
264 event->xbutton.x, event->xbutton.y,
265 MI_WIDTH (mi), MI_HEIGHT (mi));
268 else if (event->xany.type == ButtonRelease &&
269 event->xbutton.button == Button1)
271 bp->button_down_p = False;
274 else if (event->xany.type == ButtonPress &&
275 (event->xbutton.button == Button4 ||
276 event->xbutton.button == Button5 ||
277 event->xbutton.button == Button6 ||
278 event->xbutton.button == Button7))
280 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
281 !!event->xbutton.state);
284 else if (event->xany.type == KeyPress)
288 XLookupString (&event->xkey, &c, 1, &keysym, 0);
291 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
292 bp->change_to = random() % bp->npolyhedra;
293 else if (c == '>' || c == '.' || c == '+' || c == '=' ||
294 keysym == XK_Right || keysym == XK_Up)
295 bp->change_to = (bp->which + 1) % bp->npolyhedra;
296 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
297 c == '\010' || c == '\177' ||
298 keysym == XK_Left || keysym == XK_Down)
299 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
301 if (bp->change_to != -1)
304 else if (event->xany.type == MotionNotify &&
307 gltrackball_track (bp->trackball,
308 event->xmotion.x, event->xmotion.y,
309 MI_WIDTH (mi), MI_HEIGHT (mi));
318 new_label (ModeInfo *mi)
320 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
321 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
323 glNewList (bp->title_list, GL_COMPILE);
328 strcpy (name2, p->name);
330 sprintf (name2 + strlen(name2), " (%s)", p->class);
333 "Polyhedron %d: \t%s\n\n"
334 "Wythoff Symbol:\t%s\n"
335 "Vertex Configuration:\t%s\n"
336 "Symmetry Group:\t%s\n"
337 /* "Dual of: \t%s\n" */
344 bp->which, name2, p->wythoff, p->config, p->group,
346 p->logical_faces, p->nedges, p->logical_vertices,
347 p->density, (p->chi < 0 ? "" : " "), p->chi);
350 # ifdef HAVE_GLBITMAP
353 # else /* !HAVE_GLBITMAP */
354 texture_font_data *f;
355 # endif /* !HAVE_GLBITMAP */
356 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
357 # ifdef HAVE_GLBITMAP
358 f = bp->xfont1, fl = bp->font1_dlist; /* big font */
359 # else /* !HAVE_GLBITMAP */
361 # endif /* !HAVE_GLBITMAP */
362 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
363 # ifdef HAVE_GLBITMAP
364 f = bp->xfont2, fl = bp->font2_dlist; /* small font */
365 # else /* !HAVE_GLBITMAP */
366 f = bp->font2_data; /* small font */
367 # endif /* !HAVE_GLBITMAP */
369 # ifdef HAVE_GLBITMAP
370 f = bp->xfont3, fl = bp->font3_dlist; /* tiny font */
371 # else /* !HAVE_GLBITMAP */
372 f = bp->font3_data; /* tiny font */
373 # endif /* !HAVE_GLBITMAP */
375 glColor3f (0.8, 0.8, 0);
376 print_gl_string (mi->dpy, f,
377 # ifdef HAVE_GLBITMAP
379 # endif /* HAVE_GLBITMAP */
380 mi->xgwa.width, mi->xgwa.height,
381 10, mi->xgwa.height - 10,
391 tess_error (GLenum errorCode)
393 fprintf (stderr, "%s: tesselation error: %s\n",
394 progname, gluErrorString(errorCode));
397 #endif /* HAVE_TESS */
401 new_polyhedron (ModeInfo *mi)
403 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
405 int wire = MI_IS_WIREFRAME(mi);
408 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
409 correctly (e.g., for the "pentagrammic concave deltohedron").
412 GLUtesselator *tobj = gluNewTess();
413 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
414 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
415 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
416 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
417 # endif /* HAVE_TESS */
419 mi->polygon_count = 0;
422 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
423 make_random_colormap (0, 0, 0,
424 bp->colors, &bp->ncolors,
425 True, False, 0, False);
427 if (do_which >= bp->npolyhedra)
430 bp->which = (bp->change_to != -1 ? bp->change_to :
431 do_which >= 0 ? do_which :
432 (random() % bp->npolyhedra));
434 p = bp->polyhedra[bp->which];
439 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
441 glNewList (bp->object_list, GL_COMPILE);
442 if (bp->which == bp->npolyhedra-1)
445 bcolor[0] = bp->colors[0].red / 65536.0;
446 bcolor[1] = bp->colors[0].green / 65536.0;
447 bcolor[2] = bp->colors[0].blue / 65536.0;
452 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
454 glScalef (0.8, 0.8, 0.8);
455 p->nfaces = unit_teapot (6, wire);
456 p->nedges = p->nfaces * 3 / 2;
457 p->npoints = p->nfaces * 3;
458 p->logical_faces = p->nfaces;
459 p->logical_vertices = p->npoints;
463 glFrontFace (GL_CCW);
464 for (i = 0; i < p->nfaces; i++)
467 face *f = &p->faces[i];
469 if (f->color > 64 || f->color < 0) abort();
475 bcolor[0] = bp->colors[f->color].red / 65536.0;
476 bcolor[1] = bp->colors[f->color].green / 65536.0;
477 bcolor[2] = bp->colors[f->color].blue / 65536.0;
479 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
482 kludge_normal (f->npoints, f->points, p->points);
485 gluTessBeginPolygon (tobj, 0);
486 gluTessBeginContour (tobj);
487 for (j = 0; j < f->npoints; j++)
489 point *pp = &p->points[f->points[j]];
490 gluTessVertex (tobj, &pp->x, &pp->x);
492 gluTessEndContour (tobj);
493 gluTessEndPolygon (tobj);
494 # else /* !HAVE_TESS */
495 glBegin (wire ? GL_LINE_LOOP :
496 f->npoints == 3 ? GL_TRIANGLES :
497 f->npoints == 4 ? GL_QUADS :
499 for (j = 0; j < f->npoints; j++)
501 point *pp = &p->points[f->points[j]];
502 glVertex3f (pp->x, pp->y, pp->z);
505 # endif /* !HAVE_TESS */
510 mi->polygon_count += p->nfaces;
512 gluDeleteTess (tobj);
518 construct_teapot (ModeInfo *mi)
520 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
521 int n = bp->npolyhedra-1;
522 polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
524 p->wythoff = strdup("X00398|1984");
525 p->name = strdup("Teapot");
526 p->dual = strdup("");
527 p->config = strdup("Melitta");
528 p->group = strdup("Teapotahedral (Newell[1975])");
529 p->class = strdup("Utah Teapotahedron");
530 bp->polyhedra[n] = p;
535 init_polyhedra (ModeInfo *mi)
537 polyhedra_configuration *bp;
538 int wire = MI_IS_WIREFRAME(mi);
541 bps = (polyhedra_configuration *)
542 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
544 fprintf(stderr, "%s: out of memory\n", progname);
549 bp = &bps[MI_SCREEN(mi)];
551 bp->glx_context = init_GL(mi);
559 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
560 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
561 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
562 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
564 glEnable(GL_LIGHTING);
566 glEnable(GL_DEPTH_TEST);
567 /* glEnable(GL_CULL_FACE); */
569 /* We need two-sided lighting for polyhedra where both sides of
570 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
572 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
574 glLightfv(GL_LIGHT0, GL_POSITION, pos);
575 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
576 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
577 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
581 double spin_speed = 2.0;
582 double wander_speed = 0.05;
583 double spin_accel = 0.2;
585 bp->rot = make_rotator (do_spin ? spin_speed : 0,
586 do_spin ? spin_speed : 0,
587 do_spin ? spin_speed : 0,
589 do_wander ? wander_speed : 0,
591 bp->trackball = gltrackball_init ();
594 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
595 construct_teapot (mi);
597 bp->object_list = glGenLists (1);
598 bp->title_list = glGenLists (1);
605 if (!strcasecmp (do_which_str, "random"))
607 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
609 if (x >= 0 && x < bp->npolyhedra)
613 "%s: polyhedron %d does not exist: there are only %d.\n",
614 progname, x, bp->npolyhedra-1);
616 else if (*do_which_str)
619 for (s = do_which_str; *s; s++)
620 if (*s == '-' || *s == '_') *s = ' ';
622 for (x = 0; x < bp->npolyhedra; x++)
623 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
624 !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
625 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
626 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
633 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
634 progname, do_which_str);
641 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
642 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
648 draw_polyhedra (ModeInfo *mi)
650 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
651 Display *dpy = MI_DISPLAY(mi);
652 Window window = MI_WINDOW(mi);
654 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
655 GLfloat bshiny = 128.0;
657 if (!bp->glx_context)
660 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
662 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
664 else if (bp->mode == 0)
666 if (bp->change_to >= 0)
667 bp->change_tick = 999, bp->last_change_time = 1;
668 if (bp->change_tick++ > 10)
670 time_t now = time((time_t *) 0);
671 if (bp->last_change_time == 0) bp->last_change_time = now;
673 if (!bp->button_down_p && now - bp->last_change_time >= duration)
675 bp->mode = 1; /* go out */
676 bp->mode_tick = 20 * speed;
677 bp->last_change_time = now;
681 else if (bp->mode == 1) /* out */
683 if (--bp->mode_tick <= 0)
686 bp->mode_tick = 20 * speed;
687 bp->mode = 2; /* go in */
690 else if (bp->mode == 2) /* in */
692 if (--bp->mode_tick <= 0)
693 bp->mode = 0; /* normal */
698 glShadeModel(GL_FLAT);
699 glEnable(GL_NORMALIZE);
701 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
705 glScalef(1.1, 1.1, 1.1);
709 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
710 glTranslatef((x - 0.5) * 8,
714 /* Do it twice because we don't track the device's orientation. */
715 glRotatef( current_device_rotation(), 0, 0, 1);
716 gltrackball_rotate (bp->trackball);
717 glRotatef(-current_device_rotation(), 0, 0, 1);
719 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
720 glRotatef (x * 360, 1.0, 0.0, 0.0);
721 glRotatef (y * 360, 0.0, 1.0, 0.0);
722 glRotatef (z * 360, 0.0, 0.0, 1.0);
725 glScalef (2.0, 2.0, 2.0);
727 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
728 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
732 GLfloat s = (bp->mode == 1
733 ? bp->mode_tick / (20 * speed)
734 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
739 glCallList (bp->object_list);
740 if (bp->mode == 0 && !bp->button_down_p)
741 glCallList (bp->title_list);
745 if (mi->fps_p) do_fps (mi);
748 glXSwapBuffers(dpy, window);
751 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)