1 /* polyhedra, Copyright (c) 2004-2012 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);
540 # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
541 MI_IS_WIREFRAME(mi) = 0;
546 bps = (polyhedra_configuration *)
547 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
549 fprintf(stderr, "%s: out of memory\n", progname);
554 bp = &bps[MI_SCREEN(mi)];
556 bp->glx_context = init_GL(mi);
564 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
565 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
566 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
567 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
569 glEnable(GL_LIGHTING);
571 glEnable(GL_DEPTH_TEST);
572 /* glEnable(GL_CULL_FACE); */
574 /* We need two-sided lighting for polyhedra where both sides of
575 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
577 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
579 glLightfv(GL_LIGHT0, GL_POSITION, pos);
580 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
581 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
582 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
586 double spin_speed = 2.0;
587 double wander_speed = 0.05;
588 double spin_accel = 0.2;
590 bp->rot = make_rotator (do_spin ? spin_speed : 0,
591 do_spin ? spin_speed : 0,
592 do_spin ? spin_speed : 0,
594 do_wander ? wander_speed : 0,
596 bp->trackball = gltrackball_init ();
599 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
600 construct_teapot (mi);
602 bp->object_list = glGenLists (1);
603 bp->title_list = glGenLists (1);
610 if (!strcasecmp (do_which_str, "random"))
612 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
614 if (x >= 0 && x < bp->npolyhedra)
618 "%s: polyhedron %d does not exist: there are only %d.\n",
619 progname, x, bp->npolyhedra-1);
621 else if (*do_which_str)
624 for (s = do_which_str; *s; s++)
625 if (*s == '-' || *s == '_') *s = ' ';
627 for (x = 0; x < bp->npolyhedra; x++)
628 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
629 !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
630 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
631 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
638 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
639 progname, do_which_str);
646 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
647 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
653 draw_polyhedra (ModeInfo *mi)
655 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
656 Display *dpy = MI_DISPLAY(mi);
657 Window window = MI_WINDOW(mi);
659 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
660 GLfloat bshiny = 128.0;
662 if (!bp->glx_context)
665 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
667 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
669 else if (bp->mode == 0)
671 if (bp->change_to >= 0)
672 bp->change_tick = 999, bp->last_change_time = 1;
673 if (bp->change_tick++ > 10)
675 time_t now = time((time_t *) 0);
676 if (bp->last_change_time == 0) bp->last_change_time = now;
678 if (!bp->button_down_p && now - bp->last_change_time >= duration)
680 bp->mode = 1; /* go out */
681 bp->mode_tick = 20 * speed;
682 bp->last_change_time = now;
686 else if (bp->mode == 1) /* out */
688 if (--bp->mode_tick <= 0)
691 bp->mode_tick = 20 * speed;
692 bp->mode = 2; /* go in */
695 else if (bp->mode == 2) /* in */
697 if (--bp->mode_tick <= 0)
698 bp->mode = 0; /* normal */
703 glShadeModel(GL_FLAT);
704 glEnable(GL_NORMALIZE);
706 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
710 glScalef(1.1, 1.1, 1.1);
714 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
715 glTranslatef((x - 0.5) * 8,
719 /* Do it twice because we don't track the device's orientation. */
720 glRotatef( current_device_rotation(), 0, 0, 1);
721 gltrackball_rotate (bp->trackball);
722 glRotatef(-current_device_rotation(), 0, 0, 1);
724 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
725 glRotatef (x * 360, 1.0, 0.0, 0.0);
726 glRotatef (y * 360, 0.0, 1.0, 0.0);
727 glRotatef (z * 360, 0.0, 0.0, 1.0);
730 glScalef (2.0, 2.0, 2.0);
732 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
733 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
737 GLfloat s = (bp->mode == 1
738 ? bp->mode_tick / (20 * speed)
739 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
744 glCallList (bp->object_list);
745 if (bp->mode == 0 && !bp->button_down_p)
746 glCallList (bp->title_list);
750 if (mi->fps_p) do_fps (mi);
753 glXSwapBuffers(dpy, window);
756 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)