1 /* polyhedra, Copyright (c) 2004-2008 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: -*-times-bold-r-normal-*-180-*\n" \
22 "*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
23 "*titleFont3: -*-times-bold-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"
34 #define DEF_SPIN "True"
35 #define DEF_WANDER "True"
36 #define DEF_SPEED "1.0"
37 #define DEF_TITLES "True"
38 #define DEF_DURATION "12"
39 #define DEF_WHICH "random"
43 #include "polyhedra.h"
46 #include "gltrackball.h"
48 #ifdef USE_GL /* whole file */
51 GLXContext *glx_context;
53 trackball_state *trackball;
57 polyhedron **polyhedra;
64 int mode; /* 0 = normal, 1 = out, 2 = in */
70 XFontStruct *xfont1, *xfont2, *xfont3;
71 GLuint font1_dlist, font2_dlist, font3_dlist;
73 time_t last_change_time;
76 } polyhedra_configuration;
78 static polyhedra_configuration *bps = NULL;
82 static Bool do_wander;
83 static Bool do_titles;
86 static char *do_which_str;
88 static XrmOptionDescRec opts[] = {
89 { "-spin", ".spin", XrmoptionNoArg, "True" },
90 { "+spin", ".spin", XrmoptionNoArg, "False" },
91 { "-speed", ".speed", XrmoptionSepArg, 0 },
92 { "-wander", ".wander", XrmoptionNoArg, "True" },
93 { "+wander", ".wander", XrmoptionNoArg, "False" },
94 { "-titles", ".titles", XrmoptionNoArg, "True" },
95 { "+titles", ".titles", XrmoptionNoArg, "False" },
96 { "-duration",".duration",XrmoptionSepArg, 0 },
97 { "-which", ".which", XrmoptionSepArg, 0 },
100 static argtype vars[] = {
101 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
102 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
103 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
104 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
105 {&duration,"duration","Duration",DEF_DURATION,t_Int},
106 {&do_which_str,"which", "Which", DEF_WHICH, t_String},
109 ENTRYPOINT ModeSpecOpt polyhedra_opts = {countof(opts), opts, countof(vars), vars, NULL};
113 /* Calculate the normals at each vertex of a face, and use the sum to
114 decide which normal to assign to the entire face. This also solves
115 problems caused by nonconvex faces, in most (but not all) cases.
118 kludge_normal (int n, const int *indices, const point *points)
120 XYZ normal = { 0, 0, 0 };
124 for (i = 0; i < n; ++i) {
126 int i2 = indices[(i + 1) % n];
127 int i3 = indices[(i + 2) % n];
130 p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
131 p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
132 p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
134 p = calc_normal (p1, p2, p3);
140 /*normalize(&normal);*/
141 if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
142 glNormal3f (p.x, p.y, p.z);
144 glNormal3f (normal.x, normal.y, normal.z);
150 load_fonts (ModeInfo *mi)
152 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
153 load_font (mi->dpy, "titleFont", &bp->xfont1, &bp->font1_dlist);
154 load_font (mi->dpy, "titleFont2", &bp->xfont2, &bp->font2_dlist);
155 load_font (mi->dpy, "titleFont3", &bp->xfont3, &bp->font3_dlist);
161 startup_blurb (ModeInfo *mi)
163 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
164 const char *s = "Computing polyhedra...";
165 glColor3f (0.8, 0.8, 0);
166 print_gl_string (mi->dpy, bp->xfont1, bp->font1_dlist,
167 mi->xgwa.width, mi->xgwa.height,
168 mi->xgwa.width - (string_width (bp->xfont1, s) + 40),
169 mi->xgwa.height - 10,
172 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
177 /* Window management, etc
179 static void new_label (ModeInfo *mi);
182 reshape_polyhedra (ModeInfo *mi, int width, int height)
184 GLfloat h = (GLfloat) height / (GLfloat) width;
186 glViewport (0, 0, (GLint) width, (GLint) height);
188 glMatrixMode(GL_PROJECTION);
190 gluPerspective (30.0, 1/h, 1.0, 100.0);
192 glMatrixMode(GL_MODELVIEW);
194 gluLookAt( 0.0, 0.0, 30.0,
198 glClear(GL_COLOR_BUFFER_BIT);
200 /* need to re-render the text when the window size changes */
206 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
208 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
210 if (event->xany.type == ButtonPress &&
211 event->xbutton.button == Button1)
213 bp->button_down_p = True;
214 gltrackball_start (bp->trackball,
215 event->xbutton.x, event->xbutton.y,
216 MI_WIDTH (mi), MI_HEIGHT (mi));
219 else if (event->xany.type == ButtonRelease &&
220 event->xbutton.button == Button1)
222 bp->button_down_p = False;
225 else if (event->xany.type == ButtonPress &&
226 (event->xbutton.button == Button4 ||
227 event->xbutton.button == Button5 ||
228 event->xbutton.button == Button6 ||
229 event->xbutton.button == Button7))
231 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
232 !!event->xbutton.state);
235 else if (event->xany.type == KeyPress)
239 XLookupString (&event->xkey, &c, 1, &keysym, 0);
242 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
243 bp->change_to = random() % bp->npolyhedra;
244 else if (c == '>' || c == '.' || c == '+' || c == '=')
245 bp->change_to = (bp->which + 1) % bp->npolyhedra;
246 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
247 c == '\010' || c == '\177')
248 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
250 if (bp->change_to != -1)
253 else if (event->xany.type == MotionNotify &&
256 gltrackball_track (bp->trackball,
257 event->xmotion.x, event->xmotion.y,
258 MI_WIDTH (mi), MI_HEIGHT (mi));
267 new_label (ModeInfo *mi)
269 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
270 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
272 glNewList (bp->title_list, GL_COMPILE);
277 strcpy (name2, p->name);
279 sprintf (name2 + strlen(name2), " (%s)", p->class);
282 "Polyhedron %d: \t%s\n\n"
283 "Wythoff Symbol:\t%s\n"
284 "Vertex Configuration:\t%s\n"
285 "Symmetry Group:\t%s\n"
286 /* "Dual of: \t%s\n" */
293 bp->which, name2, p->wythoff, p->config, p->group,
295 p->logical_faces, p->nedges, p->logical_vertices,
296 p->density, (p->chi < 0 ? "" : " "), p->chi);
301 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
302 f = bp->xfont1, fl = bp->font1_dlist; /* big font */
303 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
304 f = bp->xfont2, fl = bp->font2_dlist; /* small font */
306 f = bp->xfont3, fl = bp->font3_dlist; /* tiny font */
308 glColor3f (0.8, 0.8, 0);
309 print_gl_string (mi->dpy, f, fl,
310 mi->xgwa.width, mi->xgwa.height,
311 10, mi->xgwa.height - 10,
320 tess_error (GLenum errorCode)
322 fprintf (stderr, "%s: tesselation error: %s\n",
323 progname, gluErrorString(errorCode));
328 new_polyhedron (ModeInfo *mi)
330 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
332 int wire = MI_IS_WIREFRAME(mi);
335 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
336 correctly (e.g., for the "pentagrammic concave deltohedron").
338 GLUtesselator *tobj = gluNewTess();
339 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
340 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
341 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
342 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
344 mi->polygon_count = 0;
347 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
348 make_random_colormap (0, 0, 0,
349 bp->colors, &bp->ncolors,
350 True, False, 0, False);
352 if (do_which >= bp->npolyhedra)
355 bp->which = (bp->change_to != -1 ? bp->change_to :
356 do_which >= 0 ? do_which :
357 (random() % bp->npolyhedra));
359 p = bp->polyhedra[bp->which];
364 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
366 glNewList (bp->object_list, GL_COMPILE);
367 for (i = 0; i < p->nfaces; i++)
370 face *f = &p->faces[i];
372 if (f->color > 64 || f->color < 0) abort();
378 bcolor[0] = bp->colors[f->color].red / 65536.0;
379 bcolor[1] = bp->colors[f->color].green / 65536.0;
380 bcolor[2] = bp->colors[f->color].blue / 65536.0;
382 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
385 kludge_normal (f->npoints, f->points, p->points);
387 gluTessBeginPolygon (tobj, 0);
388 gluTessBeginContour (tobj);
389 for (j = 0; j < f->npoints; j++)
391 point *pp = &p->points[f->points[j]];
392 gluTessVertex (tobj, &pp->x, &pp->x);
394 gluTessEndContour (tobj);
395 gluTessEndPolygon (tobj);
399 mi->polygon_count += p->nfaces;
400 gluDeleteTess (tobj);
405 init_polyhedra (ModeInfo *mi)
407 polyhedra_configuration *bp;
408 int wire = MI_IS_WIREFRAME(mi);
411 bps = (polyhedra_configuration *)
412 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
414 fprintf(stderr, "%s: out of memory\n", progname);
418 bp = &bps[MI_SCREEN(mi)];
421 bp = &bps[MI_SCREEN(mi)];
423 bp->glx_context = init_GL(mi);
429 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
433 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
434 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
435 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
436 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
438 glEnable(GL_LIGHTING);
440 glEnable(GL_DEPTH_TEST);
441 /* glEnable(GL_CULL_FACE); */
443 /* We need two-sided lighting for polyhedra where both sides of
444 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
446 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
448 glLightfv(GL_LIGHT0, GL_POSITION, pos);
449 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
450 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
451 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
455 double spin_speed = 2.0;
456 double wander_speed = 0.05;
457 double spin_accel = 0.2;
459 bp->rot = make_rotator (do_spin ? spin_speed : 0,
460 do_spin ? spin_speed : 0,
461 do_spin ? spin_speed : 0,
463 do_wander ? wander_speed : 0,
465 bp->trackball = gltrackball_init ();
468 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
470 bp->object_list = glGenLists (1);
471 bp->title_list = glGenLists (1);
478 if (!strcasecmp (do_which_str, "random"))
480 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
482 else if (*do_which_str)
485 for (s = do_which_str; *s; s++)
486 if (*s == '-' || *s == '_') *s = ' ';
488 for (x = 0; x < bp->npolyhedra; x++)
489 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
490 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
491 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
498 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
499 progname, do_which_str);
510 draw_polyhedra (ModeInfo *mi)
512 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
513 Display *dpy = MI_DISPLAY(mi);
514 Window window = MI_WINDOW(mi);
516 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
517 GLfloat bshiny = 128.0;
519 if (!bp->glx_context)
522 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
524 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
526 else if (bp->mode == 0)
528 if (bp->change_to >= 0)
529 bp->change_tick = 999, bp->last_change_time = 1;
530 if (bp->change_tick++ > 10)
532 time_t now = time((time_t *) 0);
533 if (bp->last_change_time == 0) bp->last_change_time = now;
535 if (!bp->button_down_p && now - bp->last_change_time >= duration)
537 bp->mode = 1; /* go out */
538 bp->mode_tick = 20 * speed;
539 bp->last_change_time = now;
543 else if (bp->mode == 1) /* out */
545 if (--bp->mode_tick <= 0)
548 bp->mode_tick = 20 * speed;
549 bp->mode = 2; /* go in */
552 else if (bp->mode == 2) /* in */
554 if (--bp->mode_tick <= 0)
555 bp->mode = 0; /* normal */
560 glShadeModel(GL_FLAT);
561 glEnable(GL_NORMALIZE);
563 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
567 glScalef(1.1, 1.1, 1.1);
571 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
572 glTranslatef((x - 0.5) * 8,
576 gltrackball_rotate (bp->trackball);
578 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
579 glRotatef (x * 360, 1.0, 0.0, 0.0);
580 glRotatef (y * 360, 0.0, 1.0, 0.0);
581 glRotatef (z * 360, 0.0, 0.0, 1.0);
584 glScalef (2.0, 2.0, 2.0);
586 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
587 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
591 GLfloat s = (bp->mode == 1
592 ? bp->mode_tick / (20 * speed)
593 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
598 glCallList (bp->object_list);
599 if (bp->mode == 0 && !bp->button_down_p)
600 glCallList (bp->title_list);
604 if (mi->fps_p) do_fps (mi);
607 glXSwapBuffers(dpy, window);
610 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)