X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fglx%2Fhydrostat.c;fp=hacks%2Fglx%2Fhydrostat.c;h=ea3de9dfb7df595d2629166c5d34d158128ce56e;hp=0000000000000000000000000000000000000000;hb=aa75c7476aeaa84cf3abc192b376a8b03c325213;hpb=88cfe534a698a0562e81345957a50714af1453bc diff --git a/hacks/glx/hydrostat.c b/hacks/glx/hydrostat.c new file mode 100644 index 00000000..ea3de9df --- /dev/null +++ b/hacks/glx/hydrostat.c @@ -0,0 +1,778 @@ +/* hydrostat, Copyright (C) 2012 by Justin Windle + * Copyright (c) 2016 Jamie Zawinski + * + * 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 + +#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 }, + { ¤t_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 */