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 free_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;
206 if (width > height * 5) { /* tiny window: show middle */
207 height = width * 9/16;
209 h = height / (GLfloat) width;
212 glViewport (0, y, (GLint) width, (GLint) height);
214 glMatrixMode(GL_PROJECTION);
216 gluPerspective (30.0, 1/h, 1.0, 100.0);
218 glMatrixMode(GL_MODELVIEW);
220 gluLookAt( 0.0, 0.0, 30.0,
224 glClear(GL_COLOR_BUFFER_BIT);
229 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
231 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
233 if (gltrackball_event_handler (event, bp->trackball,
234 MI_WIDTH (mi), MI_HEIGHT (mi),
237 else if (event->xany.type == KeyPress)
241 XLookupString (&event->xkey, &c, 1, &keysym, 0);
244 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
245 bp->change_to = random() % bp->npolyhedra;
246 else if (c == '>' || c == '.' || c == '+' || c == '=' ||
247 keysym == XK_Right || keysym == XK_Up || keysym == XK_Next)
248 bp->change_to = (bp->which + 1) % bp->npolyhedra;
249 else if (c == '<' || c == ',' || c == '-' || c == '_' ||
250 c == '\010' || c == '\177' ||
251 keysym == XK_Left || keysym == XK_Down || keysym == XK_Prior)
252 bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
253 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
256 if (bp->change_to != -1)
259 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
262 bp->change_to = random() % bp->npolyhedra;
271 draw_label (ModeInfo *mi)
273 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
274 polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
277 GLfloat color[4] = { 0.8, 0.8, 0.8, 1 };
278 texture_font_data *f;
280 if (!p || !do_titles) return;
282 strcpy (name2, p->name);
284 sprintf (name2 + strlen(name2), " (%s)", p->class);
287 "Polyhedron %d: \t%s\n\n"
288 "Wythoff Symbol:\t%s\n"
289 "Vertex Configuration:\t%s\n"
290 "Symmetry Group:\t%s\n"
291 /* "Dual of: \t%s\n" */
298 bp->which, name2, p->wythoff, p->config, p->group,
300 p->logical_faces, p->nedges, p->logical_vertices,
301 p->density, (p->chi < 0 ? "" : " "), p->chi);
303 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
305 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
306 f = bp->font2_data; /* small font */
308 f = bp->font3_data; /* tiny font */
311 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
312 print_texture_label (mi->dpy, f,
313 mi->xgwa.width, mi->xgwa.height,
320 tess_error (GLenum errorCode)
322 fprintf (stderr, "%s: tesselation error: %s\n",
323 progname, gluErrorString(errorCode));
326 #endif /* HAVE_TESS */
330 new_polyhedron (ModeInfo *mi)
332 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
334 int wire = MI_IS_WIREFRAME(mi);
337 /* Use the GLU polygon tesselator so that nonconvex faces are displayed
338 correctly (e.g., for the "pentagrammic concave deltohedron").
341 GLUtesselator *tobj = gluNewTess();
342 gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
343 gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
344 gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
345 gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
346 # endif /* HAVE_TESS */
348 mi->polygon_count = 0;
351 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
352 make_random_colormap (0, 0, 0,
353 bp->colors, &bp->ncolors,
354 True, False, 0, False);
356 if (do_which >= bp->npolyhedra)
359 bp->which = (bp->change_to != -1 ? bp->change_to :
360 do_which >= 0 ? do_which :
361 (random() % bp->npolyhedra));
363 p = bp->polyhedra[bp->which];
366 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
368 glNewList (bp->object_list, GL_COMPILE);
369 if (bp->which == bp->npolyhedra-1)
372 bcolor[0] = bp->colors[0].red / 65536.0;
373 bcolor[1] = bp->colors[0].green / 65536.0;
374 bcolor[2] = bp->colors[0].blue / 65536.0;
379 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
381 glScalef (0.8, 0.8, 0.8);
382 p->nfaces = unit_teapot (6, wire);
383 p->nedges = p->nfaces * 3 / 2;
384 p->npoints = p->nfaces * 3;
385 p->logical_faces = p->nfaces;
386 p->logical_vertices = p->npoints;
390 glFrontFace (GL_CCW);
391 for (i = 0; i < p->nfaces; i++)
394 face *f = &p->faces[i];
396 if (f->color > 64 || f->color < 0) abort();
402 bcolor[0] = bp->colors[f->color].red / 65536.0;
403 bcolor[1] = bp->colors[f->color].green / 65536.0;
404 bcolor[2] = bp->colors[f->color].blue / 65536.0;
406 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
409 kludge_normal (f->npoints, f->points, p->points);
412 gluTessBeginPolygon (tobj, 0);
413 gluTessBeginContour (tobj);
414 for (j = 0; j < f->npoints; j++)
416 point *pp = &p->points[f->points[j]];
417 gluTessVertex (tobj, &pp->x, &pp->x);
419 gluTessEndContour (tobj);
420 gluTessEndPolygon (tobj);
421 # else /* !HAVE_TESS */
422 glBegin (wire ? GL_LINE_LOOP :
423 f->npoints == 3 ? GL_TRIANGLES :
424 f->npoints == 4 ? GL_QUADS :
426 for (j = 0; j < f->npoints; j++)
428 point *pp = &p->points[f->points[j]];
429 glVertex3f (pp->x, pp->y, pp->z);
432 # endif /* !HAVE_TESS */
437 mi->polygon_count += p->nfaces;
439 gluDeleteTess (tobj);
445 construct_teapot (ModeInfo *mi)
447 polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
448 int n = bp->npolyhedra-1;
449 polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
451 p->wythoff = strdup("X00398|1984");
452 p->name = strdup("Teapot");
453 p->dual = strdup("");
454 p->config = strdup("Melitta");
455 p->group = strdup("Teapotahedral (Newell[1975])");
456 p->class = strdup("Utah Teapotahedron");
457 bp->polyhedra[n] = p;
462 init_polyhedra (ModeInfo *mi)
464 polyhedra_configuration *bp;
465 int wire = MI_IS_WIREFRAME(mi);
467 # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
468 MI_IS_WIREFRAME(mi) = 0;
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)