summaryrefslogtreecommitdiffstats
path: root/hacks/glx/pinion.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/glx/pinion.c')
-rw-r--r--hacks/glx/pinion.c1470
1 files changed, 1470 insertions, 0 deletions
diff --git a/hacks/glx/pinion.c b/hacks/glx/pinion.c
new file mode 100644
index 0000000..f6793eb
--- /dev/null
+++ b/hacks/glx/pinion.c
@@ -0,0 +1,1470 @@
+/* pinion, 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.
+ */
+
+#define DEFAULTS "*delay: 15000 \n" \
+ "*showFPS: False \n" \
+ "*wireframe: False \n" \
+ "*titleFont: -*-helvetica-medium-r-normal-*-*-180-*-*-*-*-*-*\n" \
+ "*titleFont2: -*-helvetica-medium-r-normal-*-*-120-*-*-*-*-*-*\n" \
+ "*titleFont3: -*-helvetica-medium-r-normal-*-*-80-*-*-*-*-*-*\n" \
+
+# define free_pinion 0
+# define release_pinion 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#undef BELLRAND
+#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
+
+#include "xlockmore.h"
+#include "normals.h"
+#include "gltrackball.h"
+#include "texfont.h"
+#include "involute.h"
+#include <ctype.h>
+
+#ifdef USE_GL /* whole file */
+
+#define DEF_SPIN_SPEED "1.0"
+#define DEF_SCROLL_SPEED "1.0"
+#define DEF_GEAR_SIZE "1.0"
+#define DEF_MAX_RPM "900"
+
+typedef struct {
+ GLXContext *glx_context;
+ GLfloat vp_left, vp_right, vp_top, vp_bottom; /* default visible area */
+ GLfloat vp_width, vp_height;
+ GLfloat render_left, render_right; /* area in which gears are displayed */
+ GLfloat layout_left, layout_right; /* layout region, on the right side */
+
+ int ngears;
+ int gears_size;
+ gear **gears;
+
+ trackball_state *trackball;
+ Bool button_down_p;
+ unsigned long mouse_gear_id;
+
+ texture_font_data *font1, *font2, *font3;
+
+ int draw_tick;
+
+ GLfloat plane_displacement; /* distance between coaxial gears */
+
+ int debug_size_failures; /* for debugging messages */
+ int debug_position_failures;
+ unsigned long current_length; /* gear count in current train */
+ unsigned long current_blur_length; /* how long have we been blurring? */
+
+} pinion_configuration;
+
+
+static pinion_configuration *pps = NULL;
+
+/* command line arguments */
+static GLfloat spin_speed, scroll_speed, gear_size, max_rpm;
+
+static Bool verbose_p = False; /* print progress on stderr */
+static Bool debug_p = False; /* render as flat schematic */
+
+/* internal debugging variables */
+static Bool debug_placement_p = False; /* extreme verbosity on stderr */
+static Bool debug_one_gear_p = False; /* draw one big stationary gear */
+
+
+static XrmOptionDescRec opts[] = {
+ { "-spin", ".spinSpeed", XrmoptionSepArg, 0 },
+ { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
+ { "-size", ".gearSize", XrmoptionSepArg, 0 },
+ { "-max-rpm",".maxRPM", XrmoptionSepArg, 0 },
+ { "-debug", ".debug", XrmoptionNoArg, "True" },
+ { "-verbose",".verbose", XrmoptionNoArg, "True" },
+};
+
+static argtype vars[] = {
+ {&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
+ {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
+ {&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float},
+ {&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float},
+ {&debug_p, "debug", "Debug", "False", t_Bool},
+ {&verbose_p, "verbose", "Verbose", "False", t_Bool},
+};
+
+ENTRYPOINT ModeSpecOpt pinion_opts = {countof(opts), opts, countof(vars), vars, NULL};
+
+
+/* Font stuff
+ */
+
+static void
+load_fonts (ModeInfo *mi)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ pp->font1 = load_texture_font (mi->dpy, "titleFont");
+ pp->font2 = load_texture_font (mi->dpy, "titleFont2");
+ pp->font3 = load_texture_font (mi->dpy, "titleFont3");
+}
+
+
+
+static void rpm_string (double rpm, char *s);
+
+static void
+draw_label (ModeInfo *mi)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ char label[1024];
+ int i;
+ gear *g = 0;
+
+ if (pp->mouse_gear_id)
+ for (i = 0; i < pp->ngears; i++)
+ if (pp->gears[i]->id == pp->mouse_gear_id)
+ {
+ g = pp->gears[i];
+ break;
+ }
+
+ if (!g)
+ *label = 0;
+ else
+ {
+ sprintf (label, "%d teeth\n", (int) g->nteeth);
+ rpm_string (g->rpm, label + strlen(label));
+ if (debug_p)
+ sprintf (label + strlen (label), "\nPolys: %d\nModel: %s (%.2f)\n",
+ g->polygons,
+ (g->size == INVOLUTE_SMALL ? "small" :
+ g->size == INVOLUTE_MEDIUM ? "medium"
+ : "large"),
+ g->tooth_h * MI_HEIGHT(mi));
+ }
+
+ if (*label)
+ {
+ texture_font_data *fd;
+ if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
+ fd = pp->font1;
+ else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
+ fd = pp->font2;
+ else
+ fd = pp->font3;
+
+ glColor3f (0.8, 0.8, 0);
+ print_texture_label (mi->dpy, fd,
+ mi->xgwa.width, mi->xgwa.height,
+ 1, label);
+ }
+}
+
+
+/* Some utilities
+ */
+
+
+/* Find the gear in the scene that is farthest to the right or left.
+ */
+static gear *
+farthest_gear (ModeInfo *mi, Bool left_p)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ int i;
+ gear *rg = 0;
+ double x = (left_p ? 999999 : -999999);
+ for (i = 0; i < pp->ngears; i++)
+ {
+ gear *g = pp->gears[i];
+ double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
+ if (left_p ? (x > gx) : (x < gx))
+ {
+ rg = g;
+ x = gx;
+ }
+ }
+ return rg;
+}
+
+
+/* Compute the revolutions per minute of a gear.
+ */
+static void
+compute_rpm (ModeInfo *mi, gear *g)
+{
+ double fps, rpf, rps;
+ fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
+
+ if (fps > 150) fps = 150; /* let's be reasonable... */
+ if (fps < 10) fps = 10;
+
+ rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
+ rps = rpf * fps; /* rotations per second */
+ g->rpm = rps * 60;
+}
+
+/* Prints the RPM into a string, doing fancy float formatting.
+ */
+static void
+rpm_string (double rpm, char *s)
+{
+ char buf[30];
+ int L;
+ if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
+ else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
+ else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
+ else sprintf (buf, "%.16f",rpm);
+
+ L = strlen(buf);
+ while (buf[L-1] == '0') buf[--L] = 0;
+ if (buf[L-1] == '.') buf[--L] = 0;
+ strcpy (s, buf);
+ strcat (s, " RPM");
+}
+
+
+
+/* Layout and stuff.
+ */
+
+
+/* Create and return a new gear sized for placement next to or on top of
+ the given parent gear (if any.) Returns 0 if out of memory.
+ */
+static gear *
+new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ gear *g = (gear *) calloc (1, sizeof (*g));
+ int loop_count = 0;
+ static unsigned long id = 0; /* only used in debugging output */
+
+ if (!g) return 0;
+ if (coaxial_p && !parent) abort();
+ g->id = ++id;
+
+ g->coax_displacement = pp->plane_displacement;
+
+ while (1)
+ {
+ loop_count++;
+ if (loop_count > 1000)
+ /* The only time we loop in here is when making a coaxial gear, and
+ trying to pick a radius that is either significantly larger or
+ smaller than its parent. That shouldn't be hard, so something
+ must be really wrong if we can't do that in only a few tries.
+ */
+ abort();
+
+ /* Pick the size of the teeth.
+ */
+ if (parent && !coaxial_p) /* adjascent gears need matching teeth */
+ {
+ g->tooth_w = parent->tooth_w;
+ g->tooth_h = parent->tooth_h;
+ g->thickness = parent->thickness;
+ g->thickness2 = parent->thickness2;
+ g->thickness3 = parent->thickness3;
+ }
+ else /* gears that begin trains get any size they want */
+ {
+ double scale = (1.0 + BELLRAND(4.0)) * gear_size;
+ g->tooth_w = 0.007 * scale;
+ g->tooth_h = 0.005 * scale;
+ g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
+ g->thickness2 = g->thickness / 4;
+ g->thickness3 = g->thickness;
+ }
+
+ /* Pick the number of teeth, and thus, the radius.
+ */
+ {
+ double c;
+
+ AGAIN:
+ g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
+
+ if (g->nteeth < 7 && (random() % 5) != 0)
+ goto AGAIN; /* Let's make very small tooth-counts more rare */
+
+ c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
+ g->r = c / (M_PI * 2); /* c = 2 pi r */
+ }
+
+
+ /* Are we done now?
+ */
+ if (! coaxial_p) break; /* yes */
+ if (g->nteeth == parent->nteeth) continue; /* ugly */
+ if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
+ if (parent->r < g->r * 0.6) break; /* g much larger than parent */
+ }
+
+ /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */
+
+ if (debug_one_gear_p)
+ g->tooth_slope = frand(20)-10;
+
+
+ /* Colorize
+ */
+ g->color[0] = 0.5 + frand(0.5);
+ g->color[1] = 0.5 + frand(0.5);
+ g->color[2] = 0.5 + frand(0.5);
+ g->color[3] = 1.0;
+
+ g->color2[0] = g->color[0] * 0.85;
+ g->color2[1] = g->color[1] * 0.85;
+ g->color2[2] = g->color[2] * 0.85;
+ g->color2[3] = g->color[3];
+
+
+ /* Decide on shape of gear interior:
+ - just a ring with teeth;
+ - that, plus a thinner in-set "plate" in the middle;
+ - that, plus a thin raised "lip" on the inner plate;
+ - or, a wide lip (really, a thicker third inner plate.)
+ */
+ if ((random() % 10) == 0)
+ {
+ /* inner_r can go all the way in; there's no inset disc. */
+ g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
+ g->inner_r2 = 0;
+ g->inner_r3 = 0;
+ }
+ else
+ {
+ /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
+ g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
+ g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
+ g->inner_r3 = 0;
+
+ if (g->inner_r2 > (g->r * 0.2))
+ {
+ int nn = (random() % 10);
+ if (nn <= 2)
+ g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
+ else if (nn <= 7 && g->inner_r2 >= 0.1)
+ g->inner_r3 = g->inner_r2 - 0.01;
+ }
+ }
+
+ /* Coaxial gears need to have the same innermost hole size (for the axle.)
+ Use whichever of the two is smaller. (Modifies parent.)
+ */
+ if (coaxial_p)
+ {
+ double hole1 = (g->inner_r3 ? g->inner_r3 :
+ g->inner_r2 ? g->inner_r2 :
+ g->inner_r);
+ double hole2 = (parent->inner_r3 ? parent->inner_r3 :
+ parent->inner_r2 ? parent->inner_r2 :
+ parent->inner_r);
+ double hole = (hole1 < hole2 ? hole1 : hole2);
+ if (hole <= 0) abort();
+
+ if (g->inner_r3) g->inner_r3 = hole;
+ else if (g->inner_r2) g->inner_r2 = hole;
+ else g->inner_r = hole;
+
+ if (parent->inner_r3) parent->inner_r3 = hole;
+ else if (parent->inner_r2) parent->inner_r2 = hole;
+ else parent->inner_r = hole;
+ }
+
+ /* If we have three discs, sometimes make the middle disc be spokes.
+ */
+ if (g->inner_r3 && ((random() % 5) == 0))
+ {
+ g->spokes = 2 + BELLRAND (5);
+ g->spoke_thickness = 1 + frand(7.0);
+ if (g->spokes == 2 && g->spoke_thickness < 2)
+ g->spoke_thickness += 1;
+ }
+
+ /* Sometimes add little nubbly bits, if there is room.
+ */
+ if (g->nteeth > 5)
+ {
+ double size = 0;
+ involute_biggest_ring (g, 0, &size, 0);
+ if (size > g->r * 0.2 && (random() % 5) == 0)
+ {
+ g->nubs = 1 + (random() % 16);
+ if (g->nubs > 8) g->nubs = 1;
+ }
+ }
+
+ if (g->inner_r3 > g->inner_r2) abort();
+ if (g->inner_r2 > g->inner_r) abort();
+ if (g->inner_r > g->r) abort();
+
+ /* Decide how complex the polygon model should be.
+ */
+ {
+ double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
+ if (pix <= 2.5) g->size = INVOLUTE_SMALL;
+ else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
+ else if (pix <= 25) g->size = INVOLUTE_LARGE;
+ else g->size = INVOLUTE_HUGE;
+ }
+
+ g->base_p = !parent;
+
+ return g;
+}
+
+
+/* Given a newly-created gear, place it next to its parent in the scene,
+ with its teeth meshed and the proper velocity. Returns False if it
+ didn't work. (Call this a bunch of times until either it works, or
+ you decide it's probably not going to.)
+ */
+static Bool
+place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+
+ /* If this gear takes up more than 1/3rd of the screen, it's no good.
+ */
+ if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
+ ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
+ {
+ if (verbose_p && debug_placement_p && 0)
+ fprintf (stderr,
+ "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
+ progname,
+ (g->r + g->tooth_h), gear_size,
+ pp->vp_width, pp->vp_height);
+ pp->debug_size_failures++;
+ return False;
+ }
+
+ /* Compute this gear's velocity.
+ */
+ if (! parent)
+ {
+ g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
+ g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
+ }
+ else if (coaxial_p)
+ {
+ g->ratio = parent->ratio; /* bound gears have the same ratio */
+ g->th = parent->th;
+ g->rpm = parent->rpm;
+ g->wobble = parent->wobble;
+ }
+ else
+ {
+ /* Gearing ratio is the ratio of the number of teeth to previous gear
+ (which is also the ratio of the circumferences.)
+ */
+ g->ratio = (double) parent->nteeth / (double) g->nteeth;
+
+ /* Set our initial rotation to match that of the previous gear,
+ multiplied by the gearing ratio. (This is finessed later,
+ once we know the exact position of the gear relative to its
+ parent.)
+ */
+ g->th = -(parent->th * g->ratio);
+
+ if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
+ {
+ double off = (180.0 / g->nteeth);
+ if (g->th > 0)
+ g->th += off;
+ else
+ g->th -= off;
+ }
+
+ /* ratios are cumulative for all gears in the train. */
+ g->ratio *= parent->ratio;
+ }
+
+
+ /* Place the gear relative to the parent.
+ */
+
+ if (! parent)
+ {
+ gear *rg = farthest_gear (mi, False);
+ double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
+ if (right < pp->layout_left) /* place off screen */
+ right = pp->layout_left;
+
+ g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
+ g->y = 0;
+ g->z = 0;
+
+ if (debug_one_gear_p)
+ g->x = 0;
+ }
+ else if (coaxial_p)
+ {
+ double off = pp->plane_displacement;
+
+ g->x = parent->x;
+ g->y = parent->y;
+ g->z = parent->z + (g->r > parent->r /* small gear on top */
+ ? -off : off);
+
+ if (parent->r > g->r) /* mark which is top and which is bottom */
+ {
+ parent->coax_p = 1;
+ g->coax_p = 2;
+ parent->wobble = 0; /* looks bad when axle moves */
+ }
+ else
+ {
+ parent->coax_p = 2;
+ g->coax_p = 1;
+ g->wobble = 0;
+ }
+
+ g->coax_thickness = parent->thickness;
+ parent->coax_thickness = g->thickness;
+
+ /* Don't let the train get too close to or far from the screen.
+ If we're getting too close, give up on this gear.
+ (But getting very far away is fine.)
+ */
+ if (g->z >= off * 4 ||
+ g->z <= -off * 4)
+ {
+ if (verbose_p && debug_placement_p)
+ fprintf (stderr, "%s: placement: bad depth: %.2f\n",
+ progname, g->z);
+ pp->debug_position_failures++;
+ return False;
+ }
+ }
+ else /* position it somewhere next to the parent. */
+ {
+ double r_off = parent->r + g->r;
+ int angle;
+
+ if ((random() % 3) != 0)
+ angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */
+ else
+ angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */
+
+ g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
+ g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
+ g->z = parent->z;
+
+ /* If the angle we picked would have positioned this gear
+ more than halfway off screen, that's no good. */
+ if (g->y > pp->vp_top ||
+ g->y < pp->vp_bottom)
+ {
+ if (verbose_p && debug_placement_p)
+ fprintf (stderr, "%s: placement: out of bounds: %s\n",
+ progname, (g->y > pp->vp_top ? "top" : "bottom"));
+ pp->debug_position_failures++;
+ return False;
+ }
+
+ /* avoid accidentally changing sign of "th" in the math below. */
+ g->th += (g->th > 0 ? 360 : -360);
+
+ /* Adjust the rotation of the gear so that its teeth line up with its
+ parent, based on the position of the gear and the current rotation
+ of the parent.
+ */
+ {
+ double p_c = 2 * M_PI * parent->r; /* circumference of parent */
+ double g_c = 2 * M_PI * g->r; /* circumference of g */
+
+ double p_t = p_c * (angle/360.0); /* distance travelled along
+ circumference of parent when
+ moving "angle" degrees along
+ parent. */
+ double g_rat = p_t / g_c; /* if travelling that distance
+ along circumference of g,
+ ratio of g's circumference
+ travelled. */
+ double g_th = 360.0 * g_rat; /* that ratio in degrees */
+
+ g->th += angle + g_th;
+ }
+ }
+
+ if (debug_one_gear_p)
+ {
+ compute_rpm (mi, g);
+ return True;
+ }
+
+ /* If the position we picked for this gear would cause it to already
+ be visible on the screen, give up. This can happen when the train
+ is growing backwards, and we don't want to see gears flash into
+ existence.
+ */
+ if (g->x - g->r - g->tooth_h < pp->render_right)
+ {
+ if (verbose_p && debug_placement_p)
+ fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
+ pp->debug_position_failures++;
+ return False;
+ }
+
+ /* If the position we picked for this gear causes it to overlap
+ with any earlier gear in the train, give up.
+ */
+ {
+ int i;
+
+ for (i = pp->ngears-1; i >= 0; i--)
+ {
+ gear *og = pp->gears[i];
+
+ if (og == g) continue;
+ if (og == parent) continue;
+ if (g->z != og->z) continue; /* Ignore unless on same layer */
+
+ /* Collision detection without sqrt:
+ d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
+ d < r1 + r2 d^2 < (r1 + r2)^2
+ */
+ if (((g->x - og->x) * (g->x - og->x) +
+ (g->y - og->y) * (g->y - og->y)) <
+ ((g->r + g->tooth_h + og->r + og->tooth_h) *
+ (g->r + g->tooth_h + og->r + og->tooth_h)))
+ {
+ if (verbose_p && debug_placement_p)
+ fprintf (stderr, "%s: placement: collision with %lu\n",
+ progname, og->id);
+ pp->debug_position_failures++;
+ return False;
+ }
+ }
+ }
+
+ compute_rpm (mi, g);
+
+
+ /* Make deeper gears be darker.
+ */
+ {
+ double depth = g->z / pp->plane_displacement;
+ double brightness = 1 + (depth / 6);
+ double limit = 0.4;
+ if (brightness < limit) brightness = limit;
+ if (brightness > 1/limit) brightness = 1/limit;
+ g->color[0] *= brightness;
+ g->color[1] *= brightness;
+ g->color[2] *= brightness;
+ g->color2[0] *= brightness;
+ g->color2[1] *= brightness;
+ g->color2[2] *= brightness;
+ }
+
+ /* If a single frame of animation would cause the gear to rotate by
+ more than 1/2 the size of a single tooth, then it won't look right:
+ the gear will appear to be turning at some lower harmonic of its
+ actual speed.
+ */
+ {
+ double ratio = g->ratio * spin_speed;
+ double blur_limit = 180.0 / g->nteeth;
+
+ if (ratio > blur_limit)
+ g->motion_blur_p = 1;
+
+ if (!coaxial_p)
+ {
+ /* ride until the wheels fall off... */
+ if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
+ if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
+ if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
+ if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
+ if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
+ if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
+ }
+ }
+
+ return True;
+}
+
+static void
+free_gear (gear *g)
+{
+ if (g->dlist)
+ glDeleteLists (g->dlist, 1);
+ free (g);
+}
+
+
+/* Make a new gear, place it next to its parent in the scene,
+ with its teeth meshed and the proper velocity. Returns the gear;
+ or 0 if it didn't work. (Call this a bunch of times until either
+ it works, or you decide it's probably not going to.)
+ */
+static gear *
+place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ int loop_count = 0;
+ gear *g = 0;
+
+ while (1)
+ {
+ loop_count++;
+ if (loop_count >= 100)
+ {
+ if (g)
+ free_gear (g);
+ g = 0;
+ break;
+ }
+
+ g = new_gear (mi, parent, coaxial_p);
+ if (!g) return 0; /* out of memory? */
+
+ if (place_gear (mi, g, parent, coaxial_p))
+ break;
+ }
+
+ if (! g) return 0;
+
+ /* We got a gear, and it is properly positioned.
+ Insert it in the scene.
+ */
+ if (pp->ngears + 2 >= pp->gears_size)
+ {
+ pp->gears_size += 100;
+ pp->gears = (gear **) realloc (pp->gears,
+ pp->gears_size * sizeof (*pp->gears));
+ if (! pp->gears)
+ {
+ fprintf (stderr, "%s: out of memory (%d gears)\n",
+ progname, pp->gears_size);
+ }
+ }
+
+ pp->gears[pp->ngears++] = g;
+ return g;
+}
+
+
+static void delete_gear (ModeInfo *mi, gear *g);
+
+static void
+push_gear (ModeInfo *mi)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ gear *g;
+ gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
+
+ Bool tried_coaxial_p = False;
+ Bool coaxial_p = False;
+ Bool last_ditch_coax_p = False;
+ int loop_count = 0;
+
+ pp->debug_size_failures = 0;
+ pp->debug_position_failures = 0;
+
+ AGAIN:
+ loop_count++;
+ if (loop_count > 100) abort(); /* we're doomed! */
+
+ g = 0;
+
+ /* If the gears are turning at LUDICROUS SPEED, unhook the train to
+ reset things to a sane velocity.
+
+ 10,000 RPM at 30 FPS = 5.5 rotations per frame.
+ 1,000 RPM at 30 FPS = 0.5 rotations per frame.
+ 600 RPM at 30 FPS = 3 frames per rotation.
+ 200 RPM at 30 FPS = 9 frames per rotation.
+ 100 RPM at 30 FPS = 18 frames per rotation.
+ 50 RPM at 30 FPS = 36 frames per rotation.
+ 10 RPM at 30 FPS = 3 sec per rotation.
+ 1 RPM at 30 FPS = 30 sec per rotation.
+ .5 RPM at 30 FPS = 1 min per rotation.
+ .1 RPM at 30 FPS = 5 min per rotation.
+ */
+ if (parent && parent->rpm > max_rpm)
+ {
+ if (verbose_p)
+ {
+ char buf[100];
+ rpm_string (parent->rpm, buf);
+ fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf);
+ }
+ parent = 0;
+ }
+
+ /* If the last N gears we've placed have all been motion-blurred, then
+ it's a safe guess that we've wandered off into the woods and aren't
+ coming back. Bail on this train.
+ */
+ if (pp->current_blur_length >= 10)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
+ parent = 0;
+ }
+
+
+
+ /* Sometimes, try to make a coaxial gear.
+ */
+ if (parent && !parent->coax_p && (random() % 40) == 0)
+ {
+ tried_coaxial_p = True;
+ coaxial_p = True;
+ g = place_new_gear (mi, parent, coaxial_p);
+ }
+
+ /* Try to make a regular gear.
+ */
+ if (!g)
+ {
+ coaxial_p = False;
+ g = place_new_gear (mi, parent, coaxial_p);
+ }
+
+ /* If we couldn't make a regular gear, then try to make a coxial gear
+ (unless we already tried that.)
+ */
+ if (!g && !tried_coaxial_p && parent && !parent->coax_p)
+ {
+ tried_coaxial_p = True;
+ coaxial_p = True;
+ g = place_new_gear (mi, parent, coaxial_p);
+ if (g)
+ last_ditch_coax_p = True;
+ }
+
+ /* If we couldn't do that either, then the train has hit a dead end:
+ start a new train.
+ */
+ if (!g)
+ {
+ coaxial_p = False;
+ if (verbose_p)
+ fprintf (stderr, "%s: dead end!\n\n", progname);
+ parent = 0;
+ g = place_new_gear (mi, parent, coaxial_p);
+ }
+
+ if (! g)
+ {
+ /* Unable to make/place any gears at all!
+ This can happen if we've backed ourself into a corner very near
+ the top-right or bottom-right corner of the growth zone.
+ It's time to add a gear, but there's no room to add one!
+ In that case, let's just wipe all the gears that are in the
+ growth zone and try again.
+ */
+ int i;
+
+ if (verbose_p && debug_placement_p)
+ fprintf (stderr,
+ "%s: placement: resetting growth zone! "
+ "failed: %d size, %d pos\n",
+ progname,
+ pp->debug_size_failures, pp->debug_position_failures);
+ for (i = pp->ngears-1; i >= 0; i--)
+ {
+ gear *g = pp->gears[i];
+ if (g->x - g->r - g->tooth_h < pp->render_left)
+ delete_gear (mi, g);
+ }
+ goto AGAIN;
+ }
+
+ if (g->coax_p)
+ {
+ if (!parent) abort();
+ if (g->x != parent->x) abort();
+ if (g->y != parent->y) abort();
+ if (g->z == parent->z) abort();
+ if (g->r == parent->r) abort();
+ if (g->th != parent->th) abort();
+ if (g->ratio != parent->ratio) abort();
+ if (g->rpm != parent->rpm) abort();
+ }
+
+ if (verbose_p)
+ {
+ fprintf (stderr, "%s: %5lu ", progname, g->id);
+
+ fputc ((g->motion_blur_p ? '*' : ' '), stderr);
+ fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
+ g->coax_p ? '1' : ' '),
+ stderr);
+
+ fprintf (stderr, " %2d%%",
+ (int) (g->r * 2 * 100 / pp->vp_height));
+ fprintf (stderr, " %2d teeth", (int) g->nteeth);
+ fprintf (stderr, " %3.0f rpm;", g->rpm);
+
+ {
+ char buf1[50], buf2[50], buf3[100];
+ *buf1 = 0; *buf2 = 0; *buf3 = 0;
+ if (pp->debug_size_failures)
+ sprintf (buf1, "%3d sz", pp->debug_size_failures);
+ if (pp->debug_position_failures)
+ sprintf (buf2, "%2d pos", pp->debug_position_failures);
+ if (*buf1 || *buf2)
+ sprintf (buf3, " tries: %-7s%s", buf1, buf2);
+ fprintf (stderr, "%-21s", buf3);
+ }
+
+ if (g->base_p) fprintf (stderr, " RESET %lu", pp->current_length);
+ fprintf (stderr, "\n");
+ }
+
+ if (g->base_p)
+ pp->current_length = 1;
+ else
+ pp->current_length++;
+
+ if (g->motion_blur_p)
+ pp->current_blur_length++;
+ else
+ pp->current_blur_length = 0;
+}
+
+
+
+/* Remove the given gear from the scene and free it.
+ */
+static void
+delete_gear (ModeInfo *mi, gear *g)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ int i;
+
+ for (i = 0; i < pp->ngears; i++) /* find this gear in the list */
+ if (pp->gears[i] == g) break;
+ if (pp->gears[i] != g) abort();
+
+ for (; i < pp->ngears-1; i++) /* pull later entries forward */
+ pp->gears[i] = pp->gears[i+1];
+ pp->gears[i] = 0;
+ pp->ngears--;
+ free_gear (g);
+}
+
+
+/* Update the position of each gear in the scene.
+ */
+static void
+scroll_gears (ModeInfo *mi)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ int i;
+
+ for (i = 0; i < pp->ngears; i++)
+ pp->gears[i]->x -= (scroll_speed * 0.002);
+
+ /* if the right edge of any gear is off screen to the left, delete it.
+ */
+ for (i = pp->ngears-1; i >= 0; i--)
+ {
+ gear *g = pp->gears[i];
+ if (g->x + g->r + g->tooth_h < pp->render_left)
+ delete_gear (mi, g);
+ }
+
+ /* if the right edge of the last-added gear is left of the right edge
+ of the layout area, add another gear.
+ */
+ i = 0;
+ while (1)
+ {
+ gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
+ if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
+ push_gear (mi);
+ else
+ break;
+ i++;
+ if (debug_one_gear_p) break;
+ }
+
+ /*
+ if (i > 1 && verbose_p)
+ fprintf (stderr, "%s: pushed %d gears\n", progname, i);
+ */
+}
+
+
+/* Update the rotation of each gear in the scene.
+ */
+static void
+spin_gears (ModeInfo *mi)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ int i;
+
+ for (i = 0; i < pp->ngears; i++)
+ {
+ gear *g = pp->gears[i];
+ double off = (g->ratio * spin_speed);
+
+ if (g->th > 0)
+ g->th += off;
+ else
+ g->th -= off;
+ }
+}
+
+
+/* Run the animation fast (without displaying anything) until the first
+ gear is just about to come on screen. This is to avoid a big delay
+ with a blank screen when -scroll is low.
+ */
+static void
+ffwd (ModeInfo *mi)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ if (debug_one_gear_p) return;
+ while (1)
+ {
+ gear *g = farthest_gear (mi, True);
+ if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
+ break;
+ scroll_gears (mi);
+ }
+}
+
+
+
+/* Render one gear in the proper position, creating the gear's
+ display list first if necessary.
+ */
+static void
+draw_gear (ModeInfo *mi, int which)
+{
+ Bool wire_p = MI_IS_WIREFRAME(mi);
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ gear *g = pp->gears[which];
+ GLfloat th;
+
+ Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
+ g->x - g->r - g->tooth_h <= pp->render_right);
+
+ if (!visible_p && !debug_p)
+ return;
+
+ if (! g->dlist)
+ {
+ g->dlist = glGenLists (1);
+ if (! g->dlist)
+ {
+ /* I don't know how many display lists a GL implementation
+ is supposed to provide, but hopefully it's more than
+ "a few hundred", or we'll be in trouble...
+ */
+ check_gl_error ("glGenLists");
+ abort();
+ }
+
+ glNewList (g->dlist, GL_COMPILE);
+ g->polygons = draw_involute_gear (g, (wire_p && debug_p ? 2 : wire_p));
+ glEndList ();
+ }
+
+ glPushMatrix();
+
+ glTranslatef (g->x, g->y, g->z);
+
+ if (g->motion_blur_p && !pp->button_down_p)
+ {
+ /* If we're in motion-blur mode, then draw the gear so that each
+ frame rotates it by exactly one half tooth-width, so that it
+ looks flickery around the edges. But, revert to the normal
+ way when the mouse button is down lest the user see overlapping
+ polygons.
+ */
+ th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
+ g->motion_blur_p++;
+ }
+ else
+ th = g->th;
+
+ glRotatef (th, 0, 0, 1);
+
+ glPushName (g->id);
+
+ if (! visible_p)
+ mi->polygon_count += draw_involute_schematic (g, wire_p);
+ else
+ {
+ glCallList (g->dlist);
+ mi->polygon_count += g->polygons;
+ }
+
+ glPopName();
+ glPopMatrix();
+}
+
+
+/* Render all gears.
+ */
+static void
+draw_gears (ModeInfo *mi)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ Bool wire_p = MI_IS_WIREFRAME(mi);
+ int i;
+
+ glColor4f (1, 1, 0.8, 1);
+
+ glInitNames();
+
+ for (i = 0; i < pp->ngears; i++)
+ draw_gear (mi, i);
+
+ /* draw a line connecting gears that are, uh, geared. */
+ if (debug_p)
+ {
+ static const GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
+ GLfloat off = 0.1;
+ GLfloat ox=0, oy=0, oz=0;
+
+ if (!wire_p) glDisable(GL_LIGHTING);
+ glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
+ glColor3f (color[0], color[1], color[2]);
+
+ for (i = 0; i < pp->ngears; i++)
+ {
+ gear *g = pp->gears[i];
+ glBegin(GL_LINE_STRIP);
+ glVertex3f (g->x, g->y, g->z - off);
+ glVertex3f (g->x, g->y, g->z + off);
+ if (i > 0 && !g->base_p)
+ glVertex3f (ox, oy, oz + off);
+ glEnd();
+ ox = g->x;
+ oy = g->y;
+ oz = g->z;
+ }
+ if (!wire_p) glEnable(GL_LIGHTING);
+ }
+}
+
+
+/* Mouse hit detection
+ */
+static void
+find_mouse_gear (ModeInfo *mi)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+
+# ifndef HAVE_JWZGLES
+
+ int screen_width = MI_WIDTH (mi);
+ int screen_height = MI_HEIGHT (mi);
+ GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
+ int x, y;
+ int hits;
+
+ pp->mouse_gear_id = 0;
+
+ /* Poll mouse position */
+ {
+ Window r, c;
+ int rx, ry;
+ unsigned int m;
+ XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
+ &r, &c, &rx, &ry, &x, &y, &m);
+ }
+
+ if (x < 0 || y < 0 || x > screen_width || y > screen_height)
+ return; /* out of window */
+
+ /* Run OpenGL hit detector */
+ {
+ GLint vp[4];
+ GLuint selbuf[512];
+
+ glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
+ glRenderMode (GL_SELECT);
+ glMatrixMode (GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
+ gluPickMatrix (x, vp[3]-y, 5, 5, vp);
+ gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
+ glMatrixMode (GL_MODELVIEW);
+
+ draw_gears (mi); /* render into "select" buffer */
+
+ glMatrixMode (GL_PROJECTION); /* restore old vp */
+ glPopMatrix ();
+ glMatrixMode (GL_MODELVIEW);
+ glFlush();
+ hits = glRenderMode (GL_RENDER); /* done selecting */
+
+ if (hits > 0)
+ {
+ int i;
+ GLuint name_count = 0;
+ GLuint *p = (GLuint *) selbuf;
+ GLuint *pnames = 0;
+ GLuint min_z = ~0;
+
+ for (i = 0; i < hits; i++)
+ {
+ int names = *p++;
+ if (*p < min_z) /* find match closest to screen */
+ {
+ name_count = names;
+ min_z = *p;
+ pnames = p+2;
+ }
+ p += names+2;
+ }
+
+ if (name_count > 0) /* take first hit */
+ pp->mouse_gear_id = pnames[0];
+ }
+ }
+
+#else /* HAVE_JWZGLES */
+ /* #### not yet implemented */
+ pp->mouse_gear_id = (pp->ngears > 1 ? pp->gears[1]->id : 0);
+ return;
+#endif /* HAVE_JWZGLES */
+
+
+}
+
+
+/* Window management, etc
+ */
+ENTRYPOINT void
+reshape_pinion (ModeInfo *mi, int width, int height)
+{
+ pinion_configuration *pp = &pps[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);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ {
+ GLfloat render_width, layout_width;
+ pp->vp_height = 1.0;
+ pp->vp_width = 1/h;
+
+ pp->vp_left = -pp->vp_width/2;
+ pp->vp_right = pp->vp_width/2;
+ pp->vp_top = pp->vp_height/2;
+ pp->vp_bottom = -pp->vp_height/2;
+
+ render_width = pp->vp_width * 2;
+ layout_width = pp->vp_width * 0.8 * gear_size;
+
+ pp->render_left = -render_width/2;
+ pp->render_right = render_width/2;
+
+ pp->layout_left = pp->render_right;
+ pp->layout_right = pp->layout_left + layout_width;
+ }
+}
+
+
+ENTRYPOINT Bool
+pinion_handle_event (ModeInfo *mi, XEvent *event)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+
+ if (gltrackball_event_handler (event, pp->trackball,
+ MI_WIDTH (mi), MI_HEIGHT (mi),
+ &pp->button_down_p))
+ return True;
+ else if (event->xany.type == KeyPress)
+ {
+ KeySym keysym;
+ char c = 0;
+ XLookupString (&event->xkey, &c, 1, &keysym, 0);
+ if (c == ' ' && debug_one_gear_p && pp->ngears)
+ {
+ delete_gear (mi, pp->gears[0]);
+ return True;
+ }
+ }
+
+ return False;
+}
+
+
+ENTRYPOINT void
+init_pinion (ModeInfo *mi)
+{
+ pinion_configuration *pp;
+
+ MI_INIT (mi, pps);
+
+ pp = &pps[MI_SCREEN(mi)];
+
+ pp->glx_context = init_GL(mi);
+
+ load_fonts (mi);
+ reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+ clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
+
+ pp->ngears = 0;
+ pp->gears_size = 0;
+ pp->gears = 0;
+
+ pp->plane_displacement = gear_size * 0.1;
+
+ pp->trackball = gltrackball_init (False);
+
+ ffwd (mi);
+}
+
+
+ENTRYPOINT void
+draw_pinion (ModeInfo *mi)
+{
+ pinion_configuration *pp = &pps[MI_SCREEN(mi)];
+ Display *dpy = MI_DISPLAY(mi);
+ Window window = MI_WINDOW(mi);
+ Bool wire_p = MI_IS_WIREFRAME(mi);
+
+ if (!pp->glx_context)
+ return;
+
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
+
+ glPushMatrix();
+ glRotatef(current_device_rotation(), 0, 0, 1);
+
+ if (!wire_p)
+ {
+ GLfloat pos[4] = {-3.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] = { 1.0, 1.0, 1.0, 1.0};
+
+ glEnable(GL_LIGHTING);
+ glEnable(GL_LIGHT0);
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+
+ glLightfv(GL_LIGHT0, GL_POSITION, pos);
+ glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
+ glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
+ }
+
+ if (!pp->button_down_p)
+ {
+ if (!debug_one_gear_p || pp->ngears == 0)
+ scroll_gears (mi);
+ spin_gears (mi);
+ }
+
+ glShadeModel(GL_SMOOTH);
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_NORMALIZE);
+ glEnable(GL_CULL_FACE);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glPushMatrix ();
+ {
+ gltrackball_rotate (pp->trackball);
+ mi->polygon_count = 0;
+
+ glScalef (16, 16, 16); /* map vp_width/height to the screen */
+
+ if (debug_one_gear_p) /* zoom in */
+ glScalef (3, 3, 3);
+ else if (debug_p) /* show the "visible" and "layout" areas */
+ {
+ GLfloat ow = pp->layout_right - pp->render_left;
+ GLfloat rw = pp->render_right - pp->render_left;
+ GLfloat s = (pp->vp_width / ow) * 0.85;
+ glScalef (s, s, s);
+ glTranslatef (-(ow - rw) / 2, 0, 0);
+ }
+ else
+ {
+ GLfloat s = 1.2;
+ glScalef (s, s, s); /* zoom in a little more */
+ glRotatef (-35, 1, 0, 0); /* tilt back */
+ glRotatef ( 8, 0, 1, 0); /* tilt left */
+ glTranslatef (0.02, 0.1, 0); /* pan up */
+ }
+
+ draw_gears (mi);
+
+ if (debug_p)
+ {
+ if (!wire_p) glDisable(GL_LIGHTING);
+ glColor3f (0.6, 0, 0);
+ glBegin(GL_LINE_LOOP);
+ glVertex3f (pp->render_left, pp->vp_top, 0);
+ glVertex3f (pp->render_right, pp->vp_top, 0);
+ glVertex3f (pp->render_right, pp->vp_bottom, 0);
+ glVertex3f (pp->render_left, pp->vp_bottom, 0);
+ glEnd();
+ glColor3f (0.4, 0, 0);
+ glBegin(GL_LINES);
+ glVertex3f (pp->vp_left, pp->vp_top, 0);
+ glVertex3f (pp->vp_left, pp->vp_bottom, 0);
+ glVertex3f (pp->vp_right, pp->vp_top, 0);
+ glVertex3f (pp->vp_right, pp->vp_bottom, 0);
+ glEnd();
+ glColor3f (0, 0.4, 0);
+ glBegin(GL_LINE_LOOP);
+ glVertex3f (pp->layout_left, pp->vp_top, 0);
+ glVertex3f (pp->layout_right, pp->vp_top, 0);
+ glVertex3f (pp->layout_right, pp->vp_bottom, 0);
+ glVertex3f (pp->layout_left, pp->vp_bottom, 0);
+ glEnd();
+ if (!wire_p) glEnable(GL_LIGHTING);
+ }
+
+ if (pp->draw_tick++ > 10) /* only do this every N frames */
+ {
+ pp->draw_tick = 0;
+ find_mouse_gear (mi);
+ }
+ }
+ glPopMatrix ();
+
+ draw_label (mi);
+ glPopMatrix ();
+
+ if (mi->fps_p) do_fps (mi);
+ glFinish();
+
+ glXSwapBuffers(dpy, window);
+}
+
+XSCREENSAVER_MODULE ("Pinion", pinion)
+
+#endif /* USE_GL */