1 /* polyhedra, Copyright (c) 2004-2014 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: -*-helvetica-medium-r-normal-*-*-140-*-*-*-*-*-*\n" \
22 "*titleFont2: -*-helvetica-medium-r-normal-*-*-100-*-*-*-*-*-*\n" \
23 "*titleFont3: -*-helvetica-medium-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"
36 # include <X11/Xlib.h>
43 #endif /* HAVE_JWZGLES */
45 #define DEF_SPIN "True"
46 #define DEF_WANDER "True"
47 #define DEF_SPEED "1.0"
48 #define DEF_TITLES "True"
49 #define DEF_DURATION "12"
50 #define DEF_WHICH "random"
54 #include "polyhedra.h"
57 #include "gltrackball.h"
61 # define XK_MISCELLANY
62 # include <X11/keysymdef.h>
70 #ifdef USE_GL /* whole file */
73 GLXContext *glx_context;
75 trackball_state *trackball;
79 polyhedron **polyhedra;
85 int mode; /* 0 = normal, 1 = out, 2 = in */
91 texture_font_data *font1_data, *font2_data, *font3_data;
93 time_t last_change_time;
96 } polyhedra_configuration;
98 static polyhedra_configuration *bps = NULL;
101 static GLfloat speed;
102 static Bool do_wander;
103 static Bool do_titles;
106 static char *do_which_str;
108 static XrmOptionDescRec opts[] = {
109 { "-spin", ".spin", XrmoptionNoArg, "True" },
110 { "+spin", ".spin", XrmoptionNoArg, "False" },
111 { "-speed", ".speed", XrmoptionSepArg, 0 },
112 { "-wander", ".wander", XrmoptionNoArg, "True" },
113 { "+wander", ".wander", XrmoptionNoArg, "False" },
114 { "-titles", ".titles", XrmoptionNoArg, "True" },
115 { "+titles", ".titles", XrmoptionNoArg, "False" },
116 { "-duration",".duration",XrmoptionSepArg, 0 },
117 { "-which", ".which", XrmoptionSepArg, 0 },
120 static argtype vars[] = {
121 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
122 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
123 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
124 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
125 {&duration,"duration","Duration",DEF_DURATION,t_Int},
126 {&do_which_str,"which", "Which", DEF_WHICH, t_String},
129 ENTRYPOINT ModeSpecOpt polyhedra_opts = {countof(opts), opts, countof(vars), vars, NULL};
133 /* Calculate the normals at each vertex of a face, and use the sum to
134 decide which normal to assign to the entire face. This also solves
135 problems caused by nonconvex faces, in most (but not all) cases.
138 kludge_normal (int n, const int *indices, const point *points)
140 XYZ normal = { 0, 0, 0 };
144 for (i = 0; i < n; ++i) {
146 int i2 = indices[(i + 1) % n];
147 int i3 = indices[(i + 2) % n];
150 p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
151 p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
152 p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
154 p = calc_normal (p1, p2, p3);
160 /*normalize(&normal);*/
161 if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
162 glNormal3f (p.x, p.y, p.z);
164 glNormal3f (normal.x, normal.y, normal.z);
170 load_fonts (ModeInfo *mi)
172 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
173 bp->font1_data = load_texture_font (mi->dpy, "titleFont");
174 bp->font2_data = load_texture_font (mi->dpy, "titleFont2");
175 bp->font3_data = load_texture_font (mi->dpy, "titleFont3");
181 startup_blurb (ModeInfo *mi)
183 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
184 const char *s = "Computing polyhedra...";
185 texture_font_data *f = bp->font1_data;
187 glColor3f (0.8, 0.8, 0);
188 print_texture_label (mi->dpy, f,
189 mi->xgwa.width, mi->xgwa.height,
192 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
197 /* Window management, etc
200 reshape_polyhedra (ModeInfo *mi, int width, int height)
202 GLfloat h = (GLfloat) height / (GLfloat) width;
204 glViewport (0, 0, (GLint) width, (GLint) height);
206 glMatrixMode(GL_PROJECTION);
208 gluPerspective (30.0, 1/h, 1.0, 100.0);
210 glMatrixMode(GL_MODELVIEW);
212 gluLookAt( 0.0, 0.0, 30.0,
216 glClear(GL_COLOR_BUFFER_BIT);
221 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
223 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
225 if (gltrackball_event_handler (event, bp->trackball,
226 MI_WIDTH (mi), MI_HEIGHT (mi),
229 else if (event->xany.type == KeyPress)
233 XLookupString (&event->xkey, &c, 1, &keysym, 0);
236 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
237 bp->change_to = random() % bp->npolyhedra;
238 else if (c == '>' || c == '.' || c == '+' || c == '=' ||
239 keysym == XK_Right || keysym == XK_Up || keysym == XK_Next)
240 bp->change_to = (bp->which + 1) % bp->npolyhedra;
241 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
242 c == '\010' || c == '\177' ||
243 keysym == XK_Left || keysym == XK_Down || keysym == XK_Prior)
244 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
245 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
248 if (bp->change_to != -1)
251 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
254 bp->change_to = random() % bp->npolyhedra;
263 draw_label (ModeInfo *mi)
265 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
266 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
269 GLfloat color[4] = { 0.8, 0.8, 0.8, 1 };
270 texture_font_data *f;
272 if (!p || !do_titles) return;
274 strcpy (name2, p->name);
276 sprintf (name2 + strlen(name2), " (%s)", p->class);
279 "Polyhedron %d: \t%s\n\n"
280 "Wythoff Symbol:\t%s\n"
281 "Vertex Configuration:\t%s\n"
282 "Symmetry Group:\t%s\n"
283 /* "Dual of: \t%s\n" */
290 bp->which, name2, p->wythoff, p->config, p->group,
292 p->logical_faces, p->nedges, p->logical_vertices,
293 p->density, (p->chi < 0 ? "" : " "), p->chi);
295 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
297 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
298 f = bp->font2_data; /* small font */
300 f = bp->font3_data; /* tiny font */
303 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
304 print_texture_label (mi->dpy, f,
305 mi->xgwa.width, mi->xgwa.height,
312 tess_error (GLenum errorCode)
314 fprintf (stderr, "%s: tesselation error: %s\n",
315 progname, gluErrorString(errorCode));
318 #endif /* HAVE_TESS */
322 new_polyhedron (ModeInfo *mi)
324 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
326 int wire = MI_IS_WIREFRAME(mi);
329 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
330 correctly (e.g., for the "pentagrammic concave deltohedron").
333 GLUtesselator *tobj = gluNewTess();
334 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
335 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
336 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
337 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
338 # endif /* HAVE_TESS */
340 mi->polygon_count = 0;
343 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
344 make_random_colormap (0, 0, 0,
345 bp->colors, &bp->ncolors,
346 True, False, 0, False);
348 if (do_which >= bp->npolyhedra)
351 bp->which = (bp->change_to != -1 ? bp->change_to :
352 do_which >= 0 ? do_which :
353 (random() % bp->npolyhedra));
355 p = bp->polyhedra[bp->which];
358 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
360 glNewList (bp->object_list, GL_COMPILE);
361 if (bp->which == bp->npolyhedra-1)
364 bcolor[0] = bp->colors[0].red / 65536.0;
365 bcolor[1] = bp->colors[0].green / 65536.0;
366 bcolor[2] = bp->colors[0].blue / 65536.0;
371 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
373 glScalef (0.8, 0.8, 0.8);
374 p->nfaces = unit_teapot (6, wire);
375 p->nedges = p->nfaces * 3 / 2;
376 p->npoints = p->nfaces * 3;
377 p->logical_faces = p->nfaces;
378 p->logical_vertices = p->npoints;
382 glFrontFace (GL_CCW);
383 for (i = 0; i < p->nfaces; i++)
386 face *f = &p->faces[i];
388 if (f->color > 64 || f->color < 0) abort();
394 bcolor[0] = bp->colors[f->color].red / 65536.0;
395 bcolor[1] = bp->colors[f->color].green / 65536.0;
396 bcolor[2] = bp->colors[f->color].blue / 65536.0;
398 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
401 kludge_normal (f->npoints, f->points, p->points);
404 gluTessBeginPolygon (tobj, 0);
405 gluTessBeginContour (tobj);
406 for (j = 0; j < f->npoints; j++)
408 point *pp = &p->points[f->points[j]];
409 gluTessVertex (tobj, &pp->x, &pp->x);
411 gluTessEndContour (tobj);
412 gluTessEndPolygon (tobj);
413 # else /* !HAVE_TESS */
414 glBegin (wire ? GL_LINE_LOOP :
415 f->npoints == 3 ? GL_TRIANGLES :
416 f->npoints == 4 ? GL_QUADS :
418 for (j = 0; j < f->npoints; j++)
420 point *pp = &p->points[f->points[j]];
421 glVertex3f (pp->x, pp->y, pp->z);
424 # endif /* !HAVE_TESS */
429 mi->polygon_count += p->nfaces;
431 gluDeleteTess (tobj);
437 construct_teapot (ModeInfo *mi)
439 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
440 int n = bp->npolyhedra-1;
441 polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
443 p->wythoff = strdup("X00398|1984");
444 p->name = strdup("Teapot");
445 p->dual = strdup("");
446 p->config = strdup("Melitta");
447 p->group = strdup("Teapotahedral (Newell[1975])");
448 p->class = strdup("Utah Teapotahedron");
449 bp->polyhedra[n] = p;
454 init_polyhedra (ModeInfo *mi)
456 polyhedra_configuration *bp;
457 int wire = MI_IS_WIREFRAME(mi);
459 # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
460 MI_IS_WIREFRAME(mi) = 0;
465 bps = (polyhedra_configuration *)
466 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
468 fprintf(stderr, "%s: out of memory\n", progname);
473 bp = &bps[MI_SCREEN(mi)];
475 bp->glx_context = init_GL(mi);
483 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
484 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
485 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
486 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
488 glEnable(GL_LIGHTING);
490 glEnable(GL_DEPTH_TEST);
491 /* glEnable(GL_CULL_FACE); */
493 /* We need two-sided lighting for polyhedra where both sides of
494 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
496 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
498 glLightfv(GL_LIGHT0, GL_POSITION, pos);
499 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
500 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
501 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
505 double spin_speed = 2.0;
506 double wander_speed = 0.05;
507 double spin_accel = 0.2;
509 bp->rot = make_rotator (do_spin ? spin_speed : 0,
510 do_spin ? spin_speed : 0,
511 do_spin ? spin_speed : 0,
513 do_wander ? wander_speed : 0,
515 bp->trackball = gltrackball_init (True);
518 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
519 construct_teapot (mi);
521 bp->object_list = glGenLists (1);
528 if (!strcasecmp (do_which_str, "random"))
530 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
532 if (x >= 0 && x < bp->npolyhedra)
536 "%s: polyhedron %d does not exist: there are only %d.\n",
537 progname, x, bp->npolyhedra-1);
539 else if (*do_which_str)
542 for (s = do_which_str; *s; s++)
543 if (*s == '-' || *s == '_') *s = ' ';
545 for (x = 0; x < bp->npolyhedra; x++)
546 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
547 !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
548 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
549 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
556 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
557 progname, do_which_str);
564 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
565 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
571 draw_polyhedra (ModeInfo *mi)
573 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
574 Display *dpy = MI_DISPLAY(mi);
575 Window window = MI_WINDOW(mi);
577 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
578 GLfloat bshiny = 128.0;
580 if (!bp->glx_context)
583 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
585 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
587 else if (bp->mode == 0)
589 if (bp->change_to >= 0)
590 bp->change_tick = 999, bp->last_change_time = 1;
591 if (bp->change_tick++ > 10)
593 time_t now = time((time_t *) 0);
594 if (bp->last_change_time == 0) bp->last_change_time = now;
596 if (!bp->button_down_p && now - bp->last_change_time >= duration)
598 bp->mode = 1; /* go out */
599 bp->mode_tick = 20 / speed;
600 bp->last_change_time = now;
604 else if (bp->mode == 1) /* out */
606 if (--bp->mode_tick <= 0)
609 bp->mode_tick = 20 / speed;
610 bp->mode = 2; /* go in */
613 else if (bp->mode == 2) /* in */
615 if (--bp->mode_tick <= 0)
616 bp->mode = 0; /* normal */
621 glShadeModel(GL_FLAT);
622 glEnable(GL_NORMALIZE);
624 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
628 glScalef(1.1, 1.1, 1.1);
632 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
633 glTranslatef((x - 0.5) * 8,
637 gltrackball_rotate (bp->trackball);
639 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
640 glRotatef (x * 360, 1.0, 0.0, 0.0);
641 glRotatef (y * 360, 0.0, 1.0, 0.0);
642 glRotatef (z * 360, 0.0, 0.0, 1.0);
645 glScalef (2.0, 2.0, 2.0);
647 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
648 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
652 GLfloat s = (bp->mode == 1
653 ? bp->mode_tick / (20 / speed)
654 : ((20 / speed) - bp->mode_tick + 1) / (20 / speed));
659 glCallList (bp->object_list);
660 if (bp->mode == 0 && !bp->button_down_p)
661 draw_label (mi); /* print_texture_font can't go inside a display list */
665 if (mi->fps_p) do_fps (mi);
668 glXSwapBuffers(dpy, window);
671 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)