1 /* polyhedra, Copyright (c) 2004-2006 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))
229 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
230 !!event->xbutton.state);
233 else if (event->xany.type == KeyPress)
237 XLookupString (&event->xkey, &c, 1, &keysym, 0);
240 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
241 bp->change_to = random() % bp->npolyhedra;
242 else if (c == '>' || c == '.' || c == '+' || c == '=')
243 bp->change_to = (bp->which + 1) % bp->npolyhedra;
244 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
245 c == '\010' || c == '\177')
246 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
248 if (bp->change_to != -1)
251 else if (event->xany.type == MotionNotify &&
254 gltrackball_track (bp->trackball,
255 event->xmotion.x, event->xmotion.y,
256 MI_WIDTH (mi), MI_HEIGHT (mi));
265 new_label (ModeInfo *mi)
267 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
268 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
270 glNewList (bp->title_list, GL_COMPILE);
275 strcpy (name2, p->name);
277 sprintf (name2 + strlen(name2), " (%s)", p->class);
280 "Polyhedron %d: \t%s\n\n"
281 "Wythoff Symbol:\t%s\n"
282 "Vertex Configuration:\t%s\n"
283 "Symmetry Group:\t%s\n"
284 /* "Dual of: \t%s\n" */
291 bp->which, name2, p->wythoff, p->config, p->group,
293 p->logical_faces, p->nedges, p->logical_vertices,
294 p->density, (p->chi < 0 ? "" : " "), p->chi);
299 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
300 f = bp->xfont1, fl = bp->font1_dlist; /* big font */
301 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
302 f = bp->xfont2, fl = bp->font2_dlist; /* small font */
304 f = bp->xfont3, fl = bp->font3_dlist; /* tiny font */
306 glColor3f (0.8, 0.8, 0);
307 print_gl_string (mi->dpy, f, fl,
308 mi->xgwa.width, mi->xgwa.height,
309 10, mi->xgwa.height - 10,
318 tess_error (GLenum errorCode)
320 fprintf (stderr, "%s: tesselation error: %s\n",
321 progname, gluErrorString(errorCode));
326 new_polyhedron (ModeInfo *mi)
328 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
330 int wire = MI_IS_WIREFRAME(mi);
333 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
334 correctly (e.g., for the "pentagrammic concave deltohedron").
336 GLUtesselator *tobj = gluNewTess();
337 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
338 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
339 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
340 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
342 mi->polygon_count = 0;
345 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
346 make_random_colormap (0, 0, 0,
347 bp->colors, &bp->ncolors,
348 True, False, 0, False);
350 if (do_which >= bp->npolyhedra)
353 bp->which = (bp->change_to != -1 ? bp->change_to :
354 do_which >= 0 ? do_which :
355 (random() % bp->npolyhedra));
357 p = bp->polyhedra[bp->which];
362 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
364 glNewList (bp->object_list, GL_COMPILE);
365 for (i = 0; i < p->nfaces; i++)
368 face *f = &p->faces[i];
370 if (f->color > 64 || f->color < 0) abort();
376 bcolor[0] = bp->colors[f->color].red / 65536.0;
377 bcolor[1] = bp->colors[f->color].green / 65536.0;
378 bcolor[2] = bp->colors[f->color].blue / 65536.0;
380 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
383 kludge_normal (f->npoints, f->points, p->points);
385 gluTessBeginPolygon (tobj, 0);
386 gluTessBeginContour (tobj);
387 for (j = 0; j < f->npoints; j++)
389 point *pp = &p->points[f->points[j]];
390 gluTessVertex (tobj, &pp->x, &pp->x);
392 gluTessEndContour (tobj);
393 gluTessEndPolygon (tobj);
397 mi->polygon_count += p->nfaces;
398 gluDeleteTess (tobj);
403 init_polyhedra (ModeInfo *mi)
405 polyhedra_configuration *bp;
406 int wire = MI_IS_WIREFRAME(mi);
409 bps = (polyhedra_configuration *)
410 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
412 fprintf(stderr, "%s: out of memory\n", progname);
416 bp = &bps[MI_SCREEN(mi)];
419 bp = &bps[MI_SCREEN(mi)];
421 bp->glx_context = init_GL(mi);
427 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
431 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
432 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
433 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
434 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
436 glEnable(GL_LIGHTING);
438 glEnable(GL_DEPTH_TEST);
439 /* glEnable(GL_CULL_FACE); */
441 /* We need two-sided lighting for polyhedra where both sides of
442 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
444 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
446 glLightfv(GL_LIGHT0, GL_POSITION, pos);
447 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
448 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
449 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
453 double spin_speed = 2.0;
454 double wander_speed = 0.05;
455 double spin_accel = 0.2;
457 bp->rot = make_rotator (do_spin ? spin_speed : 0,
458 do_spin ? spin_speed : 0,
459 do_spin ? spin_speed : 0,
461 do_wander ? wander_speed : 0,
463 bp->trackball = gltrackball_init ();
466 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
468 bp->object_list = glGenLists (1);
469 bp->title_list = glGenLists (1);
476 if (!strcasecmp (do_which_str, "random"))
478 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
480 else if (*do_which_str)
483 for (s = do_which_str; *s; s++)
484 if (*s == '-' || *s == '_') *s = ' ';
486 for (x = 0; x < bp->npolyhedra; x++)
487 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
488 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
489 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
496 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
497 progname, do_which_str);
508 draw_polyhedra (ModeInfo *mi)
510 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
511 Display *dpy = MI_DISPLAY(mi);
512 Window window = MI_WINDOW(mi);
514 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
515 GLfloat bshiny = 128.0;
517 if (!bp->glx_context)
520 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
522 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
524 else if (bp->mode == 0)
526 if (bp->change_to >= 0)
527 bp->change_tick = 999, bp->last_change_time = 1;
528 if (bp->change_tick++ > 10)
530 time_t now = time((time_t *) 0);
531 if (bp->last_change_time == 0) bp->last_change_time = now;
533 if (!bp->button_down_p && now - bp->last_change_time >= duration)
535 bp->mode = 1; /* go out */
536 bp->mode_tick = 20 * speed;
537 bp->last_change_time = now;
541 else if (bp->mode == 1) /* out */
543 if (--bp->mode_tick <= 0)
546 bp->mode_tick = 20 * speed;
547 bp->mode = 2; /* go in */
550 else if (bp->mode == 2) /* in */
552 if (--bp->mode_tick <= 0)
553 bp->mode = 0; /* normal */
558 glShadeModel(GL_FLAT);
559 glEnable(GL_NORMALIZE);
561 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
565 glScalef(1.1, 1.1, 1.1);
569 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
570 glTranslatef((x - 0.5) * 8,
574 gltrackball_rotate (bp->trackball);
576 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
577 glRotatef (x * 360, 1.0, 0.0, 0.0);
578 glRotatef (y * 360, 0.0, 1.0, 0.0);
579 glRotatef (z * 360, 0.0, 0.0, 1.0);
582 glScalef (2.0, 2.0, 2.0);
584 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
585 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
589 GLfloat s = (bp->mode == 1
590 ? bp->mode_tick / (20 * speed)
591 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
596 glCallList (bp->object_list);
597 if (bp->mode == 0 && !bp->button_down_p)
598 glCallList (bp->title_list);
602 if (mi->fps_p) do_fps (mi);
605 glXSwapBuffers(dpy, window);
608 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)