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 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
41 "*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
42 "*titleFont3: -*-times-bold-r-normal-*-80-*\n" \
46 #define countof(x) (sizeof((x))/sizeof((*x)))
48 #include "xlockmore.h"
53 #include "polyhedra.h"
56 #include "gltrackball.h"
59 #ifdef USE_GL /* whole file */
64 GLXContext *glx_context;
66 trackball_state *trackball;
70 polyhedron **polyhedra;
77 int mode; /* 0 = normal, 1 = out, 2 = in */
83 XFontStruct *xfont1, *xfont2, *xfont3;
84 GLuint font1_dlist, font2_dlist, font3_dlist;
86 } polyhedra_configuration;
88 static polyhedra_configuration *bps = NULL;
92 static Bool do_wander;
93 static Bool do_titles;
96 static char *do_which_str;
98 static XrmOptionDescRec opts[] = {
99 { "-spin", ".spin", XrmoptionNoArg, "True" },
100 { "+spin", ".spin", XrmoptionNoArg, "False" },
101 { "-speed", ".speed", XrmoptionSepArg, 0 },
102 { "-wander", ".wander", XrmoptionNoArg, "True" },
103 { "+wander", ".wander", XrmoptionNoArg, "False" },
104 { "-titles", ".titles", XrmoptionNoArg, "True" },
105 { "+titles", ".titles", XrmoptionNoArg, "False" },
106 { "-duration",".duration",XrmoptionSepArg, 0 },
107 { "-which", ".which", XrmoptionSepArg, 0 },
110 static argtype vars[] = {
111 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
112 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
113 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
114 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
115 {&duration,"duration","Duration",DEF_DURATION,t_Int},
116 {&do_which_str,"which", "Which", DEF_WHICH, t_String},
119 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
123 /* Calculate the normals at each vertex of a face, and use the sum to
124 decide which normal to assign to the entire face. This also solves
125 problems caused by nonconvex faces, in most (but not all) cases.
128 kludge_normal (int n, const int *indices, const point *points)
130 XYZ normal = { 0, 0, 0 };
134 for (i = 0; i < n; ++i) {
136 int i2 = indices[(i + 1) % n];
137 int i3 = indices[(i + 2) % n];
140 p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
141 p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
142 p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
144 p = calc_normal (p1, p2, p3);
150 /*normalize(&normal);*/
151 if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
152 glNormal3f (p.x, p.y, p.z);
154 glNormal3f (normal.x, normal.y, normal.z);
160 load_fonts (ModeInfo *mi)
162 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
163 load_font (mi->dpy, "titleFont", &bp->xfont1, &bp->font1_dlist);
164 load_font (mi->dpy, "titleFont2", &bp->xfont2, &bp->font2_dlist);
165 load_font (mi->dpy, "titleFont3", &bp->xfont3, &bp->font3_dlist);
171 startup_blurb (ModeInfo *mi)
173 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
174 const char *s = "Computing polyhedra...";
175 glColor3f (0.8, 0.8, 0);
176 print_gl_string (mi->dpy, bp->xfont1, bp->font1_dlist,
177 mi->xgwa.width, mi->xgwa.height,
178 mi->xgwa.width - (string_width (bp->xfont1, s) + 40),
179 mi->xgwa.height - 10,
182 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
187 /* Window management, etc
189 static void new_label (ModeInfo *mi);
192 reshape_polyhedra (ModeInfo *mi, int width, int height)
194 GLfloat h = (GLfloat) height / (GLfloat) width;
196 glViewport (0, 0, (GLint) width, (GLint) height);
198 glMatrixMode(GL_PROJECTION);
200 gluPerspective (30.0, 1/h, 1.0, 100.0);
202 glMatrixMode(GL_MODELVIEW);
204 gluLookAt( 0.0, 0.0, 30.0,
208 glClear(GL_COLOR_BUFFER_BIT);
210 /* need to re-render the text when the window size changes */
216 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
218 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
220 if (event->xany.type == ButtonPress &&
221 event->xbutton.button == Button1)
223 bp->button_down_p = True;
224 gltrackball_start (bp->trackball,
225 event->xbutton.x, event->xbutton.y,
226 MI_WIDTH (mi), MI_HEIGHT (mi));
229 else if (event->xany.type == ButtonRelease &&
230 event->xbutton.button == Button1)
232 bp->button_down_p = False;
235 else if (event->xany.type == ButtonPress &&
236 (event->xbutton.button == Button4 ||
237 event->xbutton.button == Button5))
239 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
240 !!event->xbutton.state);
243 else if (event->xany.type == KeyPress)
247 XLookupString (&event->xkey, &c, 1, &keysym, 0);
250 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
251 bp->change_to = random() % bp->npolyhedra;
252 else if (c == '>' || c == '.' || c == '+' || c == '=')
253 bp->change_to = (bp->which + 1) % bp->npolyhedra;
254 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
255 c == '\010' || c == '\177')
256 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
258 if (bp->change_to != -1)
261 else if (event->xany.type == MotionNotify &&
264 gltrackball_track (bp->trackball,
265 event->xmotion.x, event->xmotion.y,
266 MI_WIDTH (mi), MI_HEIGHT (mi));
275 new_label (ModeInfo *mi)
277 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
278 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
280 glNewList (bp->title_list, GL_COMPILE);
285 strcpy (name2, p->name);
287 sprintf (name2 + strlen(name2), " (%s)", p->class);
290 "Polyhedron %d: \t%s\n\n"
291 "Wythoff Symbol:\t%s\n"
292 "Vertex Configuration:\t%s\n"
293 "Symmetry Group:\t%s\n"
294 /* "Dual of: \t%s\n" */
301 bp->which, name2, p->wythoff, p->config, p->group,
303 p->logical_faces, p->nedges, p->logical_vertices,
304 p->density, (p->chi < 0 ? "" : " "), p->chi);
309 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
310 f = bp->xfont1, fl = bp->font1_dlist; /* big font */
311 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
312 f = bp->xfont2, fl = bp->font2_dlist; /* small font */
314 f = bp->xfont3, fl = bp->font3_dlist; /* tiny font */
316 glColor3f (0.8, 0.8, 0);
317 print_gl_string (mi->dpy, f, fl,
318 mi->xgwa.width, mi->xgwa.height,
319 10, mi->xgwa.height - 10,
328 tess_error (GLenum errorCode)
330 fprintf (stderr, "%s: tesselation error: %s\n",
331 progname, gluErrorString(errorCode));
336 new_polyhedron (ModeInfo *mi)
338 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
340 int wire = MI_IS_WIREFRAME(mi);
341 static GLfloat bcolor[4] = {0.0, 0.0, 0.0, 1.0};
344 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
345 correctly (e.g., for the "pentagrammic concave deltohedron").
347 GLUtesselator *tobj = gluNewTess();
348 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
349 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
350 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
351 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
353 mi->polygon_count = 0;
356 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
357 make_random_colormap (0, 0, 0,
358 bp->colors, &bp->ncolors,
359 True, False, 0, False);
361 if (do_which >= bp->npolyhedra)
364 bp->which = (bp->change_to != -1 ? bp->change_to :
365 do_which >= 0 ? do_which :
366 (random() % bp->npolyhedra));
368 p = bp->polyhedra[bp->which];
373 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
375 glNewList (bp->object_list, GL_COMPILE);
376 for (i = 0; i < p->nfaces; i++)
379 face *f = &p->faces[i];
381 if (f->color > 64 || f->color < 0) abort();
386 bcolor[0] = bp->colors[f->color].red / 65536.0;
387 bcolor[1] = bp->colors[f->color].green / 65536.0;
388 bcolor[2] = bp->colors[f->color].blue / 65536.0;
389 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
392 kludge_normal (f->npoints, f->points, p->points);
394 gluTessBeginPolygon (tobj, 0);
395 gluTessBeginContour (tobj);
396 for (j = 0; j < f->npoints; j++)
398 point *pp = &p->points[f->points[j]];
399 gluTessVertex (tobj, &pp->x, &pp->x);
401 gluTessEndContour (tobj);
402 gluTessEndPolygon (tobj);
406 mi->polygon_count += p->nfaces;
407 gluDeleteTess (tobj);
412 init_polyhedra (ModeInfo *mi)
414 polyhedra_configuration *bp;
415 int wire = MI_IS_WIREFRAME(mi);
418 bps = (polyhedra_configuration *)
419 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
421 fprintf(stderr, "%s: out of memory\n", progname);
425 bp = &bps[MI_SCREEN(mi)];
428 bp = &bps[MI_SCREEN(mi)];
430 bp->glx_context = init_GL(mi);
436 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
440 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
441 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
442 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
443 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
445 glEnable(GL_LIGHTING);
447 glEnable(GL_DEPTH_TEST);
448 /* glEnable(GL_CULL_FACE); */
450 /* We need two-sided lighting for polyhedra where both sides of
451 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
453 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
455 glLightfv(GL_LIGHT0, GL_POSITION, pos);
456 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
457 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
458 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
462 double spin_speed = 2.0;
463 double wander_speed = 0.05;
464 double spin_accel = 0.2;
466 bp->rot = make_rotator (do_spin ? spin_speed : 0,
467 do_spin ? spin_speed : 0,
468 do_spin ? spin_speed : 0,
470 do_wander ? wander_speed : 0,
472 bp->trackball = gltrackball_init ();
475 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
477 bp->object_list = glGenLists (1);
478 bp->title_list = glGenLists (1);
485 if (1 == sscanf (do_which_str, " %d %c", &x, &c))
487 else if (*do_which_str)
490 for (s = do_which_str; *s; s++)
491 if (*s == '-' || *s == '_') *s = ' ';
493 for (x = 0; x < bp->npolyhedra; x++)
494 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
495 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
496 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
503 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
504 progname, do_which_str);
515 draw_polyhedra (ModeInfo *mi)
517 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
518 Display *dpy = MI_DISPLAY(mi);
519 Window window = MI_WINDOW(mi);
521 static time_t last_time = 0;
523 static GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
524 static GLfloat bshiny = 128.0;
526 if (!bp->glx_context)
529 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
531 else if (bp->mode == 0)
535 if (bp->change_to >= 0)
536 tick = 999, last_time = 1;
539 time_t now = time((time_t *) 0);
540 if (last_time == 0) last_time = now;
542 if (!bp->button_down_p && now - last_time >= duration)
544 bp->mode = 1; /* go out */
545 bp->mode_tick = 20 * speed;
550 else if (bp->mode == 1) /* out */
552 if (--bp->mode_tick <= 0)
555 bp->mode_tick = 20 * speed;
556 bp->mode = 2; /* go in */
559 else if (bp->mode == 2) /* in */
561 if (--bp->mode_tick <= 0)
562 bp->mode = 0; /* normal */
567 glShadeModel(GL_FLAT);
568 glEnable(GL_NORMALIZE);
570 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
574 glScalef(1.1, 1.1, 1.1);
578 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
579 glTranslatef((x - 0.5) * 8,
583 gltrackball_rotate (bp->trackball);
585 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
586 glRotatef (x * 360, 1.0, 0.0, 0.0);
587 glRotatef (y * 360, 0.0, 1.0, 0.0);
588 glRotatef (z * 360, 0.0, 0.0, 1.0);
591 glScalef (2.0, 2.0, 2.0);
593 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
594 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
598 GLfloat s = (bp->mode == 1
599 ? bp->mode_tick / (20 * speed)
600 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
605 glCallList (bp->object_list);
606 if (bp->mode == 0 && !bp->button_down_p)
607 glCallList (bp->title_list);
611 if (mi->fps_p) do_fps (mi);
614 glXSwapBuffers(dpy, window);