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" \
24 "*suppressRotationAnimation: True\n" \
27 # define refresh_polyhedra 0
28 # define release_polyhedra 0
30 #define countof(x) (sizeof((x))/sizeof((*x)))
32 #include "xlockmore.h"
37 # include <X11/Xlib.h>
44 #endif /* HAVE_JWZGLES */
46 #define DEF_SPIN "True"
47 #define DEF_WANDER "True"
48 #define DEF_SPEED "1.0"
49 #define DEF_TITLES "True"
50 #define DEF_DURATION "12"
51 #define DEF_WHICH "random"
55 #include "polyhedra.h"
58 #include "gltrackball.h"
62 # define XK_MISCELLANY
63 # include <X11/keysymdef.h>
71 #ifdef USE_GL /* whole file */
74 GLXContext *glx_context;
76 trackball_state *trackball;
80 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_texture_label (mi->dpy, f,
190 mi->xgwa.width, mi->xgwa.height,
193 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
198 /* Window management, etc
201 reshape_polyhedra (ModeInfo *mi, int width, int height)
203 GLfloat h = (GLfloat) height / (GLfloat) width;
205 glViewport (0, 0, (GLint) width, (GLint) height);
207 glMatrixMode(GL_PROJECTION);
209 gluPerspective (30.0, 1/h, 1.0, 100.0);
211 glMatrixMode(GL_MODELVIEW);
213 gluLookAt( 0.0, 0.0, 30.0,
217 glClear(GL_COLOR_BUFFER_BIT);
222 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
224 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
226 if (gltrackball_event_handler (event, bp->trackball,
227 MI_WIDTH (mi), MI_HEIGHT (mi),
230 else if (event->xany.type == KeyPress)
234 XLookupString (&event->xkey, &c, 1, &keysym, 0);
237 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
238 bp->change_to = random() % bp->npolyhedra;
239 else if (c == '>' || c == '.' || c == '+' || c == '=' ||
240 keysym == XK_Right || keysym == XK_Up || keysym == XK_Next)
241 bp->change_to = (bp->which + 1) % bp->npolyhedra;
242 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
243 c == '\010' || c == '\177' ||
244 keysym == XK_Left || keysym == XK_Down || keysym == XK_Prior)
245 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
246 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
249 if (bp->change_to != -1)
252 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
255 bp->change_to = random() % bp->npolyhedra;
264 draw_label (ModeInfo *mi)
266 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
267 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
270 GLfloat color[4] = { 0.8, 0.8, 0.8, 1 };
271 texture_font_data *f;
273 if (!p || !do_titles) return;
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);
296 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
298 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
299 f = bp->font2_data; /* small font */
301 f = bp->font3_data; /* tiny font */
304 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
305 print_texture_label (mi->dpy, f,
306 mi->xgwa.width, mi->xgwa.height,
313 tess_error (GLenum errorCode)
315 fprintf (stderr, "%s: tesselation error: %s\n",
316 progname, gluErrorString(errorCode));
319 #endif /* HAVE_TESS */
323 new_polyhedron (ModeInfo *mi)
325 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
327 int wire = MI_IS_WIREFRAME(mi);
330 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
331 correctly (e.g., for the "pentagrammic concave deltohedron").
334 GLUtesselator *tobj = gluNewTess();
335 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
336 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
337 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
338 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
339 # endif /* HAVE_TESS */
341 mi->polygon_count = 0;
344 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
345 make_random_colormap (0, 0, 0,
346 bp->colors, &bp->ncolors,
347 True, False, 0, False);
349 if (do_which >= bp->npolyhedra)
352 bp->which = (bp->change_to != -1 ? bp->change_to :
353 do_which >= 0 ? do_which :
354 (random() % bp->npolyhedra));
356 p = bp->polyhedra[bp->which];
359 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
361 glNewList (bp->object_list, GL_COMPILE);
362 if (bp->which == bp->npolyhedra-1)
365 bcolor[0] = bp->colors[0].red / 65536.0;
366 bcolor[1] = bp->colors[0].green / 65536.0;
367 bcolor[2] = bp->colors[0].blue / 65536.0;
372 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
374 glScalef (0.8, 0.8, 0.8);
375 p->nfaces = unit_teapot (6, wire);
376 p->nedges = p->nfaces * 3 / 2;
377 p->npoints = p->nfaces * 3;
378 p->logical_faces = p->nfaces;
379 p->logical_vertices = p->npoints;
383 glFrontFace (GL_CCW);
384 for (i = 0; i < p->nfaces; i++)
387 face *f = &p->faces[i];
389 if (f->color > 64 || f->color < 0) abort();
395 bcolor[0] = bp->colors[f->color].red / 65536.0;
396 bcolor[1] = bp->colors[f->color].green / 65536.0;
397 bcolor[2] = bp->colors[f->color].blue / 65536.0;
399 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
402 kludge_normal (f->npoints, f->points, p->points);
405 gluTessBeginPolygon (tobj, 0);
406 gluTessBeginContour (tobj);
407 for (j = 0; j < f->npoints; j++)
409 point *pp = &p->points[f->points[j]];
410 gluTessVertex (tobj, &pp->x, &pp->x);
412 gluTessEndContour (tobj);
413 gluTessEndPolygon (tobj);
414 # else /* !HAVE_TESS */
415 glBegin (wire ? GL_LINE_LOOP :
416 f->npoints == 3 ? GL_TRIANGLES :
417 f->npoints == 4 ? GL_QUADS :
419 for (j = 0; j < f->npoints; j++)
421 point *pp = &p->points[f->points[j]];
422 glVertex3f (pp->x, pp->y, pp->z);
425 # endif /* !HAVE_TESS */
430 mi->polygon_count += p->nfaces;
432 gluDeleteTess (tobj);
438 construct_teapot (ModeInfo *mi)
440 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
441 int n = bp->npolyhedra-1;
442 polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
444 p->wythoff = strdup("X00398|1984");
445 p->name = strdup("Teapot");
446 p->dual = strdup("");
447 p->config = strdup("Melitta");
448 p->group = strdup("Teapotahedral (Newell[1975])");
449 p->class = strdup("Utah Teapotahedron");
450 bp->polyhedra[n] = p;
455 init_polyhedra (ModeInfo *mi)
457 polyhedra_configuration *bp;
458 int wire = MI_IS_WIREFRAME(mi);
460 # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
461 MI_IS_WIREFRAME(mi) = 0;
466 bps = (polyhedra_configuration *)
467 calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
469 fprintf(stderr, "%s: out of memory\n", progname);
474 bp = &bps[MI_SCREEN(mi)];
476 bp->glx_context = init_GL(mi);
484 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
485 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
486 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
487 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
489 glEnable(GL_LIGHTING);
491 glEnable(GL_DEPTH_TEST);
492 /* glEnable(GL_CULL_FACE); */
494 /* We need two-sided lighting for polyhedra where both sides of
495 a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
497 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
499 glLightfv(GL_LIGHT0, GL_POSITION, pos);
500 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
501 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
502 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
506 double spin_speed = 2.0;
507 double wander_speed = 0.05;
508 double spin_accel = 0.2;
510 bp->rot = make_rotator (do_spin ? spin_speed : 0,
511 do_spin ? spin_speed : 0,
512 do_spin ? spin_speed : 0,
514 do_wander ? wander_speed : 0,
516 bp->trackball = gltrackball_init (True);
519 bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
520 construct_teapot (mi);
522 bp->object_list = glGenLists (1);
529 if (!strcasecmp (do_which_str, "random"))
531 else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
533 if (x >= 0 && x < bp->npolyhedra)
537 "%s: polyhedron %d does not exist: there are only %d.\n",
538 progname, x, bp->npolyhedra-1);
540 else if (*do_which_str)
543 for (s = do_which_str; *s; s++)
544 if (*s == '-' || *s == '_') *s = ' ';
546 for (x = 0; x < bp->npolyhedra; x++)
547 if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
548 !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
549 !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
550 !strcasecmp (do_which_str, bp->polyhedra[x]->config))
557 fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
558 progname, do_which_str);
565 reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
566 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
572 draw_polyhedra (ModeInfo *mi)
574 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
575 Display *dpy = MI_DISPLAY(mi);
576 Window window = MI_WINDOW(mi);
578 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
579 GLfloat bshiny = 128.0;
581 if (!bp->glx_context)
584 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
586 if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
588 else if (bp->mode == 0)
590 if (bp->change_to >= 0)
591 bp->change_tick = 999, bp->last_change_time = 1;
592 if (bp->change_tick++ > 10)
594 time_t now = time((time_t *) 0);
595 if (bp->last_change_time == 0) bp->last_change_time = now;
597 if (!bp->button_down_p && now - bp->last_change_time >= duration)
599 bp->mode = 1; /* go out */
600 bp->mode_tick = 20 / speed;
601 bp->last_change_time = now;
605 else if (bp->mode == 1) /* out */
607 if (--bp->mode_tick <= 0)
610 bp->mode_tick = 20 / speed;
611 bp->mode = 2; /* go in */
614 else if (bp->mode == 2) /* in */
616 if (--bp->mode_tick <= 0)
617 bp->mode = 0; /* normal */
622 glShadeModel(GL_FLAT);
623 glEnable(GL_NORMALIZE);
625 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
629 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
631 GLfloat h = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
632 int o = (int) current_device_rotation();
633 if (o != 0 && o != 180 && o != -180)
634 glScalef (1/h, 1/h, 1/h);
638 glScalef(1.1, 1.1, 1.1);
642 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
643 glTranslatef((x - 0.5) * 8,
647 gltrackball_rotate (bp->trackball);
649 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
650 glRotatef (x * 360, 1.0, 0.0, 0.0);
651 glRotatef (y * 360, 0.0, 1.0, 0.0);
652 glRotatef (z * 360, 0.0, 0.0, 1.0);
655 glScalef (2.0, 2.0, 2.0);
657 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
658 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
662 GLfloat s = (bp->mode == 1
663 ? bp->mode_tick / (20 / speed)
664 : ((20 / speed) - bp->mode_tick + 1) / (20 / speed));
669 glCallList (bp->object_list);
670 if (bp->mode == 0 && !bp->button_down_p)
671 draw_label (mi); /* print_texture_font can't go inside a display list */
675 if (mi->fps_p) do_fps (mi);
678 glXSwapBuffers(dpy, window);
681 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)