1 /* polyhedra, Copyright (c) 2004 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 #include <X11/Intrinsic.h>
20 extern XtAppContext app;
22 #define PROGCLASS "Polyhedra"
23 #define HACK_INIT init_polyhedra
24 #define HACK_DRAW draw_polyhedra
25 #define HACK_RESHAPE reshape_polyhedra
26 #define HACK_HANDLE_EVENT polyhedra_handle_event
27 #define EVENT_MASK PointerMotionMask
28 #define sws_opts xlockmore_opts
30 #define DEF_SPIN "True"
31 #define DEF_WANDER "True"
32 #define DEF_SPEED "1.0"
33 #define DEF_TITLES "True"
34 #define DEF_DURATION "12"
35 #define DEF_WHICH "-1"
37 #define DEFAULTS "*delay: 30000 \n" \
38 "*showFPS: False \n" \
39 "*wireframe: False \n" \
40 "*speed: " DEF_SPEED "\n" \
41 "*spin: " DEF_SPIN "\n" \
42 "*wander: " DEF_WANDER "\n" \
43 "*duration: " DEF_DURATION "\n" \
44 "*which: " DEF_WHICH "\n" \
45 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
46 "*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
47 "*titleFont3: -*-times-bold-r-normal-*-80-*\n" \
51 #define countof(x) (sizeof((x))/sizeof((*x)))
53 #include "xlockmore.h"
56 #include "polyhedra.h"
59 #include "gltrackball.h"
62 #ifdef USE_GL /* whole file */
67 GLXContext *glx_context;
69 trackball_state *trackball;
73 polyhedron **polyhedra;
80 int mode; /* 0 = normal, 1 = out, 2 = in */
86 XFontStruct *xfont1, *xfont2, *xfont3;
87 GLuint font1_dlist, font2_dlist, font3_dlist;
89 } polyhedra_configuration;
91 static polyhedra_configuration *bps = NULL;
95 static Bool do_wander;
96 static Bool do_titles;
99 static char *do_which_str;
101 static XrmOptionDescRec opts[] = {
102 { "-spin", ".spin", XrmoptionNoArg, "True" },
103 { "+spin", ".spin", XrmoptionNoArg, "False" },
104 { "-speed", ".speed", XrmoptionSepArg, 0 },
105 { "-wander", ".wander", XrmoptionNoArg, "True" },
106 { "+wander", ".wander", XrmoptionNoArg, "False" },
107 { "-titles", ".titles", XrmoptionNoArg, "True" },
108 { "+titles", ".titles", XrmoptionNoArg, "False" },
109 { "-duration",".duration",XrmoptionSepArg, 0 },
110 { "-which", ".which", XrmoptionSepArg, 0 },
113 static argtype vars[] = {
114 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
115 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
116 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
117 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
118 {&duration,"duration","Duration",DEF_DURATION,t_Int},
119 {&do_which_str,"which", "Which", DEF_WHICH, t_String},
122 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
134 length = sqrt (p->x * p->x +
151 /* Calculate the unit normal at p given two other points p1,p2 on the
152 surface. The normal points in the direction of p1 crossproduct p2
155 calc_normal (XYZ p, XYZ p1, XYZ p2)
164 n.x = pa.y * pb.z - pa.z * pb.y;
165 n.y = pa.z * pb.x - pa.x * pb.z;
166 n.z = pa.x * pb.y - pa.y * pb.x;
172 /* Calculate the normals at each vertex of a face, and use the sum to
173 decide which normal to assign to the entire face. This also solves
174 problems caused by nonconvex faces, in most (but not all) cases.
177 kludge_normal (int n, const int *indices, const point *points)
179 XYZ normal = { 0, 0, 0 };
183 for (i = 0; i < n; ++i) {
185 int i2 = indices[(i + 1) % n];
186 int i3 = indices[(i + 2) % n];
189 p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
190 p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
191 p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
193 p = calc_normal (p1, p2, p3);
200 if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
201 glNormal3f (p.x, p.y, p.z);
203 glNormal3f (normal.x, normal.y, normal.z);
208 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
210 const char *font = get_string_resource (res, "Font");
215 if (!font) font = "-*-times-bold-r-normal-*-180-*";
217 f = XLoadQueryFont(mi->dpy, font);
218 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
221 first = f->min_char_or_byte2;
222 last = f->max_char_or_byte2;
225 *dlistP = glGenLists ((GLuint) last+1);
226 check_gl_error ("glGenLists");
227 glXUseXFont(id, first, last-first+1, *dlistP + first);
228 check_gl_error ("glXUseXFont");
235 load_fonts (ModeInfo *mi)
237 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
238 load_font (mi, "titleFont", &bp->xfont1, &bp->font1_dlist);
239 load_font (mi, "titleFont2", &bp->xfont2, &bp->font2_dlist);
240 load_font (mi, "titleFont3", &bp->xfont3, &bp->font3_dlist);
245 string_width (XFontStruct *f, const char *c)
250 int cc = *((unsigned char *) c);
252 ? f->per_char[cc-f->min_char_or_byte2].rbearing
253 : f->min_bounds.rbearing);
260 print_title_string (ModeInfo *mi, const char *string,
261 GLfloat x, GLfloat y,
262 XFontStruct *font, int font_dlist)
264 GLfloat line_height = font->ascent + font->descent;
265 GLfloat sub_shift = (line_height * 0.3);
266 int cw = string_width (font, "m");
271 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
272 GL_ENABLE_BIT); /* for various glDisable calls */
273 glDisable (GL_LIGHTING);
274 glDisable (GL_DEPTH_TEST);
276 glMatrixMode(GL_PROJECTION);
281 glMatrixMode(GL_MODELVIEW);
289 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
291 glColor3f (0.8, 0.8, 0);
293 glRasterPos2f (x, y);
294 for (i = 0; i < strlen(string); i++)
299 glRasterPos2f (x, (y -= line_height));
305 x2 = ((x2 + tabs) / tabs) * tabs; /* tab to tab stop */
307 glRasterPos2f (x2, y);
309 else if (c == '[' && (isdigit (string[i+1])))
312 glRasterPos2f (x2, (y -= sub_shift));
314 else if (c == ']' && sub_p)
317 glRasterPos2f (x2, (y += sub_shift));
321 glCallList (font_dlist + (int)(c));
322 x2 += (font->per_char
323 ? font->per_char[c - font->min_char_or_byte2].width
324 : font->min_bounds.width);
330 glMatrixMode(GL_PROJECTION);
335 glMatrixMode(GL_MODELVIEW);
340 startup_blurb (ModeInfo *mi)
342 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
343 const char *s = "Computing polyhedra...";
344 print_title_string (mi, s,
345 mi->xgwa.width - (string_width (bp->xfont1, s) + 40),
346 10 + bp->xfont1->ascent + bp->xfont1->descent,
347 bp->xfont1, bp->font1_dlist);
349 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
354 /* Window management, etc
356 static void new_label (ModeInfo *mi);
359 reshape_polyhedra (ModeInfo *mi, int width, int height)
361 GLfloat h = (GLfloat) height / (GLfloat) width;
363 glViewport (0, 0, (GLint) width, (GLint) height);
365 glMatrixMode(GL_PROJECTION);
367 gluPerspective (30.0, 1/h, 1.0, 100.0);
369 glMatrixMode(GL_MODELVIEW);
371 gluLookAt( 0.0, 0.0, 30.0,
375 glClear(GL_COLOR_BUFFER_BIT);
377 /* need to re-render the text when the window size changes */
383 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
385 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
387 if (event->xany.type == ButtonPress &&
388 event->xbutton.button & Button1)
390 bp->button_down_p = True;
391 gltrackball_start (bp->trackball,
392 event->xbutton.x, event->xbutton.y,
393 MI_WIDTH (mi), MI_HEIGHT (mi));
396 else if (event->xany.type == ButtonRelease &&
397 event->xbutton.button & Button1)
399 bp->button_down_p = False;
402 else if (event->xany.type == KeyPress)
406 XLookupString (&event->xkey, &c, 1, &keysym, 0);
409 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
410 bp->change_to = random() % bp->npolyhedra;
411 else if (c == '>' || c == '.' || c == '+' || c == '=')
412 bp->change_to = (bp->which + 1) % bp->npolyhedra;
413 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
414 c == '\010' || c == '\177')
415 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
417 if (bp->change_to != -1)
420 else if (event->xany.type == MotionNotify &&
423 gltrackball_track (bp->trackball,
424 event->xmotion.x, event->xmotion.y,
425 MI_WIDTH (mi), MI_HEIGHT (mi));
434 new_label (ModeInfo *mi)
436 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
437 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
439 glNewList (bp->title_list, GL_COMPILE);
444 strcpy (name2, p->name);
446 sprintf (name2 + strlen(name2), " (%s)", p->class);
449 "Polyhedron %d: \t%s\n\n"
450 "Wythoff Symbol:\t%s\n"
451 "Vertex Configuration:\t%s\n"
452 "Symmetry Group:\t%s\n"
453 /* "Dual of: \t%s\n" */
460 bp->which, name2, p->wythoff, p->config, p->group,
462 p->logical_faces, p->nedges, p->logical_vertices,
463 p->density, (p->chi < 0 ? "" : " "), p->chi);
468 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
469 f = bp->xfont1, fl = bp->font1_dlist; /* big font */
470 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
471 f = bp->xfont2, fl = bp->font2_dlist; /* small font */
473 f = bp->xfont3, fl = bp->font3_dlist; /* tiny font */
475 print_title_string (mi, label,
476 10, mi->xgwa.height - 10,
485 tess_error (GLenum errorCode)
487 fprintf (stderr, "%s: tesselation error: %s\n",
488 progname, gluErrorString(errorCode));
493 new_polyhedron (ModeInfo *mi)
495 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
497 int wire = MI_IS_WIREFRAME(mi);
498 static GLfloat bcolor[4] = {0.0, 0.0, 0.0, 1.0};
501 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
502 correctly (e.g., for the "pentagrammic concave deltohedron").
504 GLUtesselator *tobj = gluNewTess();
505 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
506 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
507 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
508 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
510 mi->polygon_count = 0;
513 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
514 make_random_colormap (0, 0, 0,
515 bp->colors, &bp->ncolors,
516 True, False, 0, False);
518 if (do_which >= bp->npolyhedra)
521 bp->which = (bp->change_to != -1 ? bp->change_to :
522 do_which >= 0 ? do_which :
523 (random() % bp->npolyhedra));
525 p = bp->polyhedra[bp->which];
529 glNewList (bp->object_list, GL_COMPILE);
530 for (i = 0; i < p->nfaces; i++)
533 face *f = &p->faces[i];
535 if (f->color > 64 || f->color < 0) abort();
540 bcolor[0] = bp->colors[f->color].red / 65536.0;
541 bcolor[1] = bp->colors[f->color].green / 65536.0;
542 bcolor[2] = bp->colors[f->color].blue / 65536.0;
543 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
546 kludge_normal (f->npoints, f->points, p->points);
548 gluTessBeginPolygon (tobj, 0);
549 gluTessBeginContour (tobj);
550 for (j = 0; j < f->npoints; j++)
552 point *pp = &p->points[f->points[j]];
553 gluTessVertex (tobj, &pp->x, &pp->x);
555 gluTessEndContour (tobj);
556 gluTessEndPolygon (tobj);
560 mi->polygon_count += p->nfaces;
561 gluDeleteTess (tobj);
566 init_polyhedra (ModeInfo *mi)
568 polyhedra_configuration *bp;
569 int wire = MI_IS_WIREFRAME(mi);
572 bps = (polyhedra_configuration *)
573 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
575 fprintf(stderr, "%s: out of memory\n", progname);
579 bp = &bps[MI_SCREEN(mi)];
582 bp = &bps[MI_SCREEN(mi)];
584 bp->glx_context = init_GL(mi);
590 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
594 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
595 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
596 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
597 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
599 glEnable(GL_LIGHTING);
601 glEnable(GL_DEPTH_TEST);
602 /* glEnable(GL_CULL_FACE); */
604 /* We need two-sided lighting for polyhedra where both sides of
605 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
607 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
609 glLightfv(GL_LIGHT0, GL_POSITION, pos);
610 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
611 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
612 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
616 double spin_speed = 2.0;
617 double wander_speed = 0.05;
618 double spin_accel = 0.2;
620 bp->rot = make_rotator (do_spin ? spin_speed : 0,
621 do_spin ? spin_speed : 0,
622 do_spin ? spin_speed : 0,
624 do_wander ? wander_speed : 0,
626 bp->trackball = gltrackball_init ();
629 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
631 bp->object_list = glGenLists (1);
632 bp->title_list = glGenLists (1);
639 if (1 == sscanf (do_which_str, " %d %c", &x, &c))
641 else if (*do_which_str)
644 for (s = do_which_str; *s; s++)
645 if (*s == '-' || *s == '_') *s = ' ';
647 for (x = 0; x < bp->npolyhedra; x++)
648 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
649 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
650 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
657 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
658 progname, do_which_str);
669 draw_polyhedra (ModeInfo *mi)
671 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
672 Display *dpy = MI_DISPLAY(mi);
673 Window window = MI_WINDOW(mi);
675 static time_t last_time = 0;
677 static GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
678 static GLfloat bshiny = 128.0;
680 if (!bp->glx_context)
683 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
685 else if (bp->mode == 0)
689 if (bp->change_to >= 0)
690 tick = 999, last_time = 1;
693 time_t now = time((time_t *) 0);
694 if (last_time == 0) last_time = now;
696 if (!bp->button_down_p && now - last_time >= duration)
698 bp->mode = 1; /* go out */
699 bp->mode_tick = 20 * speed;
704 else if (bp->mode == 1) /* out */
706 if (--bp->mode_tick <= 0)
709 bp->mode_tick = 20 * speed;
710 bp->mode = 2; /* go in */
713 else if (bp->mode == 2) /* in */
715 if (--bp->mode_tick <= 0)
716 bp->mode = 0; /* normal */
721 glShadeModel(GL_FLAT);
722 glEnable(GL_NORMALIZE);
724 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
728 glScalef(1.1, 1.1, 1.1);
732 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
733 glTranslatef((x - 0.5) * 8,
737 gltrackball_rotate (bp->trackball);
739 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
740 glRotatef (x * 360, 1.0, 0.0, 0.0);
741 glRotatef (y * 360, 0.0, 1.0, 0.0);
742 glRotatef (z * 360, 0.0, 0.0, 1.0);
745 glScalef (2.0, 2.0, 2.0);
747 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
748 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
752 GLfloat s = (bp->mode == 1
753 ? bp->mode_tick / (20 * speed)
754 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
759 glCallList (bp->object_list);
760 if (bp->mode == 0 && !bp->button_down_p)
761 glCallList (bp->title_list);
765 if (mi->fps_p) do_fps (mi);
768 glXSwapBuffers(dpy, window);