/* 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 release_hydrostat 0

#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"


#define TENTACLE_FACES 5

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;
  GLfloat cos_sin_table[2 * (TENTACLE_FACES + 1)];
  GLuint head;
  int head_polys;
# 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)
{
  hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
  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);

  glCallList (bp->head);
  mi->polygon_count += bp->head_polys;

  glPopMatrix();
}


static void
draw_squid (ModeInfo *mi, squid *sq)
{
  hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
  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);

  if (!wire) {
    glFrontFace (GL_CCW);
    glBegin (GL_TRIANGLE_STRIP);
  }

  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;
          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;
              GLfloat L2 = sqrt (X*X + Y*Y);

              for (k = 0; k <= TENTACLE_FACES; k++)
                {
                  GLfloat c = bp->cos_sin_table[2 * k];
                  GLfloat s = bp->cos_sin_table[2 * k + 1];
                  GLfloat x1 = radius * c;
                  GLfloat y1 = radius * s;
                  GLfloat z1 = 0;
                  GLfloat x2 = r2 * c;
                  GLfloat y2 = r2 * s;
                  GLfloat z2 = L;

                  GLfloat x1t = (L2*X*z1-X*Z*y1+L*Y*x1)/(L*L2);
                  GLfloat z1t = (L2*Y*z1-Y*Z*y1-L*X*x1)/(L*L2);
                  GLfloat y1t = (Z*z1+L2*y1)/L;

                  GLfloat x2t = (L2*X*z2-X*Z*y2+L*Y*x2)/(L*L2) + n1->pos.x;
                  GLfloat z2t = (L2*Y*z2-Y*Z*y2-L*X*x2)/(L*L2) + n1->pos.y;
                  GLfloat y2t = (Z*z2+L2*y2)/L + n1->pos.z;

                  glNormal3f (x1t, z1t, y1t);

                  x1t += n1->pos.x;
                  z1t += n1->pos.y;
                  y1t += n1->pos.z;

                  if (k == 0)
                    glVertex3f (x1t, z1t, y1t);
                  glVertex3f (x1t, z1t, y1t);

                  glVertex3f (x2t, z2t, y2t);
                  if (k == TENTACLE_FACES)
                    glVertex3f (x2t, z2t, y2t);

                  mi->polygon_count++;
                }
              radius = r2;
            }
        }
    }

  if (!wire)
    glEnd ();

  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)
{
  squid * const *a = aa;
  squid * const *b = 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);

  {
    GLfloat s = (MI_WIDTH(mi) < MI_HEIGHT(mi)
                 ? (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi))
                 : 1);
    glScalef (s, s, s);
  }

  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;

  MI_INIT (mi, bps);

  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};
      int k;

      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);

      for (k = 0; k <= TENTACLE_FACES; k++)
        {
          GLfloat th  = k * M_PI * 2 / TENTACLE_FACES;
          bp->cos_sin_table[2 * k] = cos(th);
          bp->cos_sin_table[2 * k + 1] = sin(th);
        }
    }

  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);
    }

  i = wire ? 4 : 24;
  bp->head = glGenLists (1);
  glNewList (bp->head, GL_COMPILE);
  glScalef (1, 1.1, 1);
  bp->head_polys = unit_dome (wire ? 8 : 16, i, wire);
  glRotatef (180, 0, 0, 1);
  glScalef (1, 0.5, 1);
  bp->head_polys += unit_dome (wire ? 8 : 8, i, wire);
  glEndList ();

# 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
free_hydrostat (ModeInfo *mi)
{
  hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
  int i;

  if (!bp->glx_context) return;
  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);

# ifdef USE_TRACKBALL
  if (bp->trackball) gltrackball_free (bp->trackball);
# endif
  if (bp->squids)
    {
      for (i = 0; i < MI_COUNT(mi); i++)
        free_squid (bp->squids[i]);
      free (bp->squids);
    }

  if (glIsList(bp->head)) glDeleteLists(bp->head, 1);
}

XSCREENSAVER_MODULE ("Hydrostat", hydrostat)

#endif /* USE_GL */
