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];
372 glNewList (bp->object_list, GL_COMPILE);
373 for (i = 0; i < p->nfaces; i++)
376 face *f = &p->faces[i];
378 if (f->color > 64 || f->color < 0) abort();
383 bcolor[0] = bp->colors[f->color].red / 65536.0;
384 bcolor[1] = bp->colors[f->color].green / 65536.0;
385 bcolor[2] = bp->colors[f->color].blue / 65536.0;
386 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
389 kludge_normal (f->npoints, f->points, p->points);
391 gluTessBeginPolygon (tobj, 0);
392 gluTessBeginContour (tobj);
393 for (j = 0; j < f->npoints; j++)
395 point *pp = &p->points[f->points[j]];
396 gluTessVertex (tobj, &pp->x, &pp->x);
398 gluTessEndContour (tobj);
399 gluTessEndPolygon (tobj);
403 mi->polygon_count += p->nfaces;
404 gluDeleteTess (tobj);
409 init_polyhedra (ModeInfo *mi)
411 polyhedra_configuration *bp;
412 int wire = MI_IS_WIREFRAME(mi);
415 bps = (polyhedra_configuration *)
416 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
418 fprintf(stderr, "%s: out of memory\n", progname);
422 bp = &bps[MI_SCREEN(mi)];
425 bp = &bps[MI_SCREEN(mi)];
427 bp->glx_context = init_GL(mi);
433 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
437 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
438 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
439 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
440 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
442 glEnable(GL_LIGHTING);
444 glEnable(GL_DEPTH_TEST);
445 /* glEnable(GL_CULL_FACE); */
447 /* We need two-sided lighting for polyhedra where both sides of
448 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
450 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
452 glLightfv(GL_LIGHT0, GL_POSITION, pos);
453 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
454 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
455 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
459 double spin_speed = 2.0;
460 double wander_speed = 0.05;
461 double spin_accel = 0.2;
463 bp->rot = make_rotator (do_spin ? spin_speed : 0,
464 do_spin ? spin_speed : 0,
465 do_spin ? spin_speed : 0,
467 do_wander ? wander_speed : 0,
469 bp->trackball = gltrackball_init ();
472 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
474 bp->object_list = glGenLists (1);
475 bp->title_list = glGenLists (1);
482 if (1 == sscanf (do_which_str, " %d %c", &x, &c))
484 else if (*do_which_str)
487 for (s = do_which_str; *s; s++)
488 if (*s == '-' || *s == '_') *s = ' ';
490 for (x = 0; x < bp->npolyhedra; x++)
491 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
492 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
493 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
500 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
501 progname, do_which_str);
512 draw_polyhedra (ModeInfo *mi)
514 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
515 Display *dpy = MI_DISPLAY(mi);
516 Window window = MI_WINDOW(mi);
518 static time_t last_time = 0;
520 static GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
521 static GLfloat bshiny = 128.0;
523 if (!bp->glx_context)
526 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
528 else if (bp->mode == 0)
532 if (bp->change_to >= 0)
533 tick = 999, last_time = 1;
536 time_t now = time((time_t *) 0);
537 if (last_time == 0) last_time = now;
539 if (!bp->button_down_p && now - last_time >= duration)
541 bp->mode = 1; /* go out */
542 bp->mode_tick = 20 * speed;
547 else if (bp->mode == 1) /* out */
549 if (--bp->mode_tick <= 0)
552 bp->mode_tick = 20 * speed;
553 bp->mode = 2; /* go in */
556 else if (bp->mode == 2) /* in */
558 if (--bp->mode_tick <= 0)
559 bp->mode = 0; /* normal */
564 glShadeModel(GL_FLAT);
565 glEnable(GL_NORMALIZE);
567 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
571 glScalef(1.1, 1.1, 1.1);
575 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
576 glTranslatef((x - 0.5) * 8,
580 gltrackball_rotate (bp->trackball);
582 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
583 glRotatef (x * 360, 1.0, 0.0, 0.0);
584 glRotatef (y * 360, 0.0, 1.0, 0.0);
585 glRotatef (z * 360, 0.0, 0.0, 1.0);
588 glScalef (2.0, 2.0, 2.0);
590 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
591 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
595 GLfloat s = (bp->mode == 1
596 ? bp->mode_tick / (20 * speed)
597 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
602 glCallList (bp->object_list);
603 if (bp->mode == 0 && !bp->button_down_p)
604 glCallList (bp->title_list);
608 if (mi->fps_p) do_fps (mi);
611 glXSwapBuffers(dpy, window);