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;
86 int mode; /* 0 = normal, 1 = out, 2 = in */
92 texture_font_data *font1_data, *font2_data, *font3_data;
94 time_t last_change_time;
97 } polyhedra_configuration;
99 static polyhedra_configuration *bps = NULL;
102 static GLfloat speed;
103 static Bool do_wander;
104 static Bool do_titles;
107 static char *do_which_str;
109 static XrmOptionDescRec opts[] = {
110 { "-spin", ".spin", XrmoptionNoArg, "True" },
111 { "+spin", ".spin", XrmoptionNoArg, "False" },
112 { "-speed", ".speed", XrmoptionSepArg, 0 },
113 { "-wander", ".wander", XrmoptionNoArg, "True" },
114 { "+wander", ".wander", XrmoptionNoArg, "False" },
115 { "-titles", ".titles", XrmoptionNoArg, "True" },
116 { "+titles", ".titles", XrmoptionNoArg, "False" },
117 { "-duration",".duration",XrmoptionSepArg, 0 },
118 { "-which", ".which", XrmoptionSepArg, 0 },
121 static argtype vars[] = {
122 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
123 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
124 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
125 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
126 {&duration,"duration","Duration",DEF_DURATION,t_Int},
127 {&do_which_str,"which", "Which", DEF_WHICH, t_String},
130 ENTRYPOINT ModeSpecOpt polyhedra_opts = {countof(opts), opts, countof(vars), vars, NULL};
134 /* Calculate the normals at each vertex of a face, and use the sum to
135 decide which normal to assign to the entire face. This also solves
136 problems caused by nonconvex faces, in most (but not all) cases.
139 kludge_normal (int n, const int *indices, const point *points)
141 XYZ normal = { 0, 0, 0 };
145 for (i = 0; i < n; ++i) {
147 int i2 = indices[(i + 1) % n];
148 int i3 = indices[(i + 2) % n];
151 p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
152 p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
153 p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
155 p = calc_normal (p1, p2, p3);
161 /*normalize(&normal);*/
162 if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
163 glNormal3f (p.x, p.y, p.z);
165 glNormal3f (normal.x, normal.y, normal.z);
171 load_fonts (ModeInfo *mi)
173 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
174 bp->font1_data = load_texture_font (mi->dpy, "titleFont");
175 bp->font2_data = load_texture_font (mi->dpy, "titleFont2");
176 bp->font3_data = load_texture_font (mi->dpy, "titleFont3");
182 startup_blurb (ModeInfo *mi)
184 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
185 const char *s = "Computing polyhedra...";
186 texture_font_data *f = bp->font1_data;
188 glColor3f (0.8, 0.8, 0);
189 print_gl_string (mi->dpy, bp->font1_data,
190 mi->xgwa.width, mi->xgwa.height,
192 texture_string_width (f, s, 0)
194 mi->xgwa.height - 10,
197 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
202 /* Window management, etc
204 static void new_label (ModeInfo *mi);
207 reshape_polyhedra (ModeInfo *mi, int width, int height)
209 GLfloat h = (GLfloat) height / (GLfloat) width;
211 glViewport (0, 0, (GLint) width, (GLint) height);
213 glMatrixMode(GL_PROJECTION);
215 gluPerspective (30.0, 1/h, 1.0, 100.0);
217 glMatrixMode(GL_MODELVIEW);
219 gluLookAt( 0.0, 0.0, 30.0,
223 glClear(GL_COLOR_BUFFER_BIT);
225 /* need to re-render the text when the window size changes */
231 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
233 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
235 if (gltrackball_event_handler (event, bp->trackball,
236 MI_WIDTH (mi), MI_HEIGHT (mi),
239 else if (event->xany.type == KeyPress)
243 XLookupString (&event->xkey, &c, 1, &keysym, 0);
246 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
247 bp->change_to = random() % bp->npolyhedra;
248 else if (c == '>' || c == '.' || c == '+' || c == '=' ||
249 keysym == XK_Right || keysym == XK_Up || keysym == XK_Next)
250 bp->change_to = (bp->which + 1) % bp->npolyhedra;
251 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
252 c == '\010' || c == '\177' ||
253 keysym == XK_Left || keysym == XK_Down || keysym == XK_Prior)
254 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
255 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
258 if (bp->change_to != -1)
261 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
264 bp->change_to = random() % bp->npolyhedra;
273 new_label (ModeInfo *mi)
275 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
276 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
278 glNewList (bp->title_list, GL_COMPILE);
283 strcpy (name2, p->name);
285 sprintf (name2 + strlen(name2), " (%s)", p->class);
288 "Polyhedron %d: \t%s\n\n"
289 "Wythoff Symbol:\t%s\n"
290 "Vertex Configuration:\t%s\n"
291 "Symmetry Group:\t%s\n"
292 /* "Dual of: \t%s\n" */
299 bp->which, name2, p->wythoff, p->config, p->group,
301 p->logical_faces, p->nedges, p->logical_vertices,
302 p->density, (p->chi < 0 ? "" : " "), p->chi);
305 GLfloat color[4] = { 0.8, 0.8, 0.8, 1 };
306 texture_font_data *f;
307 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
309 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
310 f = bp->font2_data; /* small font */
312 f = bp->font3_data; /* tiny font */
315 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
316 print_gl_string (mi->dpy, f,
317 mi->xgwa.width, mi->xgwa.height,
318 10, mi->xgwa.height - 10,
328 tess_error (GLenum errorCode)
330 fprintf (stderr, "%s: tesselation error: %s\n",
331 progname, gluErrorString(errorCode));
334 #endif /* HAVE_TESS */
338 new_polyhedron (ModeInfo *mi)
340 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
342 int wire = MI_IS_WIREFRAME(mi);
345 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
346 correctly (e.g., for the "pentagrammic concave deltohedron").
349 GLUtesselator *tobj = gluNewTess();
350 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
351 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
352 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
353 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
354 # endif /* HAVE_TESS */
356 mi->polygon_count = 0;
359 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
360 make_random_colormap (0, 0, 0,
361 bp->colors, &bp->ncolors,
362 True, False, 0, False);
364 if (do_which >= bp->npolyhedra)
367 bp->which = (bp->change_to != -1 ? bp->change_to :
368 do_which >= 0 ? do_which :
369 (random() % bp->npolyhedra));
371 p = bp->polyhedra[bp->which];
376 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
378 glNewList (bp->object_list, GL_COMPILE);
379 if (bp->which == bp->npolyhedra-1)
382 bcolor[0] = bp->colors[0].red / 65536.0;
383 bcolor[1] = bp->colors[0].green / 65536.0;
384 bcolor[2] = bp->colors[0].blue / 65536.0;
389 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
391 glScalef (0.8, 0.8, 0.8);
392 p->nfaces = unit_teapot (6, wire);
393 p->nedges = p->nfaces * 3 / 2;
394 p->npoints = p->nfaces * 3;
395 p->logical_faces = p->nfaces;
396 p->logical_vertices = p->npoints;
400 glFrontFace (GL_CCW);
401 for (i = 0; i < p->nfaces; i++)
404 face *f = &p->faces[i];
406 if (f->color > 64 || f->color < 0) abort();
412 bcolor[0] = bp->colors[f->color].red / 65536.0;
413 bcolor[1] = bp->colors[f->color].green / 65536.0;
414 bcolor[2] = bp->colors[f->color].blue / 65536.0;
416 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
419 kludge_normal (f->npoints, f->points, p->points);
422 gluTessBeginPolygon (tobj, 0);
423 gluTessBeginContour (tobj);
424 for (j = 0; j < f->npoints; j++)
426 point *pp = &p->points[f->points[j]];
427 gluTessVertex (tobj, &pp->x, &pp->x);
429 gluTessEndContour (tobj);
430 gluTessEndPolygon (tobj);
431 # else /* !HAVE_TESS */
432 glBegin (wire ? GL_LINE_LOOP :
433 f->npoints == 3 ? GL_TRIANGLES :
434 f->npoints == 4 ? GL_QUADS :
436 for (j = 0; j < f->npoints; j++)
438 point *pp = &p->points[f->points[j]];
439 glVertex3f (pp->x, pp->y, pp->z);
442 # endif /* !HAVE_TESS */
447 mi->polygon_count += p->nfaces;
449 gluDeleteTess (tobj);
455 construct_teapot (ModeInfo *mi)
457 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
458 int n = bp->npolyhedra-1;
459 polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
461 p->wythoff = strdup("X00398|1984");
462 p->name = strdup("Teapot");
463 p->dual = strdup("");
464 p->config = strdup("Melitta");
465 p->group = strdup("Teapotahedral (Newell[1975])");
466 p->class = strdup("Utah Teapotahedron");
467 bp->polyhedra[n] = p;
472 init_polyhedra (ModeInfo *mi)
474 polyhedra_configuration *bp;
475 int wire = MI_IS_WIREFRAME(mi);
477 # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
478 MI_IS_WIREFRAME(mi) = 0;
483 bps = (polyhedra_configuration *)
484 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
486 fprintf(stderr, "%s: out of memory\n", progname);
491 bp = &bps[MI_SCREEN(mi)];
493 bp->glx_context = init_GL(mi);
501 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
502 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
503 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
504 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
506 glEnable(GL_LIGHTING);
508 glEnable(GL_DEPTH_TEST);
509 /* glEnable(GL_CULL_FACE); */
511 /* We need two-sided lighting for polyhedra where both sides of
512 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
514 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
516 glLightfv(GL_LIGHT0, GL_POSITION, pos);
517 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
518 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
519 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
523 double spin_speed = 2.0;
524 double wander_speed = 0.05;
525 double spin_accel = 0.2;
527 bp->rot = make_rotator (do_spin ? spin_speed : 0,
528 do_spin ? spin_speed : 0,
529 do_spin ? spin_speed : 0,
531 do_wander ? wander_speed : 0,
533 bp->trackball = gltrackball_init (True);
536 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
537 construct_teapot (mi);
539 bp->object_list = glGenLists (1);
540 bp->title_list = glGenLists (1);
547 if (!strcasecmp (do_which_str, "random"))
549 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
551 if (x >= 0 && x < bp->npolyhedra)
555 "%s: polyhedron %d does not exist: there are only %d.\n",
556 progname, x, bp->npolyhedra-1);
558 else if (*do_which_str)
561 for (s = do_which_str; *s; s++)
562 if (*s == '-' || *s == '_') *s = ' ';
564 for (x = 0; x < bp->npolyhedra; x++)
565 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
566 !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
567 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
568 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
575 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
576 progname, do_which_str);
583 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
584 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
590 draw_polyhedra (ModeInfo *mi)
592 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
593 Display *dpy = MI_DISPLAY(mi);
594 Window window = MI_WINDOW(mi);
596 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
597 GLfloat bshiny = 128.0;
599 if (!bp->glx_context)
602 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
604 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
606 else if (bp->mode == 0)
608 if (bp->change_to >= 0)
609 bp->change_tick = 999, bp->last_change_time = 1;
610 if (bp->change_tick++ > 10)
612 time_t now = time((time_t *) 0);
613 if (bp->last_change_time == 0) bp->last_change_time = now;
615 if (!bp->button_down_p && now - bp->last_change_time >= duration)
617 bp->mode = 1; /* go out */
618 bp->mode_tick = 20 * speed;
619 bp->last_change_time = now;
623 else if (bp->mode == 1) /* out */
625 if (--bp->mode_tick <= 0)
628 bp->mode_tick = 20 * speed;
629 bp->mode = 2; /* go in */
632 else if (bp->mode == 2) /* in */
634 if (--bp->mode_tick <= 0)
635 bp->mode = 0; /* normal */
640 glShadeModel(GL_FLAT);
641 glEnable(GL_NORMALIZE);
643 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
647 glScalef(1.1, 1.1, 1.1);
651 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
652 glTranslatef((x - 0.5) * 8,
656 gltrackball_rotate (bp->trackball);
658 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
659 glRotatef (x * 360, 1.0, 0.0, 0.0);
660 glRotatef (y * 360, 0.0, 1.0, 0.0);
661 glRotatef (z * 360, 0.0, 0.0, 1.0);
664 glScalef (2.0, 2.0, 2.0);
666 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
667 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
671 GLfloat s = (bp->mode == 1
672 ? bp->mode_tick / (20 * speed)
673 : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
678 glCallList (bp->object_list);
679 if (bp->mode == 0 && !bp->button_down_p)
680 glCallList (bp->title_list);
684 if (mi->fps_p) do_fps (mi);
687 glXSwapBuffers(dpy, window);
690 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)