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"
58 #include "polyhedra.h"
61 #include "gltrackball.h"
64 #ifdef USE_GL /* whole file */
69 GLXContext *glx_context;
71 trackball_state *trackball;
75 polyhedron **polyhedra;
82 int mode; /* 0 = normal, 1 = out, 2 = in */
88 XFontStruct *xfont1, *xfont2, *xfont3;
89 GLuint font1_dlist, font2_dlist, font3_dlist;
91 } polyhedra_configuration;
93 static polyhedra_configuration *bps = NULL;
97 static Bool do_wander;
98 static Bool do_titles;
101 static char *do_which_str;
103 static XrmOptionDescRec opts[] = {
104 { "-spin", ".spin", XrmoptionNoArg, "True" },
105 { "+spin", ".spin", XrmoptionNoArg, "False" },
106 { "-speed", ".speed", XrmoptionSepArg, 0 },
107 { "-wander", ".wander", XrmoptionNoArg, "True" },
108 { "+wander", ".wander", XrmoptionNoArg, "False" },
109 { "-titles", ".titles", XrmoptionNoArg, "True" },
110 { "+titles", ".titles", XrmoptionNoArg, "False" },
111 { "-duration",".duration",XrmoptionSepArg, 0 },
112 { "-which", ".which", XrmoptionSepArg, 0 },
115 static argtype vars[] = {
116 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
117 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
118 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
119 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
120 {&duration,"duration","Duration",DEF_DURATION,t_Int},
121 {&do_which_str,"which", "Which", DEF_WHICH, t_String},
124 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
128 /* Calculate the normals at each vertex of a face, and use the sum to
129 decide which normal to assign to the entire face. This also solves
130 problems caused by nonconvex faces, in most (but not all) cases.
133 kludge_normal (int n, const int *indices, const point *points)
135 XYZ normal = { 0, 0, 0 };
139 for (i = 0; i < n; ++i) {
141 int i2 = indices[(i + 1) % n];
142 int i3 = indices[(i + 2) % n];
145 p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
146 p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
147 p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
149 p = calc_normal (p1, p2, p3);
155 /*normalize(&normal);*/
156 if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
157 glNormal3f (p.x, p.y, p.z);
159 glNormal3f (normal.x, normal.y, normal.z);
165 load_fonts (ModeInfo *mi)
167 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
168 load_font (mi->dpy, "titleFont", &bp->xfont1, &bp->font1_dlist);
169 load_font (mi->dpy, "titleFont2", &bp->xfont2, &bp->font2_dlist);
170 load_font (mi->dpy, "titleFont3", &bp->xfont3, &bp->font3_dlist);
176 startup_blurb (ModeInfo *mi)
178 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
179 const char *s = "Computing polyhedra...";
180 glColor3f (0.8, 0.8, 0);
181 print_gl_string (mi->dpy, bp->xfont1, bp->font1_dlist,
182 mi->xgwa.width, mi->xgwa.height,
183 mi->xgwa.width - (string_width (bp->xfont1, s) + 40),
184 mi->xgwa.height - 10,
187 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
192 /* Window management, etc
194 static void new_label (ModeInfo *mi);
197 reshape_polyhedra (ModeInfo *mi, int width, int height)
199 GLfloat h = (GLfloat) height / (GLfloat) width;
201 glViewport (0, 0, (GLint) width, (GLint) height);
203 glMatrixMode(GL_PROJECTION);
205 gluPerspective (30.0, 1/h, 1.0, 100.0);
207 glMatrixMode(GL_MODELVIEW);
209 gluLookAt( 0.0, 0.0, 30.0,
213 glClear(GL_COLOR_BUFFER_BIT);
215 /* need to re-render the text when the window size changes */
221 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
223 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
225 if (event->xany.type == ButtonPress &&
226 event->xbutton.button == Button1)
228 bp->button_down_p = True;
229 gltrackball_start (bp->trackball,
230 event->xbutton.x, event->xbutton.y,
231 MI_WIDTH (mi), MI_HEIGHT (mi));
234 else if (event->xany.type == ButtonRelease &&
235 event->xbutton.button == Button1)
237 bp->button_down_p = False;
240 else if (event->xany.type == ButtonPress &&
241 (event->xbutton.button == Button4 ||
242 event->xbutton.button == Button5))
244 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
245 !!event->xbutton.state);
248 else if (event->xany.type == KeyPress)
252 XLookupString (&event->xkey, &c, 1, &keysym, 0);
255 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
256 bp->change_to = random() % bp->npolyhedra;
257 else if (c == '>' || c == '.' || c == '+' || c == '=')
258 bp->change_to = (bp->which + 1) % bp->npolyhedra;
259 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
260 c == '\010' || c == '\177')
261 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
263 if (bp->change_to != -1)
266 else if (event->xany.type == MotionNotify &&
269 gltrackball_track (bp->trackball,
270 event->xmotion.x, event->xmotion.y,
271 MI_WIDTH (mi), MI_HEIGHT (mi));
280 new_label (ModeInfo *mi)
282 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
283 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
285 glNewList (bp->title_list, GL_COMPILE);
290 strcpy (name2, p->name);
292 sprintf (name2 + strlen(name2), " (%s)", p->class);
295 "Polyhedron %d: \t%s\n\n"
296 "Wythoff Symbol:\t%s\n"
297 "Vertex Configuration:\t%s\n"
298 "Symmetry Group:\t%s\n"
299 /* "Dual of: \t%s\n" */
306 bp->which, name2, p->wythoff, p->config, p->group,
308 p->logical_faces, p->nedges, p->logical_vertices,
309 p->density, (p->chi < 0 ? "" : " "), p->chi);
314 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
315 f = bp->xfont1, fl = bp->font1_dlist; /* big font */
316 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
317 f = bp->xfont2, fl = bp->font2_dlist; /* small font */
319 f = bp->xfont3, fl = bp->font3_dlist; /* tiny font */
321 glColor3f (0.8, 0.8, 0);
322 print_gl_string (mi->dpy, f, fl,
323 mi->xgwa.width, mi->xgwa.height,
324 10, mi->xgwa.height - 10,
333 tess_error (GLenum errorCode)
335 fprintf (stderr, "%s: tesselation error: %s\n",
336 progname, gluErrorString(errorCode));
341 new_polyhedron (ModeInfo *mi)
343 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
345 int wire = MI_IS_WIREFRAME(mi);
346 static GLfloat bcolor[4] = {0.0, 0.0, 0.0, 1.0};
349 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
350 correctly (e.g., for the "pentagrammic concave deltohedron").
352 GLUtesselator *tobj = gluNewTess();
353 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
354 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
355 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
356 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
358 mi->polygon_count = 0;
361 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
362 make_random_colormap (0, 0, 0,
363 bp->colors, &bp->ncolors,
364 True, False, 0, False);
366 if (do_which >= bp->npolyhedra)
369 bp->which = (bp->change_to != -1 ? bp->change_to :
370 do_which >= 0 ? do_which :
371 (random() % bp->npolyhedra));
373 p = bp->polyhedra[bp->which];
377 glNewList (bp->object_list, GL_COMPILE);
378 for (i = 0; i < p->nfaces; i++)
381 face *f = &p->faces[i];
383 if (f->color > 64 || f->color < 0) abort();
388 bcolor[0] = bp->colors[f->color].red / 65536.0;
389 bcolor[1] = bp->colors[f->color].green / 65536.0;
390 bcolor[2] = bp->colors[f->color].blue / 65536.0;
391 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
394 kludge_normal (f->npoints, f->points, p->points);
396 gluTessBeginPolygon (tobj, 0);
397 gluTessBeginContour (tobj);
398 for (j = 0; j < f->npoints; j++)
400 point *pp = &p->points[f->points[j]];
401 gluTessVertex (tobj, &pp->x, &pp->x);
403 gluTessEndContour (tobj);
404 gluTessEndPolygon (tobj);
408 mi->polygon_count += p->nfaces;
409 gluDeleteTess (tobj);
414 init_polyhedra (ModeInfo *mi)
416 polyhedra_configuration *bp;
417 int wire = MI_IS_WIREFRAME(mi);
420 bps = (polyhedra_configuration *)
421 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
423 fprintf(stderr, "%s: out of memory\n", progname);
427 bp = &bps[MI_SCREEN(mi)];
430 bp = &bps[MI_SCREEN(mi)];
432 bp->glx_context = init_GL(mi);
438 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
442 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
443 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
444 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
445 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
447 glEnable(GL_LIGHTING);
449 glEnable(GL_DEPTH_TEST);
450 /* glEnable(GL_CULL_FACE); */
452 /* We need two-sided lighting for polyhedra where both sides of
453 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
455 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
457 glLightfv(GL_LIGHT0, GL_POSITION, pos);
458 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
459 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
460 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
464 double spin_speed = 2.0;
465 double wander_speed = 0.05;
466 double spin_accel = 0.2;
468 bp->rot = make_rotator (do_spin ? spin_speed : 0,
469 do_spin ? spin_speed : 0,
470 do_spin ? spin_speed : 0,
472 do_wander ? wander_speed : 0,
474 bp->trackball = gltrackball_init ();
477 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
479 bp->object_list = glGenLists (1);
480 bp->title_list = glGenLists (1);
487 if (1 == sscanf (do_which_str, " %d %c", &x, &c))
489 else if (*do_which_str)
492 for (s = do_which_str; *s; s++)
493 if (*s == '-' || *s == '_') *s = ' ';
495 for (x = 0; x < bp->npolyhedra; x++)
496 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
497 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
498 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
505 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
506 progname, do_which_str);
517 draw_polyhedra (ModeInfo *mi)
519 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
520 Display *dpy = MI_DISPLAY(mi);
521 Window window = MI_WINDOW(mi);
523 static time_t last_time = 0;
525 static GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
526 static GLfloat bshiny = 128.0;
528 if (!bp->glx_context)
531 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
533 else if (bp->mode == 0)
537 if (bp->change_to >= 0)
538 tick = 999, last_time = 1;
541 time_t now = time((time_t *) 0);
542 if (last_time == 0) last_time = now;
544 if (!bp->button_down_p && now - last_time >= duration)
546 bp->mode = 1; /* go out */
547 bp->mode_tick = 20 * speed;
552 else if (bp->mode == 1) /* out */
554 if (--bp->mode_tick <= 0)
557 bp->mode_tick = 20 * speed;
558 bp->mode = 2; /* go in */
561 else if (bp->mode == 2) /* in */
563 if (--bp->mode_tick <= 0)
564 bp->mode = 0; /* normal */
569 glShadeModel(GL_FLAT);
570 glEnable(GL_NORMALIZE);
572 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
576 glScalef(1.1, 1.1, 1.1);
580 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
581 glTranslatef((x - 0.5) * 8,
585 gltrackball_rotate (bp->trackball);
587 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
588 glRotatef (x * 360, 1.0, 0.0, 0.0);
589 glRotatef (y * 360, 0.0, 1.0, 0.0);
590 glRotatef (z * 360, 0.0, 0.0, 1.0);
593 glScalef (2.0, 2.0, 2.0);
595 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
596 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
600 GLfloat s = (bp->mode == 1
601 ? bp->mode_tick / (20 * speed)
602 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
607 glCallList (bp->object_list);
608 if (bp->mode == 0 && !bp->button_down_p)
609 glCallList (bp->title_list);
613 if (mi->fps_p) do_fps (mi);
616 glXSwapBuffers(dpy, window);