/* polyhedra, Copyright (c) 2004-2014 Jamie Zawinski <jwz@jwz.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*
* Renders 160 different 3D solids, and displays some information about each.
* A new solid is chosen every few seconds.
*
* This file contains the OpenGL side; computation of the polyhedra themselves
* is in "polyhedra.c".
*/
#define DEFAULTS "*delay: 30000 \n" \
"*showFPS: False \n" \
"*wireframe: False \n" \
"*titleFont: sans-serif 14\n" \
"*titleFont2: sans-serif 10\n" \
"*titleFont3: sans-serif 8\n" \
"*suppressRotationAnimation: True\n" \
# define release_polyhedra 0
#include "xlockmore.h"
#include "texfont.h"
#include "normals.h"
#include "polyhedra.h"
#include "colors.h"
#include "rotator.h"
#include "gltrackball.h"
#include "teapot.h"
#define DEF_SPIN "True"
#define DEF_WANDER "True"
#define DEF_SPEED "1.0"
#define DEF_TITLES "True"
#define DEF_DURATION "12"
#define DEF_WHICH "random"
#ifndef HAVE_JWXYZ
# define XK_MISCELLANY
# include <X11/keysymdef.h>
#endif
#ifndef HAVE_JWZGLES
# define HAVE_TESS
#endif
#ifdef USE_GL /* whole file */
typedef struct {
GLXContext *glx_context;
rotator *rot;
trackball_state *trackball;
Bool button_down_p;
int npolyhedra;
polyhedron **polyhedra;
int which;
int change_to;
GLuint object_list;
int mode; /* 0 = normal, 1 = out, 2 = in */
int mode_tick;
int ncolors;
XColor *colors;
texture_font_data *font1_data, *font2_data, *font3_data;
time_t last_change_time;
int change_tick;
} polyhedra_configuration;
static polyhedra_configuration *bps = NULL;
static Bool do_spin;
static GLfloat speed;
static Bool do_wander;
static Bool do_titles;
static int duration;
static int do_which;
static char *do_which_str;
static XrmOptionDescRec opts[] = {
{ "-spin", ".spin", XrmoptionNoArg, "True" },
{ "+spin", ".spin", XrmoptionNoArg, "False" },
{ "-speed", ".speed", XrmoptionSepArg, 0 },
{ "-wander", ".wander", XrmoptionNoArg, "True" },
{ "+wander", ".wander", XrmoptionNoArg, "False" },
{ "-titles", ".titles", XrmoptionNoArg, "True" },
{ "+titles", ".titles", XrmoptionNoArg, "False" },
{ "-duration",".duration",XrmoptionSepArg, 0 },
{ "-which", ".which", XrmoptionSepArg, 0 },
};
static argtype vars[] = {
{&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
{&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
{&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
{&speed, "speed", "Speed", DEF_SPEED, t_Float},
{&duration,"duration","Duration",DEF_DURATION,t_Int},
{&do_which_str,"which", "Which", DEF_WHICH, t_String},
};
ENTRYPOINT ModeSpecOpt polyhedra_opts = {countof(opts), opts, countof(vars), vars, NULL};
/* Calculate the normals at each vertex of a face, and use the sum to
decide which normal to assign to the entire face. This also solves
problems caused by nonconvex faces, in most (but not all) cases.
*/
static void
kludge_normal (int n, const int *indices, const point *points)
{
XYZ normal = { 0, 0, 0 };
XYZ p = { 0, 0, 0 };
int i;
for (i = 0; i < n; ++i) {
int i1 = indices[i];
int i2 = indices[(i + 1) % n];
int i3 = indices[(i + 2) % n];
XYZ p1, p2, p3;
p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
p = calc_normal (p1, p2, p3);
normal.x += p.x;
normal.y += p.y;
normal.z += p.z;
}
/*normalize(&normal);*/
if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
glNormal3f (p.x, p.y, p.z);
} else {
glNormal3f (normal.x, normal.y, normal.z);
}
}
static void
load_fonts (ModeInfo *mi)
{
polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
bp->font1_data = load_texture_font (mi->dpy, "titleFont");
bp->font2_data = load_texture_font (mi->dpy, "titleFont2");
bp->font3_data = load_texture_font (mi->dpy, "titleFont3");
}
static void
startup_blurb (ModeInfo *mi)
{
polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
const char *s = "Computing polyhedra...";
texture_font_data *f = bp->font1_data;
glColor3f (0.8, 0.8, 0);
print_texture_label (mi->dpy, f,
mi->xgwa.width, mi->xgwa.height,
0, s);
glFinish();
glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
}
/* Window management, etc
*/
ENTRYPOINT void
reshape_polyhedra (ModeInfo *mi, int width, int height)
{
GLfloat h = (GLfloat) height / (GLfloat) width;
int y = 0;
if (width > height * 5) { /* tiny window: show middle */
height = width * 9/16;
y = -height/2;
h = height / (GLfloat) width;
}
glViewport (0, y, (GLint) width, (GLint) height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective (30.0, 1/h, 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt( 0.0, 0.0, 30.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
}
ENTRYPOINT Bool
polyhedra_handle_event (ModeInfo *mi, XEvent *event)
{
polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
if (gltrackball_event_handler (event, bp->trackball,
MI_WIDTH (mi), MI_HEIGHT (mi),
&bp->button_down_p))
return True;
else if (event->xany.type == KeyPress)
{
KeySym keysym;
char c = 0;
XLookupString (&event->xkey, &c, 1, &keysym, 0);
bp->change_to = -1;
if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
bp->change_to = random() % bp->npolyhedra;
else if (c == '>' || c == '.' || c == '+' || c == '=' ||
keysym == XK_Right || keysym == XK_Up || keysym == XK_Next)
bp->change_to = (bp->which + 1) % bp->npolyhedra;
else if (c == '<' || c == ',' || c == '-' || c == '_' ||
c == '\010' || c == '\177' ||
keysym == XK_Left || keysym == XK_Down || keysym == XK_Prior)
bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
goto DEF;
if (bp->change_to != -1)
return True;
}
else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
{
DEF:
bp->change_to = random() % bp->npolyhedra;
return True;
}
return False;
}
static void
draw_label (ModeInfo *mi)
{
polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
char label[1024];
char name2[255];
GLfloat color[4] = { 0.8, 0.8, 0.8, 1 };
texture_font_data *f;
if (!p || !do_titles) return;
strcpy (name2, p->name);
if (*p->class)
sprintf (name2 + strlen(name2), " (%s)", p->class);
sprintf (label,
"Polyhedron %d: \t%s\n\n"
"Wythoff Symbol:\t%s\n"
"Vertex Configuration:\t%s\n"
"Symmetry Group:\t%s\n"
/* "Dual of: \t%s\n" */
"\n"
"Faces:\t %d\n"
"Edges:\t %d\n"
"Vertices:\t %d\n"
"Density:\t %d\n"
"Euler:\t%s%d\n",
bp->which, name2, p->wythoff, p->config, p->group,
/* p->dual, */
p->logical_faces, p->nedges, p->logical_vertices,
p->density, (p->chi < 0 ? "" : " "), p->chi);
if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
f = bp->font1_data;
else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
f = bp->font2_data; /* small font */
else
f = bp->font3_data; /* tiny font */
glColor4fv (color);
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
print_texture_label (mi->dpy, f,
mi->xgwa.width, mi->xgwa.height,
1, label);
}
#ifdef HAVE_TESS
static void
tess_error (GLenum errorCode)
{
fprintf (stderr, "%s: tesselation error: %s\n",
progname, gluErrorString(errorCode));
abort();
}
#endif /* HAVE_TESS */
static void
new_polyhedron (ModeInfo *mi)
{
polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
polyhedron *p;
int wire = MI_IS_WIREFRAME(mi);
int i;
/* Use the GLU polygon tesselator so that nonconvex faces are displayed
correctly (e.g., for the "pentagrammic concave deltohedron").
*/
# ifdef HAVE_TESS
GLUtesselator *tobj = gluNewTess();
gluTessCallback (tobj, GLU_TESS_BEGIN, (void (*) (void)) &glBegin);
gluTessCallback (tobj, GLU_TESS_END, (void (*) (void)) &glEnd);
gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
gluTessCallback (tobj, GLU_TESS_ERROR, (void (*) (void)) &tess_error);
# endif /* HAVE_TESS */
mi->polygon_count = 0;
bp->ncolors = 128;
bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
make_random_colormap (0, 0, 0,
bp->colors, &bp->ncolors,
True, False, 0, False);
if (do_which >= bp->npolyhedra)
do_which = -1;
bp->which = (bp->change_to != -1 ? bp->change_to :
do_which >= 0 ? do_which :
(random() % bp->npolyhedra));
bp->change_to = -1;
p = bp->polyhedra[bp->which];
if (wire)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glNewList (bp->object_list, GL_COMPILE);
if (bp->which == bp->npolyhedra-1)
{
GLfloat bcolor[4];
bcolor[0] = bp->colors[0].red / 65536.0;
bcolor[1] = bp->colors[0].green / 65536.0;
bcolor[2] = bp->colors[0].blue / 65536.0;
bcolor[3] = 1.0;
if (wire)
glColor3f (0, 1, 0);
else
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
glScalef (0.8, 0.8, 0.8);
p->nfaces = unit_teapot (6, wire);
p->nedges = p->nfaces * 3 / 2;
p->npoints = p->nfaces * 3;
p->logical_faces = p->nfaces;
p->logical_vertices = p->npoints;
}
else
{
glFrontFace (GL_CCW);
for (i = 0; i < p->nfaces; i++)
{
int j;
face *f = &p->faces[i];
if (f->color > 64 || f->color < 0) abort();
if (wire)
glColor3f (0, 1, 0);
else
{
GLfloat bcolor[4];
bcolor[0] = bp->colors[f->color].red / 65536.0;
bcolor[1] = bp->colors[f->color].green / 65536.0;
bcolor[2] = bp->colors[f->color].blue / 65536.0;
bcolor[3] = 1.0;
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
}
kludge_normal (f->npoints, f->points, p->points);
# ifdef HAVE_TESS
gluTessBeginPolygon (tobj, 0);
gluTessBeginContour (tobj);
for (j = 0; j < f->npoints; j++)
{
point *pp = &p->points[f->points[j]];
gluTessVertex (tobj, &pp->x, &pp->x);
}
gluTessEndContour (tobj);
gluTessEndPolygon (tobj);
# else /* !HAVE_TESS */
glBegin (wire ? GL_LINE_LOOP :
f->npoints == 3 ? GL_TRIANGLES :
f->npoints == 4 ? GL_QUADS :
GL_POLYGON);
for (j = 0; j < f->npoints; j++)
{
point *pp = &p->points[f->points[j]];
glVertex3f (pp->x, pp->y, pp->z);
}
glEnd();
# endif /* !HAVE_TESS */
}
}
glEndList ();
mi->polygon_count += p->nfaces;
# ifdef HAVE_TESS
gluDeleteTess (tobj);
# endif
}
static void
construct_teapot (ModeInfo *mi)
{
polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
int n = bp->npolyhedra-1;
polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
p->number = n;
p->wythoff = strdup("X00398|1984");
p->name = strdup("Teapot");
p->dual = strdup("");
p->config = strdup("Melitta");
p->group = strdup("Teapotahedral (Newell[1975])");
p->class = strdup("Utah Teapotahedron");
bp->polyhedra[n] = p;
}
ENTRYPOINT void
init_polyhedra (ModeInfo *mi)
{
polyhedra_configuration *bp;
int wire = MI_IS_WIREFRAME(mi);
# ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
MI_IS_WIREFRAME(mi) = 0;
wire = 0;
# endif
MI_INIT (mi, bps);
bp = &bps[MI_SCREEN(mi)];
bp->glx_context = init_GL(mi);
bp->which = -1;
load_fonts (mi);
startup_blurb (mi);
if (!wire)
{
GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
/* glEnable(GL_CULL_FACE); */
/* We need two-sided lighting for polyhedra where both sides of
a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
*/
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
}
{
double spin_speed = 2.0;
double wander_speed = 0.05;
double spin_accel = 0.2;
bp->rot = make_rotator (do_spin ? spin_speed : 0,
do_spin ? spin_speed : 0,
do_spin ? spin_speed : 0,
spin_accel,
do_wander ? wander_speed : 0,
True);
bp->trackball = gltrackball_init (True);
}
bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
construct_teapot (mi);
bp->object_list = glGenLists (1);
bp->change_to = -1;
{
int x;
char c;
do_which = -1;
if (!strcasecmp (do_which_str, "random"))
;
else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
{
if (x >= 0 && x < bp->npolyhedra)
do_which = x;
else
fprintf (stderr,
"%s: polyhedron %d does not exist: there are only %d.\n",
progname, x, bp->npolyhedra-1);
}
else if (*do_which_str)
{
char *s;
for (s = do_which_str; *s; s++)
if (*s == '-' || *s == '_') *s = ' ';
for (x = 0; x < bp->npolyhedra; x++)
if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
!strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
!strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
!strcasecmp (do_which_str, bp->polyhedra[x]->config))
{
do_which = x;
break;
}
if (do_which < 0)
{
fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
progname, do_which_str);
exit (1);
}
}
}
new_polyhedron (mi);
reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
}
ENTRYPOINT void
draw_polyhedra (ModeInfo *mi)
{
polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
Display *dpy = MI_DISPLAY(mi);
Window window = MI_WINDOW(mi);
static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
GLfloat bshiny = 128.0;
if (!bp->glx_context)
return;
glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
;
else if (bp->mode == 0)
{
if (bp->change_to >= 0)
bp->change_tick = 999, bp->last_change_time = 1;
if (bp->change_tick++ > 10)
{
time_t now = time((time_t *) 0);
if (bp->last_change_time == 0) bp->last_change_time = now;
bp->change_tick = 0;
if (!bp->button_down_p && now - bp->last_change_time >= duration)
{
bp->mode = 1; /* go out */
bp->mode_tick = 20 / speed;
bp->last_change_time = now;
}
}
}
else if (bp->mode == 1) /* out */
{
if (--bp->mode_tick <= 0)
{
new_polyhedron (mi);
bp->mode_tick = 20 / speed;
bp->mode = 2; /* go in */
}
}
else if (bp->mode == 2) /* in */
{
if (--bp->mode_tick <= 0)
bp->mode = 0; /* normal */
}
else
abort();
glShadeModel(GL_FLAT);
glEnable(GL_NORMALIZE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix ();
{
GLfloat s = (MI_WIDTH(mi) < MI_HEIGHT(mi)
? (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi))
: 1);
glScalef (s, s, s);
}
glScalef(1.1, 1.1, 1.1);
{
double x, y, z;
get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
glTranslatef((x - 0.5) * 8,
(y - 0.5) * 8,
(z - 0.5) * 15);
gltrackball_rotate (bp->trackball);
get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
glRotatef (x * 360, 1.0, 0.0, 0.0);
glRotatef (y * 360, 0.0, 1.0, 0.0);
glRotatef (z * 360, 0.0, 0.0, 1.0);
}
glScalef (2.0, 2.0, 2.0);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, bspec);
glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
if (bp->mode != 0)
{
GLfloat s = (bp->mode == 1
? bp->mode_tick / (20 / speed)
: ((20 / speed) - bp->mode_tick + 1) / (20 / speed));
glScalef (s, s, s);
}
glScalef (2, 2, 2);
glCallList (bp->object_list);
if (bp->mode == 0 && !bp->button_down_p)
draw_label (mi); /* print_texture_font can't go inside a display list */
glPopMatrix ();
if (mi->fps_p) do_fps (mi);
glFinish();
glXSwapBuffers(dpy, window);
}
ENTRYPOINT void
free_polyhedra (ModeInfo *mi)
{
polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
int i;
if (!bp->glx_context) return;
glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
for (i = 0; i < bp->npolyhedra; i++)
free_polyhedron (bp->polyhedra[i]);
if (bp->trackball) gltrackball_free (bp->trackball);
if (bp->rot) free_rotator (bp->rot);
if (bp->colors) free (bp->colors);
if (bp->font1_data) free_texture_font (bp->font1_data);
if (bp->font2_data) free_texture_font (bp->font2_data);
if (bp->font3_data) free_texture_font (bp->font3_data);
if (glIsList(bp->object_list)) glDeleteLists(bp->object_list, 1);
}
XSCREENSAVER_MODULE ("Polyhedra", polyhedra)
#endif /* USE_GL */