summaryrefslogtreecommitdiffstats
path: root/hacks/glx/jigsaw.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/glx/jigsaw.c')
-rw-r--r--hacks/glx/jigsaw.c1509
1 files changed, 1509 insertions, 0 deletions
diff --git a/hacks/glx/jigsaw.c b/hacks/glx/jigsaw.c
new file mode 100644
index 0000000..934dbcc
--- /dev/null
+++ b/hacks/glx/jigsaw.c
@@ -0,0 +1,1509 @@
+/* xscreensaver, Copyright (c) 1997-2017 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.
+ *
+ * Written as an Xlib program some time in 1997.
+ * Rewritten as an OpenGL program 24-Aug-2008.
+ */
+
+/*
+ Currently, we do this:
+
+ - start pieces off screen and move them in.
+ - when they land, they fill the puzzle grid with a shuffled
+ puzzle (pieces are rotated, too).
+ - swap random pairs of pieces until the puzzle is solved.
+ - scatter the pieces off screen (resulting in black).
+ - load new image and repeat.
+
+ Another idea would be to show the puzzle being solved the way
+ a person would do it:
+
+ - start off with black screen.
+ - assume knowledge of size of grid (position of corners).
+ - find a corner piece, and place it.
+ - while puzzle unsolved:
+ - pick a random piece;
+ - if it is the correct piece for any open edge, place it;
+ - if it fits physically in any rotation at any open edge,
+ place it, then toss it back (show the fake-out).
+ - if it doesn't fit at all, don't animate it at all.
+
+ This would take a long time to solve, I think...
+
+ An even harder idea would involve building up completed "clumps"
+ and sliding them around (a coral growth / accretion approach)
+ */
+
+#define DEF_SPEED "1.0"
+#define DEF_COMPLEXITY "1.0"
+#define DEF_RESOLUTION "100"
+#define DEF_THICKNESS "0.06"
+#define DEF_WOBBLE "True"
+#define DEF_DEBUG "False"
+
+#define DEF_FONT "-*-helvetica-bold-r-normal-*-*-240-*-*-*-*-*-*"
+#define DEFAULTS "*delay: 20000 \n" \
+ "*showFPS: False \n" \
+ "*font: " DEF_FONT"\n" \
+ "*wireframe: False \n" \
+ "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \
+ "*grabDesktopImages: False \n" \
+ "*chooseRandomImages: True \n" \
+ "*suppressRotationAnimation: True\n" \
+
+
+# define free_jigsaw 0
+# define release_jigsaw 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+#else
+# include <X11/Xlib.h>
+# include <GL/gl.h>
+# include <GL/glu.h>
+#endif
+
+#include "xlockmore.h"
+#include "rotator.h"
+#include "gltrackball.h"
+#include "spline.h"
+#include "normals.h"
+#include "grab-ximage.h"
+#include "texfont.h"
+
+#ifdef HAVE_JWZGLES
+# include "jwzgles.h"
+#else /* !HAVE_JWZGLES */
+# define HAVE_TESS
+#endif /* !HAVE_JWZGLES */
+
+#undef BELLRAND
+#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
+
+#ifdef USE_GL /* whole file */
+
+#define TOP 0
+#define RIGHT 1
+#define BOTTOM 2
+#define LEFT 3
+
+#define IN -1
+#define FLAT 0
+#define OUT 1
+
+typedef struct jigsaw_configuration jigsaw_configuration;
+
+typedef struct {
+ double x,y,z,r; /* position and Z rotation (in degrees) */
+} XYZR;
+
+
+typedef struct {
+ jigsaw_configuration *jc;
+ int edge[4];
+ GLuint dlist;
+ int polys;
+
+ XYZR home; /* correct position in puzzle */
+ XYZR current; /* where it is right now */
+ XYZR from, to; /* in transit from A to B */
+ double tick; /* 0-1.0, how far from A to B */
+ double arc_height; /* height of apex of curved path from A to B */
+ double tilt, max_tilt;
+
+} puzzle_piece;
+
+
+struct jigsaw_configuration {
+ GLXContext *glx_context;
+ trackball_state *trackball;
+ rotator *rot;
+ Bool button_down_p;
+ texture_font_data *texfont;
+ GLuint loading_dlist;
+
+ int puzzle_width;
+ int puzzle_height;
+ puzzle_piece *puzzle;
+
+ enum { PUZZLE_LOADING_MSG,
+ PUZZLE_LOADING,
+ PUZZLE_UNSCATTER,
+ PUZZLE_SOLVE,
+ PUZZLE_SCATTER } state;
+ double pausing;
+ double tick_speed;
+
+ GLuint texid;
+ GLfloat tex_x, tex_y, tex_width, tex_height, aspect;
+
+ GLuint line_thickness;
+};
+
+static jigsaw_configuration *sps = NULL;
+
+static GLfloat speed;
+static GLfloat complexity_arg;
+static int resolution_arg;
+static GLfloat thickness_arg;
+static Bool wobble_p;
+static Bool debug_p;
+
+static XrmOptionDescRec opts[] = {
+ { "-speed", ".speed", XrmoptionSepArg, 0 },
+ { "-complexity", ".complexity", XrmoptionSepArg, 0 },
+ { "-resolution", ".resolution", XrmoptionSepArg, 0 },
+ { "-thickness", ".thickness", XrmoptionSepArg, 0 },
+ { "-wobble", ".wobble", XrmoptionNoArg, "True" },
+ { "+wobble", ".wobble", XrmoptionNoArg, "False" },
+ { "-debug", ".debug", XrmoptionNoArg, "True" },
+};
+
+static argtype vars[] = {
+ {&speed, "speed", "Speed", DEF_SPEED, t_Float},
+ {&complexity_arg, "complexity", "Complexity", DEF_COMPLEXITY, t_Float},
+ {&resolution_arg, "resolution", "Resolution", DEF_RESOLUTION, t_Int},
+ {&thickness_arg, "thickness", "Thickness", DEF_THICKNESS, t_Float},
+ {&wobble_p, "wobble", "Wobble", DEF_WOBBLE, t_Bool},
+ {&debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
+};
+
+ENTRYPOINT ModeSpecOpt jigsaw_opts = {countof(opts), opts, countof(vars), vars, NULL};
+
+
+/* Returns a spline describing one edge of a puzzle piece of the given length.
+ */
+static spline *
+make_puzzle_curve (int pixels)
+{
+ double x0 = 0.0000, y0 = 0.0000;
+ double x1 = 0.3333, y1 = 0.1000;
+ double x2 = 0.4333, y2 = 0.0333;
+ double x3 = 0.4666, y3 = -0.0666;
+ double x4 = 0.3333, y4 = -0.1666;
+ double x5 = 0.3666, y5 = -0.2900;
+ double x6 = 0.5000, y6 = -0.3333;
+
+ spline *s = make_spline(20);
+ s->n_controls = 0;
+
+# define PT(x,y) \
+ s->control_x[s->n_controls] = pixels * (x); \
+ s->control_y[s->n_controls] = pixels * (y); \
+ s->n_controls++
+ PT ( x0, y0);
+ PT ( x1, y1);
+ PT ( x2, y2);
+ PT ( x3, y3);
+ PT ( x4, y4);
+ PT ( x5, y5);
+ PT ( x6, y6);
+ PT (1-x5, y5);
+ PT (1-x4, y4);
+ PT (1-x3, y3);
+ PT (1-x2, y2);
+ PT (1-x1, y1);
+ PT (1-x0, y0);
+# undef PT
+
+ compute_spline (s);
+ return s;
+}
+
+
+#ifdef HAVE_TESS
+
+static void
+tess_error_cb (GLenum errorCode)
+{
+ fprintf (stderr, "%s: tesselation error: %s\n",
+ progname, gluErrorString(errorCode));
+ exit (0);
+}
+
+
+static void
+tess_combine_cb (GLdouble coords[3], GLdouble *d[4], GLfloat w[4],
+ GLdouble **dataOut)
+{
+ GLdouble *new = (GLdouble *) malloc (3 * sizeof(*new));
+ new[0] = coords[0];
+ new[1] = coords[1];
+ new[2] = coords[2];
+ *dataOut = new;
+}
+
+
+static void
+tess_vertex_cb (void *vertex_data, void *closure)
+{
+ puzzle_piece *p = (puzzle_piece *) closure;
+ GLdouble *v = (GLdouble *) vertex_data;
+ GLdouble x = v[0];
+ GLdouble y = v[1];
+ GLdouble z = v[2];
+
+ if (p)
+ {
+ GLfloat pw = p->jc->puzzle_width;
+ GLfloat ph = p->jc->puzzle_height;
+
+ GLfloat xx = x / (GLfloat) resolution_arg; /* 0-1 from piece origin */
+ GLfloat yy = y / (GLfloat) resolution_arg;
+ GLdouble tx = (p->home.x + xx) / pw; /* 0-1 from puzzle origin */
+ GLdouble ty = (ph - p->home.y - yy) / ph;
+
+ tx = p->jc->tex_x + (tx * p->jc->tex_width);
+ ty = p->jc->tex_y + (ty * p->jc->tex_height);
+
+ glTexCoord2d (tx, ty);
+ }
+
+ glVertex3d (x, y, z);
+}
+
+#else /* HAVE_TESS */
+
+/* Writes triangles into the array of floats.
+ Returns the number of floats written (triangles * 9).
+ */
+static int
+make_piece_eighth (jigsaw_configuration *jc, const spline *s,
+ int resolution, int type, GLfloat *out,
+ Bool flip_x, Bool flip_y, Bool rotate_p)
+{
+ GLfloat *oout = out;
+ int cx = resolution/2;
+ int cy = resolution/2;
+ int np = (s->n_points / 2) + 1;
+ int last_x = -999999, last_y = -999999;
+ Bool inflected = False;
+ int i;
+
+ if (type == FLAT)
+ {
+ *out++ = cx;
+ *out++ = 0;
+ *out++ = 0;
+
+ *out++ = cx;
+ *out++ = cy;
+ *out++ = 0;
+
+ *out++ = 0;
+ *out++ = 0;
+ *out++ = 0;
+
+ goto END;
+ }
+
+ for (i = (type == IN ? np-1 : 0);
+ (type == IN ? i >= 0 : i < np);
+ i += (type == IN ? -1 : 1))
+ {
+ int x = s->points[i].x;
+ int y = s->points[i].y;
+
+ if (type == IN)
+ y = -y;
+
+ if (last_x != -999999)
+ {
+ if (!inflected &&
+ (type == IN
+ ? x >= last_x
+ : x < last_x))
+ {
+ inflected = True;
+
+ *out++ = cx;
+ *out++ = cy;
+ *out++ = 0;
+
+ *out++ = last_x;
+ *out++ = last_y;
+ *out++ = 0;
+
+ if (type == IN)
+ {
+ cx = 0;
+ cy = 0;
+ }
+ else
+ {
+ cy = y;
+ }
+
+ *out++ = cx;
+ *out++ = cy;
+ *out++ = 0;
+ }
+
+ *out++ = cx;
+ *out++ = cy;
+ *out++ = 0;
+
+ *out++ = last_x;
+ *out++ = last_y;
+ *out++ = 0;
+
+ *out++ = x;
+ *out++ = y;
+ *out++ = 0;
+ }
+
+ last_x = x;
+ last_y = y;
+ }
+ END:
+
+ {
+ int count = out - oout;
+ Bool cw_p;
+
+ if (flip_x)
+ for (i = 0; i < count; i += 3)
+ oout[i] = resolution - oout[i];
+
+ if (flip_y)
+ for (i = 0; i < count; i += 3)
+ oout[i+1] = resolution - oout[i+1];
+
+ cw_p = (type == IN);
+ if (flip_x) cw_p = !cw_p;
+ if (flip_y) cw_p = !cw_p;
+
+ if (cw_p)
+ for (i = 0; i < count; i += 9)
+ {
+ GLfloat x1 = oout[i+0];
+ GLfloat y1 = oout[i+1];
+ GLfloat x2 = oout[i+3];
+ GLfloat y2 = oout[i+4];
+ GLfloat x3 = oout[i+6];
+ GLfloat y3 = oout[i+7];
+ oout[i+0] = x2;
+ oout[i+1] = y2;
+ oout[i+3] = x1;
+ oout[i+4] = y1;
+ oout[i+6] = x3;
+ oout[i+7] = y3;
+ }
+
+ if (rotate_p)
+ for (i = 0; i < count; i += 3)
+ {
+ GLfloat x = oout[i];
+ GLfloat y = oout[i+1];
+ oout[i] = resolution - y;
+ oout[i+1] = x;
+ }
+
+ return count;
+ }
+}
+
+#endif /* !HAVE_TESS */
+
+
+
+/* Draws a puzzle piece. The top/right/bottom/left_type args
+ indicate the direction the tabs point: 1 for out, -1 for in, 0 for flat.
+ */
+static int
+draw_piece (jigsaw_configuration *jc, puzzle_piece *p,
+ int resolution, GLfloat thickness,
+ int top_type, int right_type,
+ int bottom_type, int left_type,
+ Bool wire)
+{
+ spline *s = make_puzzle_curve (resolution);
+ GLdouble *pts = (GLdouble *) malloc (s->n_points * 4 * 3 * sizeof(*pts));
+ int polys = 0;
+ int i, o;
+ GLdouble z = resolution * thickness;
+
+ o = 0;
+ if (top_type == 0) {
+ pts[o++] = 0;
+ pts[o++] = 0;
+ pts[o++] = z;
+
+ pts[o++] = resolution;
+ pts[o++] = 0;
+ pts[o++] = z;
+ } else {
+ for (i = 0; i < s->n_points; i++) {
+ pts[o++] = s->points[i].x;
+ pts[o++] = s->points[i].y * top_type;
+ pts[o++] = z;
+ }
+ }
+
+ if (right_type == 0) {
+ pts[o++] = resolution;
+ pts[o++] = resolution;
+ pts[o++] = z;
+ } else {
+ for (i = 1; i < s->n_points; i++) {
+ pts[o++] = resolution + s->points[i].y * (-right_type);
+ pts[o++] = s->points[i].x;
+ pts[o++] = z;
+ }
+ }
+
+ if (bottom_type == 0) {
+ pts[o++] = 0;
+ pts[o++] = resolution;
+ pts[o++] = z;
+ } else {
+ for (i = 1; i < s->n_points; i++) {
+ pts[o++] = s->points[s->n_points-i-1].x;
+ pts[o++] = resolution + s->points[s->n_points-i-1].y * (-bottom_type);
+ pts[o++] = z;
+ }
+ }
+
+ if (left_type == 0) {
+ pts[o++] = 0;
+ pts[o++] = 0;
+ pts[o++] = z;
+ } else {
+ for (i = 1; i < s->n_points; i++) {
+ pts[o++] = s->points[s->n_points-i-1].y * left_type;
+ pts[o++] = s->points[s->n_points-i-1].x;
+ pts[o++] = z;
+ }
+ }
+
+ { GLfloat ss = 1.0 / resolution; glScalef (ss, ss, ss); }
+
+# ifndef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
+ glPolygonMode (GL_FRONT_AND_BACK, wire ? GL_LINE : GL_FILL);
+# endif
+
+ if (wire)
+ {
+ glDisable (GL_TEXTURE_2D);
+ glDisable (GL_BLEND);
+ glDisable (GL_LIGHTING);
+ }
+ else
+ {
+# ifdef HAVE_TESS
+
+# ifndef _GLUfuncptr
+# define _GLUfuncptr void(*)(void)
+# endif
+ GLUtesselator *tess = gluNewTess();
+ gluTessCallback(tess, GLU_TESS_BEGIN, (_GLUfuncptr)glBegin);
+ gluTessCallback(tess, GLU_TESS_VERTEX_DATA,(_GLUfuncptr)tess_vertex_cb);
+ gluTessCallback(tess, GLU_TESS_END, (_GLUfuncptr)glEnd);
+ gluTessCallback(tess, GLU_TESS_COMBINE, (_GLUfuncptr)tess_combine_cb);
+ gluTessCallback(tess, GLU_TESS_ERROR, (_GLUfuncptr)tess_error_cb);
+
+ /* front face */
+ glEnable (GL_TEXTURE_2D);
+ glEnable (GL_BLEND);
+ glEnable (GL_LIGHTING);
+ glBindTexture(GL_TEXTURE_2D, jc->texid);
+ glFrontFace (GL_CCW);
+ glNormal3f (0, 0, 1);
+ gluTessBeginPolygon (tess, p);
+ gluTessBeginContour (tess);
+ for (i = 0; i < o; i += 3)
+ {
+ GLdouble *p = pts + i;
+ gluTessVertex (tess, p, p);
+ polys++; /* not quite right but close */
+ }
+ gluTessEndContour(tess);
+ gluTessEndPolygon(tess);
+
+ /* back face */
+ glDisable (GL_TEXTURE_2D);
+ glFrontFace (GL_CW);
+ glNormal3f (0, 0, -1);
+ gluTessBeginPolygon (tess, 0);
+ gluTessBeginContour (tess);
+ for (i = 0; i < o; i += 3)
+ {
+ GLdouble *p = pts + i;
+ p[2] = -p[2];
+ gluTessVertex (tess, p, p);
+ polys++; /* not quite right but close */
+ }
+ gluTessEndContour(tess);
+ gluTessEndPolygon(tess);
+ gluDeleteTess(tess);
+
+ /* Put it back */
+ for (i = 0; i < o; i += 3)
+ {
+ GLdouble *p = pts + i;
+ p[2] = -p[2];
+ }
+
+# else /* !HAVE_TESS */
+
+ GLfloat *tri = (GLfloat *)
+ (GLfloat *) malloc (s->n_points * 4 * 3 * 3 * sizeof(*pts));
+ GLfloat *otri = tri;
+ int count;
+ GLdouble zz;
+
+ tri += make_piece_eighth (jc, s, resolution, top_type, tri, 0, 0, 0);
+ tri += make_piece_eighth (jc, s, resolution, top_type, tri, 1, 0, 0);
+ tri += make_piece_eighth (jc, s, resolution, left_type, tri, 0, 1, 1);
+ tri += make_piece_eighth (jc, s, resolution, left_type, tri, 1, 1, 1);
+ tri += make_piece_eighth (jc, s, resolution, bottom_type, tri, 0, 1, 0);
+ tri += make_piece_eighth (jc, s, resolution, bottom_type, tri, 1, 1, 0);
+ tri += make_piece_eighth (jc, s, resolution, right_type, tri, 0, 0, 1);
+ tri += make_piece_eighth (jc, s, resolution, right_type, tri, 1, 0, 1);
+ count = (tri - otri) / 9;
+
+ if (! wire)
+ {
+ glEnable (GL_TEXTURE_2D);
+ glEnable (GL_BLEND);
+ glEnable (GL_LIGHTING);
+ glBindTexture(GL_TEXTURE_2D, jc->texid);
+ }
+
+ for (zz = z; zz >= -z; zz -= 2*z)
+ {
+ int i;
+ glFrontFace (zz > 0 ? GL_CCW : GL_CW);
+ glNormal3f (0, 0, (zz > 0 ? 1 : -1));
+
+ if (zz < 0)
+ glDisable (GL_TEXTURE_2D); /* back face */
+
+ glPushMatrix();
+ glTranslatef (0, 0, zz);
+
+ tri = otri;
+ if (wire)
+ {
+ for (i = 0; i < count; i++)
+ {
+ glBegin (GL_LINE_LOOP);
+ glVertex3f (tri[0], tri[1], tri[2]); tri += 3;
+ glVertex3f (tri[0], tri[1], tri[2]); tri += 3;
+ glVertex3f (tri[0], tri[1], tri[2]); tri += 3;
+ glEnd();
+ }
+ }
+ else
+ {
+ GLfloat pw = p->jc->puzzle_width;
+ GLfloat ph = p->jc->puzzle_height;
+ GLfloat r = resolution;
+
+ glBegin (GL_TRIANGLES);
+ for (i = 0; i < count * 3; i++)
+ {
+ GLfloat x = *tri++;
+ GLfloat y = *tri++;
+ GLfloat z = *tri++;
+
+ /* 0-1 from piece origin */
+ GLfloat xx = x / r;
+ GLfloat yy = y / r;
+
+ /* 0-1 from puzzle origin */
+ GLfloat tx = (p->home.x + xx) / pw;
+ GLfloat ty = (ph - p->home.y - yy) / ph;
+
+ tx = p->jc->tex_x + (tx * p->jc->tex_width);
+ ty = p->jc->tex_y + (ty * p->jc->tex_height);
+
+ glTexCoord2f (tx, ty);
+ glVertex3f (x, y, z);
+ }
+ glEnd();
+ }
+
+ polys += count;
+ glPopMatrix();
+ }
+
+ free (otri);
+# endif /* !HAVE_TESS */
+ }
+
+ /* side faces */
+
+ glFrontFace (GL_CCW);
+ glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
+ for (i = 0; i < o; i += 3)
+ {
+ int j = (i+o-3) % o;
+ int k = (i+3) % o;
+ GLdouble *p = pts + i;
+ GLdouble *pj = pts + j;
+ GLdouble *pk = pts + k;
+
+ do_normal (pj[0], pj[1], pj[2],
+ pj[0], pj[1], -pj[2],
+ pk[0], pk[1], pk[2]);
+
+ glVertex3f (p[0], p[1], p[2]);
+ glVertex3f (p[0], p[1], -p[2]);
+ polys++;
+ }
+ glEnd();
+
+ if (! wire)
+ glColor3f (0.3, 0.3, 0.3);
+
+ /* outline the edges in gray */
+
+ glDisable (GL_TEXTURE_2D);
+ glDisable (GL_LIGHTING);
+ glLineWidth (jc->line_thickness);
+
+ glBegin (GL_LINE_LOOP);
+ for (i = 0; i < o; i += 3)
+ glVertex3f (pts[i], pts[i+1], pts[i+2]);
+ glEnd();
+ polys += o/3;
+
+ glBegin (GL_LINE_LOOP);
+ for (i = 0; i < o; i += 3)
+ glVertex3f (pts[i], pts[i+1], -pts[i+2]);
+ glEnd();
+ polys += o/3;
+
+ free_spline (s);
+ free (pts);
+
+ return polys;
+}
+
+
+static void
+free_puzzle_grid (jigsaw_configuration *jc)
+{
+ int i;
+ for (i = 0; i < jc->puzzle_width * jc->puzzle_height; i++)
+ glDeleteLists (jc->puzzle[i].dlist, 1);
+ free (jc->puzzle);
+ jc->puzzle = 0;
+ jc->puzzle_width = 0;
+ jc->puzzle_height = 0;
+}
+
+
+static void
+make_puzzle_grid (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ int x, y;
+ GLfloat size = (8 + (random() % 8)) * complexity_arg;
+
+ if (jc->puzzle)
+ free_puzzle_grid (jc);
+
+ if (wire)
+ jc->aspect = MI_WIDTH(mi) / (float) MI_HEIGHT(mi);
+
+ if (jc->aspect >= 1.0)
+ {
+ jc->puzzle_width = size;
+ jc->puzzle_height = (size + 0.5) / jc->aspect;
+ }
+ else
+ {
+ jc->puzzle_width = (size + 0.5) * jc->aspect;
+ jc->puzzle_height = size;
+ }
+
+ if (jc->puzzle_width < 1) jc->puzzle_width = 1;
+ if (jc->puzzle_height < 1) jc->puzzle_height = 1;
+
+ if (debug_p)
+ fprintf (stderr, "%s: grid %4d x %-4d (%.2f)\n", progname,
+ jc->puzzle_width, jc->puzzle_height,
+ ((float) jc->puzzle_width / jc->puzzle_height));
+
+ jc->puzzle = (puzzle_piece *)
+ calloc (jc->puzzle_width * (jc->puzzle_height+1), sizeof(*jc->puzzle));
+
+ /* Randomize the right and bottom edge of each piece.
+ Match the left edge of the piece to the right to our right edge.
+ Match the top edge of the piece to the bottom to our bottom edge.
+ */
+ for (y = 0; y < jc->puzzle_height; y++)
+ for (x = 0; x < jc->puzzle_width; x++)
+ {
+ puzzle_piece *p = &jc->puzzle [y * jc->puzzle_width + x];
+ puzzle_piece *r = &jc->puzzle [y * jc->puzzle_width + x+1];
+ puzzle_piece *b = &jc->puzzle [(y+1) * jc->puzzle_width + x];
+ p->edge[RIGHT] = (random() & 1) ? IN : OUT;
+ p->edge[BOTTOM] = (random() & 1) ? IN : OUT;
+ r->edge[LEFT] = p->edge[RIGHT] == IN ? OUT : IN;
+ b->edge[TOP] = p->edge[BOTTOM] == IN ? OUT : IN;
+ }
+
+ /* tell each piece where it belongs. */
+ for (y = 0; y < jc->puzzle_height; y++)
+ for (x = 0; x < jc->puzzle_width; x++)
+ {
+ puzzle_piece *p = &jc->puzzle [y * jc->puzzle_width + x];
+ p->jc = jc;
+ p->home.x = x;
+ p->home.y = y;
+ p->current = p->home;
+
+ /* make sure the outer border is flat */
+ if (p->home.x == 0) p->edge[LEFT] = FLAT;
+ if (p->home.y == 0) p->edge[TOP] = FLAT;
+ if (p->home.x == jc->puzzle_width-1) p->edge[RIGHT] = FLAT;
+ if (p->home.y == jc->puzzle_height-1) p->edge[BOTTOM] = FLAT;
+
+ /* generate the polygons */
+ p->dlist = glGenLists (1);
+ check_gl_error ("generating lists");
+ if (p->dlist <= 0) abort();
+
+ glNewList (p->dlist, GL_COMPILE);
+ p->polys += draw_piece (jc, p,
+ resolution_arg, thickness_arg,
+ p->edge[TOP], p->edge[RIGHT],
+ p->edge[BOTTOM], p->edge[LEFT],
+ wire);
+ glEndList();
+ }
+}
+
+
+static void shuffle_grid (ModeInfo *mi);
+
+
+static void
+image_loaded_cb (const char *filename, XRectangle *geometry,
+ int image_width, int image_height,
+ int texture_width, int texture_height,
+ void *closure)
+{
+ ModeInfo *mi = (ModeInfo *) closure;
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+
+ jc->tex_x = geometry->x / (float) texture_width;
+ jc->tex_y = geometry->y / (float) texture_height;
+ jc->tex_width = geometry->width / (float) texture_width;
+ jc->tex_height = geometry->height / (float) texture_height;
+ jc->aspect = geometry->width / (float) geometry->height;
+
+ if (debug_p)
+ {
+ fprintf (stderr, "%s: image %s\n", progname,
+ (filename ? filename : "(null)"));
+ fprintf (stderr, "%s: image %4d x %-4d + %4d + %-4d (%.2f)\n", progname,
+ geometry->width, geometry->height, geometry->x, geometry->y,
+ (float) geometry->width / geometry->height);
+ fprintf (stderr, "%s: tex %4d x %-4d\n", progname,
+ texture_width, texture_height);
+ fprintf (stderr, "%s: tex %4.2f x %4.2f + %4.2f + %4.2f (%.2f)\n",
+ progname,
+ jc->tex_width, jc->tex_height, jc->tex_x, jc->tex_y,
+ (jc->tex_width / jc->tex_height) *
+ (texture_width / texture_height));
+ }
+
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ make_puzzle_grid (mi);
+}
+
+
+static void
+load_image (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ load_texture_async (mi->xgwa.screen, mi->window,
+ *jc->glx_context, 0, 0,
+ False, jc->texid,
+ image_loaded_cb, mi);
+}
+
+
+/* Whether the two pieces are the same shape, when the second piece
+ is rotated by the given degrees.
+ */
+static Bool
+same_shape (puzzle_piece *p0, puzzle_piece *p1, int rotated_by)
+{
+ switch (rotated_by)
+ {
+ case 0:
+ return (p0->edge[0] == p1->edge[0] &&
+ p0->edge[1] == p1->edge[1] &&
+ p0->edge[2] == p1->edge[2] &&
+ p0->edge[3] == p1->edge[3]);
+ case 90:
+ return (p0->edge[0] == p1->edge[1] &&
+ p0->edge[1] == p1->edge[2] &&
+ p0->edge[2] == p1->edge[3] &&
+ p0->edge[3] == p1->edge[0]);
+ case 180:
+ return (p0->edge[0] == p1->edge[2] &&
+ p0->edge[1] == p1->edge[3] &&
+ p0->edge[2] == p1->edge[0] &&
+ p0->edge[3] == p1->edge[1]);
+ case 270:
+ return (p0->edge[0] == p1->edge[3] &&
+ p0->edge[1] == p1->edge[0] &&
+ p0->edge[2] == p1->edge[1] &&
+ p0->edge[3] == p1->edge[2]);
+ default:
+ abort();
+ }
+}
+
+
+/* Returns the proper rotation for the piece at the given position.
+ */
+static int
+proper_rotation (jigsaw_configuration *jc, puzzle_piece *p,
+ double x, double y)
+{
+ puzzle_piece *p1;
+ int cx = x;
+ int cy = y;
+ if (cx != x) abort(); /* must be in integral position! */
+ if (cy != y) abort();
+ p1 = &jc->puzzle [cy * jc->puzzle_width + cx];
+ if (same_shape (p, p1, 0)) return 0;
+ if (same_shape (p, p1, 90)) return 90;
+ if (same_shape (p, p1, 180)) return 180;
+ if (same_shape (p, p1, 270)) return 270;
+ abort(); /* these two pieces don't match in any rotation! */
+}
+
+
+/* Returns the piece currently at the given position.
+ */
+static puzzle_piece *
+piece_at (jigsaw_configuration *jc, double x, double y)
+{
+ int npieces = jc->puzzle_width * jc->puzzle_height;
+ int i;
+ int cx = x;
+ int cy = y;
+ if (cx != x) abort(); /* must be in integral position! */
+ if (cy != y) abort();
+
+ for (i = 0; i < npieces; i++)
+ {
+ puzzle_piece *p = &jc->puzzle [i];
+ if (p->current.x == cx &&
+ p->current.y == cy)
+ return p;
+ }
+ abort(); /* no piece at that position? */
+}
+
+
+static void
+shuffle_grid (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ int max_tries = jc->puzzle_width * jc->puzzle_height;
+ int npieces = jc->puzzle_width * jc->puzzle_height;
+ int i;
+
+ for (i = 0; i < npieces; i++)
+ {
+ puzzle_piece *p0 = &jc->puzzle [i];
+ puzzle_piece *p1 = 0;
+ int k;
+
+ for (k = 0; k < max_tries; k++)
+ {
+ p1 = &jc->puzzle [random() % npieces];
+ if (same_shape (p0, p1, 0)) break;
+ if (same_shape (p0, p1, 90)) break;
+ if (same_shape (p0, p1, 180)) break;
+ if (same_shape (p0, p1, 270)) break;
+ p1 = 0; /* mismatch */
+ }
+ if (p1 && p0 != p1)
+ {
+ XYZR s;
+ s = p0->current; p0->current = p1->current; p1->current = s;
+ p0->current.r =
+ proper_rotation (jc, p0, p0->current.x, p0->current.y);
+ p1->current.r =
+ proper_rotation (jc, p1, p1->current.x, p1->current.y);
+ }
+ }
+}
+
+
+/* We tend to accumulate floating point errors, e.g., z being 0.000001
+ after a move. This makes sure float values that should be integral are.
+ */
+static void
+smooth_grid (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ int npieces = jc->puzzle_width * jc->puzzle_height;
+ int i;
+
+ for (i = 0; i < npieces; i++)
+ {
+ puzzle_piece *p = &jc->puzzle [i];
+# define SMOOTH(P) \
+ P.x = (int) (P.x + 0.5); \
+ P.y = (int) (P.y + 0.5); \
+ P.z = (int) (P.z + 0.5); \
+ P.r = (int) (P.r + 0.5)
+ SMOOTH(p->home);
+ SMOOTH(p->current);
+ SMOOTH(p->from);
+ SMOOTH(p->to);
+ if (p->tick <= 0.0001) p->tick = 0.0;
+ if (p->tick >= 0.9999) p->tick = 1.0;
+ }
+}
+
+
+static void
+begin_scatter (ModeInfo *mi, Bool unscatter_p)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ int npieces = jc->puzzle_width * jc->puzzle_height;
+ int i;
+ XYZR ctr = { 0, };
+ ctr.x = jc->puzzle_width / 2;
+ ctr.y = jc->puzzle_height / 2;
+
+ for (i = 0; i < npieces; i++)
+ {
+ puzzle_piece *p = &jc->puzzle [i];
+ XYZ a;
+ double d, r, th;
+ p->tick = -frand(1.0);
+ p->from = p->current;
+
+ a.x = p->from.x - ctr.x; /* position relative to center */
+ a.y = p->from.y - ctr.y;
+
+ r = sqrt (a.x*a.x + a.y*a.y);
+ th = atan2 (a.x, a.y);
+
+ d = MAX (jc->puzzle_width, jc->puzzle_height) * 2;
+ r = r*r + d;
+
+ p->to.x = ctr.x + (r * sin (th));
+ p->to.y = ctr.y + (r * cos (th));
+ p->to.z = p->from.z;
+ p->to.r = ((int) p->from.r + (random() % 180)) % 360;
+ p->arc_height = frand(10.0);
+
+ if (unscatter_p)
+ {
+ XYZR s = p->to; p->to = p->from; p->from = s;
+ p->current = p->from;
+ }
+ }
+}
+
+
+static Bool
+solved_p (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ int npieces = jc->puzzle_width * jc->puzzle_height;
+ int i;
+
+ for (i = 0; i < npieces; i++)
+ {
+ puzzle_piece *p = &jc->puzzle [i];
+ if (p->current.x != p->home.x ||
+ p->current.y != p->home.y ||
+ p->current.z != p->home.z)
+ return False;
+ }
+ return True;
+}
+
+
+static void
+move_one_piece (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ int npieces = jc->puzzle_width * jc->puzzle_height;
+ int i;
+
+ for (i = 0; i < npieces * 100; i++) /* shouldn't take that long */
+ {
+ int i = random() % npieces;
+ puzzle_piece *p0 = &jc->puzzle [i];
+ puzzle_piece *p1;
+
+ if (p0->current.x == p0->home.x &&
+ p0->current.y == p0->home.y &&
+ p0->current.z == p0->home.z)
+ continue; /* piece already solved - try again */
+
+ /* swap with the piece occupying p0's home cell. */
+ p1 = piece_at (jc, p0->home.x, p0->home.y);
+
+ if (p0 == p1) abort(); /* should have caught this above */
+
+ p0->tick = 0;
+ p0->from = p0->current;
+ p0->to = p1->current;
+ p0->to.r = proper_rotation (jc, p0, p0->to.x, p0->to.y);
+
+ p1->tick = 0;
+ p1->from = p1->current;
+ p1->to = p0->current;
+ p1->to.r = proper_rotation (jc, p1, p1->to.x, p1->to.y);
+
+ /* Try to avoid having them intersect each other in the air. */
+ p0->arc_height = 0;
+ p1->arc_height = 0;
+ while (fabs (p0->arc_height - p1->arc_height) < 1.5)
+ {
+ p0->arc_height = 0.5 + frand(3.0);
+ p1->arc_height = 1.0 + frand(3.0);
+ }
+
+# define RTILT(V) \
+ V = 90 - BELLRAND(180); \
+ if (! (random() % 5)) V *= 2; \
+ if (! (random() % 5)) V *= 2; \
+ if (! (random() % 5)) V *= 2
+ RTILT (p0->max_tilt);
+ RTILT (p1->max_tilt);
+# undef RTILT
+
+ if (debug_p)
+ fprintf (stderr, "%s: swapping %2d,%-2d with %2d,%d\n", progname,
+ (int) p0->from.x, (int) p0->from.y,
+ (int) p1->from.x, (int) p1->from.y);
+ return;
+ }
+
+ abort(); /* infinite loop! */
+}
+
+
+static Bool
+anim_tick (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ int npieces = jc->puzzle_width * jc->puzzle_height;
+ int i;
+ Bool finished_p = True;
+
+ if (jc->pausing > 0)
+ {
+ jc->pausing -= jc->tick_speed * speed;
+ if (debug_p && jc->pausing <= 0)
+ fprintf (stderr, "%s: (done pausing)\n", progname);
+ return False;
+ }
+
+ for (i = 0; i < npieces; i++)
+ {
+ puzzle_piece *p = &jc->puzzle [i];
+ double tt;
+
+ if (p->tick >= 1.0) continue; /* this piece is done */
+ finished_p = False; /* not done */
+
+ p->tick += jc->tick_speed * speed;
+ if (p->tick > 1.0) p->tick = 1.0;
+
+ if (p->tick < 0.0) continue; /* not yet started */
+
+ tt = 1 - sin (M_PI/2 - p->tick * M_PI/2);
+
+ p->current.x = p->from.x + ((p->to.x - p->from.x) * tt);
+ p->current.y = p->from.y + ((p->to.y - p->from.y) * tt);
+ p->current.z = p->from.z + ((p->to.z - p->from.z) * tt);
+ p->current.r = p->from.r + ((p->to.r - p->from.r) * tt);
+
+ p->current.z += p->arc_height * sin (p->tick * M_PI);
+
+ p->tilt = p->max_tilt * sin (p->tick * M_PI);
+
+ }
+
+ return finished_p;
+}
+
+
+static void
+loading_msg (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ const char *text = "Loading...";
+ XCharStruct e;
+ int w, h;
+ texture_string_metrics (jc->texfont, text, &e, 0, 0);
+ w = e.width;
+ h = e.ascent + e.descent;
+
+ if (wire) return;
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ if (! jc->loading_dlist)
+ {
+ GLfloat othick = jc->line_thickness;
+ puzzle_piece P = { 0, };
+ P.jc = jc;
+ jc->loading_dlist = glGenLists (1);
+ glNewList (jc->loading_dlist, GL_COMPILE);
+ jc->line_thickness = 1;
+ draw_piece (jc, &P,
+ resolution_arg, thickness_arg,
+ OUT, OUT, IN, OUT, True);
+ jc->line_thickness = othick;
+ glEndList();
+ }
+
+ glColor3f (0.2, 0.2, 0.4);
+
+ glPushMatrix();
+ {
+ double x, y, z;
+ get_position (jc->rot, &x, &y, &z, True);
+ glRotatef (x * 360, 1, 0, 0);
+ glRotatef (y * 360, 0, 1, 0);
+ glRotatef (z * 360, 0, 0, 1);
+ glScalef (5, 5, 5);
+ glTranslatef (-0.5, -0.5, 0);
+ glCallList (jc->loading_dlist);
+ }
+ glPopMatrix();
+
+ glColor3f (0.7, 0.7, 1);
+
+
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+
+ {
+ double rot = current_device_rotation();
+ glRotatef(rot, 0, 0, 1);
+ if ((rot > 45 && rot < 135) ||
+ (rot < -45 && rot > -135))
+ {
+ GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
+ glScalef (s, 1/s, 1);
+ }
+ }
+
+ glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);
+ glTranslatef ((MI_WIDTH(mi) - w) / 2,
+ (MI_HEIGHT(mi) - h) / 2,
+ 0);
+ glEnable (GL_TEXTURE_2D);
+ glPolygonMode (GL_FRONT, GL_FILL);
+ glDisable (GL_LIGHTING);
+ glEnable (GL_BLEND);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ print_texture_string (jc->texfont, text);
+ glEnable (GL_DEPTH_TEST);
+ glPopMatrix();
+
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+
+ glMatrixMode(GL_MODELVIEW);
+}
+
+
+static void
+animate (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ double slow = 0.01;
+ double fast = 0.04;
+
+ if (jc->button_down_p && jc->state != PUZZLE_LOADING_MSG)
+ return;
+
+ switch (jc->state)
+ {
+ case PUZZLE_LOADING_MSG:
+ if (! jc->puzzle)
+ loading_msg (mi);
+ /* fall through */
+
+ case PUZZLE_LOADING:
+ if (!jc->puzzle) break; /* still loading */
+ jc->tick_speed = slow;
+ shuffle_grid (mi);
+ smooth_grid (mi);
+ begin_scatter (mi, True);
+ jc->pausing = 0;
+ jc->state = PUZZLE_UNSCATTER;
+ if (debug_p) fprintf (stderr, "%s: unscattering\n", progname);
+ break;
+
+ case PUZZLE_UNSCATTER:
+ jc->tick_speed = slow;
+ if (anim_tick (mi))
+ {
+ smooth_grid (mi);
+ jc->pausing = 1.0;
+ jc->state = PUZZLE_SOLVE;
+ if (debug_p) fprintf (stderr, "%s: solving\n", progname);
+ }
+ break;
+
+ case PUZZLE_SOLVE:
+ jc->tick_speed = fast;
+ if (anim_tick (mi))
+ {
+ smooth_grid (mi);
+ if (solved_p (mi))
+ {
+ if (debug_p) fprintf (stderr, "%s: solved!\n", progname);
+ begin_scatter (mi, False);
+ jc->state = PUZZLE_SCATTER;
+ jc->pausing = 3.0;
+ if (debug_p) fprintf (stderr, "%s: scattering\n", progname);
+ }
+ else
+ {
+ move_one_piece (mi);
+ jc->pausing = 0.3;
+ }
+ }
+ break;
+
+ case PUZZLE_SCATTER:
+ jc->tick_speed = slow;
+ if (anim_tick (mi))
+ {
+ free_puzzle_grid (jc);
+ load_image (mi);
+ jc->state = PUZZLE_LOADING;
+ jc->pausing = 1.0;
+ if (debug_p) fprintf (stderr, "%s: loading\n", progname);
+ }
+ break;
+
+ default:
+ abort();
+ }
+}
+
+
+/* Window management, etc
+ */
+ENTRYPOINT void
+reshape_jigsaw (ModeInfo *mi, int width, int height)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ 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);
+
+# ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
+ {
+ int o = (int) current_device_rotation();
+ if (o != 0 && o != 180 && o != -180)
+ glScalef (1/h, 1/h, 1/h);
+ }
+# endif
+
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ jc->line_thickness = (MI_IS_WIREFRAME (mi) ? 1 : MAX (1, height / 300.0));
+}
+
+
+ENTRYPOINT Bool
+jigsaw_handle_event (ModeInfo *mi, XEvent *event)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+
+ if (gltrackball_event_handler (event, jc->trackball,
+ MI_WIDTH (mi), MI_HEIGHT (mi),
+ &jc->button_down_p))
+ return True;
+ else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
+ {
+ begin_scatter (mi, False);
+ jc->state = PUZZLE_SCATTER;
+ return True;
+ }
+
+ return False;
+}
+
+
+ENTRYPOINT void
+init_jigsaw (ModeInfo *mi)
+{
+ jigsaw_configuration *jc;
+ int wire = MI_IS_WIREFRAME(mi);
+
+ MI_INIT (mi, sps);
+ jc = &sps[MI_SCREEN(mi)];
+ jc->glx_context = init_GL(mi);
+
+ reshape_jigsaw (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+
+ if (!wire)
+ {
+ GLfloat pos[4] = {0.05, 0.07, 1.00, 0.0};
+ GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
+ GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
+ GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
+
+ glLightfv(GL_LIGHT0, GL_POSITION, pos);
+ glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
+ glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
+ }
+
+ jc->trackball = gltrackball_init (False);
+ jc->rot = make_rotator (0, 0, 0, 0, speed * 0.002, True);
+ jc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
+
+ jc->state = PUZZLE_LOADING_MSG;
+
+ resolution_arg /= complexity_arg;
+
+# ifndef HAVE_TESS
+ /* If it's not even, we get crosses. */
+ if (resolution_arg & 1)
+ resolution_arg++;
+# endif /* !HAVE_TESS */
+
+ if (wire)
+ make_puzzle_grid (mi);
+ else
+ load_image (mi);
+}
+
+
+ENTRYPOINT void
+draw_jigsaw (ModeInfo *mi)
+{
+ jigsaw_configuration *jc = &sps[MI_SCREEN(mi)];
+ Display *dpy = MI_DISPLAY(mi);
+ Window window = MI_WINDOW(mi);
+ int wire = MI_IS_WIREFRAME(mi);
+
+ if (!jc->glx_context)
+ return;
+
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(jc->glx_context));
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ mi->polygon_count = 0;
+
+ glPushMatrix ();
+/* glRotatef(current_device_rotation(), 0, 0, 1); */
+ gltrackball_rotate (jc->trackball);
+
+ animate (mi);
+
+ if (wobble_p && jc->puzzle)
+ {
+ double x, y, z;
+ double max = 60;
+ get_position (jc->rot, &x, &y, &z, !jc->button_down_p);
+ x = 1; /* always lean back */
+ glRotatef (max/2 - x*max, 1, 0, 0);
+ glRotatef (max/2 - z*max, 0, 1, 0);
+ }
+
+ if (jc->puzzle)
+ {
+ GLfloat s = 14.0 / jc->puzzle_height;
+ int x, y;
+
+ glEnable(GL_CULL_FACE);
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_NORMALIZE);
+ glEnable(GL_LINE_SMOOTH);
+
+ glScalef (s, s, s);
+ glTranslatef (-jc->puzzle_width / 2.0, -jc->puzzle_height / 2.0, 0);
+
+ if (! wire)
+ {
+ glEnable (GL_TEXTURE_2D);
+ glEnable (GL_BLEND);
+ glEnable (GL_LIGHTING);
+ glEnable (GL_LIGHT0);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ for (y = 0; y < jc->puzzle_height; y++)
+ for (x = 0; x < jc->puzzle_width; x++)
+ {
+ puzzle_piece *p = &jc->puzzle [y * jc->puzzle_width + x];
+ glPushMatrix();
+ glTranslatef (p->current.x, p->current.y, p->current.z);
+ glTranslatef (0.5, 0.5, 0);
+ glRotatef (p->current.r, 0, 0, 1);
+ glRotatef (p->tilt, 0, 1, 0);
+ glTranslatef (-0.5, -0.5, 0);
+ glCallList(p->dlist);
+ mi->polygon_count += p->polys;
+ glPopMatrix();
+ }
+ }
+
+ glPopMatrix ();
+
+ if (mi->fps_p) do_fps (mi);
+ glFinish();
+
+ glXSwapBuffers(dpy, window);
+}
+
+XSCREENSAVER_MODULE ("Jigsaw", jigsaw)
+
+#endif /* USE_GL */