From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / glx / hydrostat.c
diff --git a/hacks/glx/hydrostat.c b/hacks/glx/hydrostat.c
new file mode 100644 (file)
index 0000000..ea3de9d
--- /dev/null
@@ -0,0 +1,778 @@
+/* hydrostat, Copyright (C) 2012 by Justin Windle
+ * Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Tentacle simulation using inverse kinematics.
+ *
+ *   http://soulwire.co.uk/experiments/muscular-hydrostats/
+ *   https://github.com/soulwire/Muscular-Hydrostats/
+ *
+ * Ported to C from Javascript by jwz, May 2016
+ */
+
+#define DEFAULTS       "*delay:        20000       \n" \
+                       "*count:        3           \n" \
+                       "*showFPS:      False       \n" \
+                       "*wireframe:    False       \n" \
+                       "*suppressRotationAnimation: True\n" \
+
+# define refresh_hydrostat 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#include "xlockmore.h"
+#include "colors.h"
+#include "sphere.h"
+#include "normals.h"
+#include "gltrackball.h"
+#include <ctype.h>
+
+#ifdef USE_GL /* whole file */
+
+/* It looks bad when you rotate it with the trackball, because it reveals
+   that the tentacles are moving in a 2d plane.  But it's useful for
+   debugging. */
+#undef USE_TRACKBALL
+
+#define DEF_SPEED       "1.0"
+#define DEF_PULSE       "True"
+#define DEF_HEAD_RADIUS "60"
+#define DEF_TENTACLES   "35"
+#define DEF_THICKNESS   "18"
+#define DEF_LENGTH      "55"
+#define DEF_GRAVITY     "0.5"
+#define DEF_CURRENT     "0.25"
+#define DEF_FRICTION    "0.02"
+#define DEF_OPACITY     "0.8"
+
+
+typedef struct {
+  XYZ pos, opos, v;
+} node;
+
+typedef struct {
+  int length;
+  GLfloat radius;
+  GLfloat spacing;
+  GLfloat friction;
+  GLfloat th;
+  node *nodes;
+  GLfloat color[4];
+} tentacle;
+
+typedef struct {
+  XYZ pos, from, to;
+  GLfloat ratio, pulse, rate;
+  GLfloat head_radius;
+  GLfloat thickness;
+  int ntentacles;
+  tentacle *tentacles;
+  GLfloat color[4];
+} squid;
+
+typedef struct {
+  GLXContext *glx_context;
+  Bool button_down_p;
+  int dragging;
+  squid **squids;
+# ifdef USE_TRACKBALL
+  trackball_state *trackball;
+# endif
+} hydrostat_configuration;
+
+static hydrostat_configuration *bps = NULL;
+
+static Bool do_pulse;
+static GLfloat speed_arg;
+static GLfloat head_radius_arg;
+static GLfloat ntentacles_arg;
+static GLfloat thickness_arg;
+static GLfloat length_arg;
+static GLfloat gravity_arg;
+static GLfloat current_arg;
+static GLfloat friction_arg;
+static GLfloat opacity_arg;
+
+static XrmOptionDescRec opts[] = {
+  { "-pulse",       ".pulse",      XrmoptionNoArg, "True"  },
+  { "+pulse",       ".pulse",      XrmoptionNoArg, "False" },
+  { "-speed",       ".speed",      XrmoptionSepArg, 0 },
+  { "-head-radius", ".headRadius", XrmoptionSepArg, 0 },
+  { "-tentacles",   ".tentacles",  XrmoptionSepArg, 0 },
+  { "-thickness",   ".thickness",  XrmoptionSepArg, 0 },
+  { "-length",      ".length",     XrmoptionSepArg, 0 },
+  { "-gravity",     ".gravity",    XrmoptionSepArg, 0 },
+  { "-current",     ".current",    XrmoptionSepArg, 0 },
+  { "-friction",    ".friction",   XrmoptionSepArg, 0 },
+  { "-opacity",     ".opacity",    XrmoptionSepArg, 0 },
+};
+
+static argtype vars[] = {
+  { &do_pulse,        "pulse",      "Pulse",      DEF_PULSE,       t_Bool  },
+  { &speed_arg,       "speed",      "Speed",      DEF_SPEED,       t_Float },
+  { &head_radius_arg, "headRadius", "HeadRadius", DEF_HEAD_RADIUS, t_Float },
+  { &ntentacles_arg,  "tentacles",  "Tentacles",  DEF_TENTACLES,   t_Float },
+  { &thickness_arg,   "thickness",  "Thickness",  DEF_THICKNESS,   t_Float },
+  { &length_arg,      "length",     "Length",     DEF_LENGTH,      t_Float },
+  { &gravity_arg,     "gravity",    "Gravity",    DEF_GRAVITY,     t_Float },
+  { &current_arg,     "current",    "Current",    DEF_CURRENT,     t_Float },
+  { &friction_arg,    "friction",   "Friction",   DEF_FRICTION,    t_Float },
+  { &opacity_arg,     "opacity",    "Opacity",    DEF_OPACITY,     t_Float },
+};
+
+ENTRYPOINT ModeSpecOpt hydrostat_opts = {countof(opts), opts,
+                                         countof(vars), vars, NULL};
+
+
+static void
+move_tentacle (squid *sq, tentacle *t)
+{
+  int i, j;
+  node *prev = &t->nodes[0];
+  int rot = (int) current_device_rotation();
+
+  for (i = 1, j = 0; i < t->length; i++, j++)
+    {
+      XYZ d, p;
+      GLfloat da;
+      node *n = &t->nodes[i];
+
+      /* Sadly, this is still computing motion in a 2d plane, so the
+         tentacles look dumb if the scene is rotated. */
+
+      n->pos.x += n->v.x;
+      n->pos.y += n->v.y;
+      n->pos.z += n->v.z;
+
+      d.x = prev->pos.x - n->pos.x;
+      d.y = prev->pos.y - n->pos.y;
+      d.z = prev->pos.z - n->pos.z;
+      da = atan2 (d.z, d.x);
+
+      p.x = n->pos.x + cos (da) * t->spacing * t->length;
+      p.y = n->pos.y + cos (da) * t->spacing * t->length;
+      p.z = n->pos.z + sin (da) * t->spacing * t->length;
+
+      n->pos.x = prev->pos.x - (p.x - n->pos.x);
+      n->pos.y = prev->pos.y - (p.y - n->pos.y);
+      n->pos.z = prev->pos.z - (p.z - n->pos.z);
+
+      n->v.x = n->pos.x - n->opos.x;
+      n->v.y = n->pos.y - n->opos.y;
+      n->v.z = n->pos.z - n->opos.z;
+
+      n->v.x *= t->friction * (1 - friction_arg);
+      n->v.y *= t->friction * (1 - friction_arg);
+      n->v.z *= t->friction * (1 - friction_arg);
+
+      switch (rot) {
+      case 90: case -270:
+        n->v.x += gravity_arg;
+        n->v.y -= current_arg;
+        n->v.z -= current_arg;
+        break;
+      case -90: case 270:
+        n->v.x -= gravity_arg;
+        n->v.y += current_arg;
+        n->v.z += current_arg;
+        break;
+      case 180: case -180:
+        n->v.x -= current_arg;
+        n->v.y -= current_arg;
+        n->v.z -= gravity_arg;
+        break;
+      default:
+        n->v.x += current_arg;
+        n->v.y += current_arg;
+        n->v.z += gravity_arg;
+        break;
+      }
+
+      n->opos.x = n->pos.x;
+      n->opos.y = n->pos.y;
+      n->opos.z = n->pos.z;
+
+      prev = n;
+    }
+}
+
+
+static GLfloat
+ease_fn (GLfloat r)
+{
+  return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
+}
+
+
+/* Squirty motion: fast acceleration, then fade. */
+static GLfloat
+ease_ratio (GLfloat r)
+{
+  GLfloat ease = 0.05;
+  GLfloat ease2 = 1-ease;
+  if      (r <= 0)     r = 0;
+  else if (r >= 1)     r = 1;
+  else if (r <= ease)  r =     ease  * ease_fn (r / ease);
+  else                 r = 1 - ease2 * ease_fn ((1 - r) / ease2);
+  return r;
+}
+
+
+static void
+move_squid (ModeInfo *mi, squid *sq)
+{
+  hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
+  GLfloat step = M_PI * 2 / sq->ntentacles;
+  int i;
+  GLfloat radius = head_radius_arg;
+  GLfloat r;
+
+  /* Move to a new position */
+
+  if (! bp->button_down_p)
+    {
+      sq->ratio += speed_arg * 0.01;
+      if (sq->ratio >= 1)
+        {
+          sq->ratio = -(frand(2.0) + frand(2.0) + frand(2.0));
+          sq->from.x = sq->to.x;
+          sq->from.y = sq->to.y;
+          sq->from.z = sq->to.z;
+          sq->to.x = 250 - frand(500);
+          sq->to.y = 250 - frand(500);
+          sq->to.z = 250 - frand(500);
+        }
+
+      r = sq->ratio > 0 ? ease_ratio (sq->ratio) : 0;
+      sq->pos.x = sq->from.x + r * (sq->to.x - sq->from.x);
+      sq->pos.y = sq->from.y + r * (sq->to.y - sq->from.y);
+      sq->pos.z = sq->from.z + r * (sq->to.z - sq->from.z);
+    }
+
+  if (do_pulse)
+    {
+      GLfloat p = pow (sin (sq->pulse * M_PI), 18);
+      sq->head_radius = (head_radius_arg * 0.7 +
+                         head_radius_arg * 0.3 * p);
+      radius = sq->head_radius * 0.25;
+      sq->pulse += sq->rate * speed_arg * 0.02;
+      if (sq->pulse > 1) sq->pulse = 0;
+    }
+
+  for (i = 0; i < sq->ntentacles; i++)
+    {
+      tentacle *tt = &sq->tentacles[i];
+      GLfloat th = i * step;
+      GLfloat px = cos (th) * radius;
+      GLfloat py = sin (th) * radius;
+      tt->th = th;
+      tt->nodes[0].pos.x = sq->pos.x + px;
+      tt->nodes[0].pos.y = sq->pos.y + py;
+      tt->nodes[0].pos.z = sq->pos.z;
+      move_tentacle (sq, tt);
+    }
+}
+
+
+/* Find the angle at which the head should be tilted in the XY plane.
+ */
+static GLfloat
+head_angle (ModeInfo *mi, squid *sq)
+{
+  int i;
+  XYZ sum = { 0, };
+
+  for (i = 0; i < sq->ntentacles; i++)
+    {
+      tentacle *t = &sq->tentacles[i];
+      int j = t->length / 3;  /* Pick a node toward the top */
+      node *n = &t->nodes[j];
+      sum.x += n->pos.x;
+      sum.y += n->pos.y;
+      sum.z += n->pos.z;
+    }
+
+  sum.x /= sq->ntentacles;
+  sum.y /= sq->ntentacles;
+  sum.z /= sq->ntentacles;
+
+  sum.x -= sq->pos.x;
+  sum.y -= sq->pos.y;
+  sum.z -= sq->pos.z;
+
+  return (-atan2 (sum.x, sum.z) * (180 / M_PI));
+}
+
+
+static void
+draw_head (ModeInfo *mi, squid *sq, GLfloat scale)
+{
+  int wire = MI_IS_WIREFRAME(mi);
+  int i = wire ? 8 : 64;
+  GLfloat c2[4];
+  GLfloat angle = head_angle (mi, sq);
+
+  scale *= 1.1;
+
+  glPushMatrix();
+  glTranslatef (sq->pos.x, sq->pos.y, sq->pos.z);
+  glScalef (sq->head_radius, sq->head_radius, sq->head_radius);
+  glScalef (scale, scale, scale);
+  glRotatef (90, 1, 0, 0);
+
+  memcpy (c2, sq->color, sizeof(c2));
+  if (opacity_arg < 1.0 && scale >= 1.0)
+    c2[3] *= 0.6;
+  glColor4fv (c2);
+  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c2);
+
+  glTranslatef (0, 0.3, 0);
+  glRotatef (angle, 0, 0, 1);
+
+  glScalef (1, 1.1, 1);
+  unit_dome (i, i/2, wire);
+  glRotatef (180, 0, 0, 1);
+  glScalef (1, 0.5, 1);
+  unit_dome (i, i/2, wire);
+
+  mi->polygon_count += i * i;
+  glPopMatrix();
+}
+
+
+static void
+draw_squid (ModeInfo *mi, squid *sq)
+{
+  int wire = MI_IS_WIREFRAME(mi);
+  int i;
+  glPushMatrix();
+  glRotatef (90, 1, 0, 0);
+
+  if (opacity_arg < 1.0)
+    draw_head (mi, sq, 0.75);
+
+  for (i = 0; i < sq->ntentacles; i++)
+    {
+      tentacle *t = &sq->tentacles[i];
+      int j;
+
+      glColor4fv (t->color);
+      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, t->color);
+
+      if (wire)
+        {
+          glBegin (GL_LINE_STRIP);
+          for (j = 0; j < t->length; j++)
+            glVertex3f (t->nodes[j].pos.x,
+                        t->nodes[j].pos.y,
+                        t->nodes[j].pos.z);
+          glEnd();
+          mi->polygon_count += t->length;
+        }
+      else
+        {
+          GLfloat radius = t->radius * thickness_arg;
+          GLfloat rstep = radius / t->length;
+          int faces = 8;
+          glFrontFace (GL_CCW);
+          for (j = 0; j < t->length-1; j++)
+            {
+              int k;
+              node *n1 = &t->nodes[j];
+              node *n2 = &t->nodes[j+1];
+              GLfloat X = (n1->pos.x - n2->pos.x);
+              GLfloat Y = (n1->pos.y - n2->pos.y);
+              GLfloat Z = (n1->pos.z - n2->pos.z);
+              GLfloat L = sqrt (X*X + Y*Y + Z*Z);
+              GLfloat r2 = radius - rstep;
+
+              glPushMatrix();
+              glTranslatef (n1->pos.x, n1->pos.y, n1->pos.z);
+              glRotatef (-atan2 (X, Y)               * (180 / M_PI), 0, 0, 1);
+              glRotatef ( atan2 (Z, sqrt(X*X + Y*Y)) * (180 / M_PI), 1, 0, 0);
+
+              glBegin (wire ? GL_LINE_LOOP : GL_QUAD_STRIP);
+              for (k = 0; k <= faces; k++)
+                {
+                  GLfloat th  = k * M_PI * 2 / faces;
+                  GLfloat c = cos(th);
+                  GLfloat s = sin(th);
+                  GLfloat x1 = radius * c;
+                  GLfloat y1 = radius * s;
+                  GLfloat z1 = 0;
+                  GLfloat x2 = r2 * c;
+                  GLfloat y2 = r2 * s;
+                  GLfloat z2 = L;
+
+                  glNormal3f (x1, z1, y1);
+                  glVertex3f (x1, z1, y1);
+                  glVertex3f (x2, z2, y2);
+                  mi->polygon_count++;
+                }
+              glEnd();
+              glPopMatrix();
+              radius = r2;
+            }
+        }
+    }
+
+  draw_head (mi, sq, 1.0);
+
+  glPopMatrix();
+}
+
+
+static squid *
+make_squid (ModeInfo *mi, int which)
+{
+  squid *sq = calloc (1, sizeof(*sq));
+  int i;
+
+  sq->head_radius = head_radius_arg;
+  sq->thickness   = thickness_arg;
+  sq->ntentacles  = ntentacles_arg;
+
+  sq->color[0] = 0.1 + frand(0.7);
+  sq->color[1] = 0.5 + frand(0.5);
+  sq->color[2] = 0.1 + frand(0.7);
+  sq->color[3] = opacity_arg;
+
+  sq->from.x = sq->to.x = sq->pos.x = 200 - frand(400);
+  sq->from.y = sq->to.y = sq->pos.y = 200 - frand(400);
+  sq->from.z = sq->to.z = sq->pos.z = -frand(200);
+
+  sq->ratio = -frand(3);
+
+  if (which > 0)   /* Start others off screen, and moving in */
+    {
+      sq->from.x = sq->to.x = sq->pos.x = 800 + frand(500)
+        * (random()&1 ? 1 : -1);
+      sq->from.y = sq->to.y = sq->pos.y = 800 + frand(500)
+        * (random()&1 ? 1 : -1);
+      sq->ratio = 0;
+    }
+
+  if (do_pulse)
+    sq->pulse = frand(1.0);
+  sq->rate = 0.8 + frand(0.2);
+
+  sq->tentacles = (tentacle *)
+    calloc (sq->ntentacles, sizeof(*sq->tentacles));
+  for (i = 0; i < sq->ntentacles; i++)
+    {
+      int j;
+      tentacle *t = &sq->tentacles[i];
+      GLfloat shade = 0.75 + frand(0.25);
+
+      t->length   = 2 + length_arg * (0.8 + frand (0.4));
+      t->radius   = 0.05 + frand (0.95);
+      t->spacing  = 0.02 + frand (0.08);
+      t->friction = 0.7  + frand (0.18);
+      t->nodes = (node *) calloc (t->length + 1, sizeof (*t->nodes));
+
+      t->color[0] = shade * sq->color[0];
+      t->color[1] = shade * sq->color[1];
+      t->color[2] = shade * sq->color[2];
+      t->color[3] = sq->color[3];
+
+      for (j = 0; j < t->length; j++)
+        {
+          node *n = &t->nodes[j];
+          n->pos.x = sq->pos.x;
+          n->pos.y = sq->pos.y;
+          n->pos.z = sq->pos.z + j;
+        }
+    }
+
+  return sq;
+}
+
+/* qsort comparator for sorting squid by depth */
+static int
+cmp_squid (const void *aa, const void *bb)
+{
+  const squid *a = (squid *) aa;
+  const squid *b = (squid *) bb;
+  return ((int) (b->pos.y * 10000) -
+          (int) (a->pos.y * 10000));
+}
+
+
+static void
+free_squid (squid *sq)
+{
+  int i;
+  for (i = 0; i < sq->ntentacles; i++)
+    free (sq->tentacles[i].nodes);
+  free (sq->tentacles);
+  free (sq);
+}
+
+
+/* Window management, etc
+ */
+ENTRYPOINT void
+reshape_hydrostat (ModeInfo *mi, int width, int height)
+{
+  GLfloat h = (GLfloat) height / (GLfloat) width;
+
+  glViewport (0, 0, (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);
+}
+
+
+
+ENTRYPOINT Bool
+hydrostat_handle_event (ModeInfo *mi, XEvent *event)
+{
+  hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
+  int w = MI_WIDTH(mi);
+  int h = MI_HEIGHT(mi);
+  int x, y;
+
+# ifdef USE_TRACKBALL
+  if (gltrackball_event_handler (event, bp->trackball,
+                                 MI_WIDTH (mi), MI_HEIGHT (mi),
+                                 &bp->button_down_p))
+    return True;
+# endif
+
+  switch (event->xany.type) {
+  case ButtonPress: case ButtonRelease:
+    x = event->xbutton.x;
+    y = event->xbutton.y;
+    break;
+  case MotionNotify:
+    x = event->xmotion.x;
+    y = event->xmotion.y;
+    break;
+  default:
+    x = y = 0;
+  }
+
+  x -= w/2;
+  y -= h/2;
+  x *= 0.7;
+  y *= 0.7;
+
+  if (event->xany.type == ButtonPress)
+    {
+      int i;
+      GLfloat D0 = 999999;
+
+      /* This is pretty halfassed hit detection, but it works ok... */
+      for (i = 0; i < MI_COUNT(mi); i++)
+        {
+          squid *s = bp->squids[i];
+          GLfloat X = s->pos.x - x;
+          GLfloat Y = s->pos.z - y;
+          GLfloat D = sqrt(X*X + Y*Y);
+          if (D < D0)
+            {
+              bp->dragging = i;
+              D0 = D;
+            }
+        }
+
+      if (D0 > 300)    /* Too far away, missed hit */
+        {
+          bp->dragging = -1;
+          return False;
+        }
+
+      bp->squids[bp->dragging]->ratio = -3;
+      bp->button_down_p = True;
+
+      return True;
+    }
+  else if (event->xany.type == ButtonRelease && bp->dragging >= 0)
+    {
+      bp->button_down_p = False;
+      bp->dragging = -1;
+      return True;
+    }
+  else if (event->xany.type == MotionNotify && bp->dragging >= 0)
+    {
+      squid *s = bp->squids[bp->dragging];
+      s->from.x = s->to.x = s->pos.x = x;
+      s->from.z = s->to.z = s->pos.z = y;
+      s->from.y = s->to.y = s->pos.y;
+      return True;
+    }
+
+  return False;
+}
+
+
+ENTRYPOINT void 
+init_hydrostat (ModeInfo *mi)
+{
+  int wire = MI_IS_WIREFRAME(mi);
+  hydrostat_configuration *bp;
+  int i;
+
+  if (!bps) {
+    bps = (hydrostat_configuration *)
+      calloc (MI_NUM_SCREENS(mi), sizeof (hydrostat_configuration));
+    if (!bps) {
+      fprintf(stderr, "%s: out of memory\n", progname);
+      exit(1);
+    }
+  }
+
+  bp = &bps[MI_SCREEN(mi)];
+
+  bp->glx_context = init_GL(mi);
+
+  reshape_hydrostat (mi, MI_WIDTH(mi), MI_HEIGHT(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);
+
+      glLightfv(GL_LIGHT0, GL_POSITION, pos);
+      glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
+      glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
+      glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
+    }
+
+  glShadeModel(GL_SMOOTH);
+
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_NORMALIZE);
+
+  if (MI_COUNT(mi) <= 0)
+    MI_COUNT(mi) = 1;
+
+  if (random() & 1)
+    current_arg = -current_arg;
+
+  if (MI_COUNT(mi) == 1 || wire)
+    opacity_arg = 1.0;
+  if (opacity_arg < 0.1) opacity_arg = 0.1;
+  if (opacity_arg > 1.0) opacity_arg = 1.0;
+
+  bp->squids = (squid **) calloc (MI_COUNT(mi), sizeof(*bp->squids));
+  for (i = 0; i < MI_COUNT(mi); i++)
+    bp->squids[i] = make_squid (mi, i);
+
+  bp->dragging = -1;
+
+  if (opacity_arg < 1.0)
+    {
+      glEnable (GL_BLEND);
+      glBlendFunc (GL_SRC_ALPHA, GL_ONE);
+    }
+
+# ifdef USE_TRACKBALL
+  bp->trackball = gltrackball_init (True);
+# endif
+}
+
+
+ENTRYPOINT void
+draw_hydrostat (ModeInfo *mi)
+{
+  hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
+  Display *dpy = MI_DISPLAY(mi);
+  Window window = MI_WINDOW(mi);
+  int i;
+
+  if (!bp->glx_context)
+    return;
+
+  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
+
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  glPushMatrix ();
+
+  glScalef (0.03, 0.03, 0.03);
+
+# ifdef USE_TRACKBALL
+  gltrackball_rotate (bp->trackball);
+# endif
+
+  mi->polygon_count = 0;
+
+  if (opacity_arg < 1.0)
+    qsort (bp->squids, MI_COUNT(mi), sizeof(*bp->squids), cmp_squid);
+
+  for (i = 0; i < MI_COUNT(mi); i++)
+    {
+      squid *sq = bp->squids[i];
+      move_squid (mi, sq);
+      draw_squid (mi, sq);
+      if (opacity_arg < 1.0)
+        glClear (GL_DEPTH_BUFFER_BIT);
+    }
+
+  if (! (random() % 700))  /* Reverse the flow every now and then */
+    current_arg = -current_arg;
+
+  glPopMatrix ();
+
+  if (mi->fps_p) do_fps (mi);
+  glFinish();
+
+  glXSwapBuffers(dpy, window);
+}
+
+
+ENTRYPOINT void
+release_hydrostat (ModeInfo *mi)
+{
+  hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
+  int i;
+  for (i = 0; i < MI_COUNT(mi); i++)
+    free_squid (bp->squids[i]);
+  free (bp->squids);
+}
+
+XSCREENSAVER_MODULE ("Hydrostat", hydrostat)
+
+#endif /* USE_GL */