From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / glx / hydrostat.c
1 /* hydrostat, Copyright (C) 2012 by Justin Windle
2  * Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  *
22  * Tentacle simulation using inverse kinematics.
23  *
24  *   http://soulwire.co.uk/experiments/muscular-hydrostats/
25  *   https://github.com/soulwire/Muscular-Hydrostats/
26  *
27  * Ported to C from Javascript by jwz, May 2016
28  */
29
30 #define DEFAULTS        "*delay:        20000       \n" \
31                         "*count:        3           \n" \
32                         "*showFPS:      False       \n" \
33                         "*wireframe:    False       \n" \
34                         "*suppressRotationAnimation: True\n" \
35
36 # define refresh_hydrostat 0
37 #undef countof
38 #define countof(x) (sizeof((x))/sizeof((*x)))
39
40 #include "xlockmore.h"
41 #include "colors.h"
42 #include "sphere.h"
43 #include "normals.h"
44 #include "gltrackball.h"
45 #include <ctype.h>
46
47 #ifdef USE_GL /* whole file */
48
49 /* It looks bad when you rotate it with the trackball, because it reveals
50    that the tentacles are moving in a 2d plane.  But it's useful for
51    debugging. */
52 #undef USE_TRACKBALL
53
54 #define DEF_SPEED       "1.0"
55 #define DEF_PULSE       "True"
56 #define DEF_HEAD_RADIUS "60"
57 #define DEF_TENTACLES   "35"
58 #define DEF_THICKNESS   "18"
59 #define DEF_LENGTH      "55"
60 #define DEF_GRAVITY     "0.5"
61 #define DEF_CURRENT     "0.25"
62 #define DEF_FRICTION    "0.02"
63 #define DEF_OPACITY     "0.8"
64
65
66 typedef struct {
67   XYZ pos, opos, v;
68 } node;
69
70 typedef struct {
71   int length;
72   GLfloat radius;
73   GLfloat spacing;
74   GLfloat friction;
75   GLfloat th;
76   node *nodes;
77   GLfloat color[4];
78 } tentacle;
79
80 typedef struct {
81   XYZ pos, from, to;
82   GLfloat ratio, pulse, rate;
83   GLfloat head_radius;
84   GLfloat thickness;
85   int ntentacles;
86   tentacle *tentacles;
87   GLfloat color[4];
88 } squid;
89
90 typedef struct {
91   GLXContext *glx_context;
92   Bool button_down_p;
93   int dragging;
94   squid **squids;
95 # ifdef USE_TRACKBALL
96   trackball_state *trackball;
97 # endif
98 } hydrostat_configuration;
99
100 static hydrostat_configuration *bps = NULL;
101
102 static Bool do_pulse;
103 static GLfloat speed_arg;
104 static GLfloat head_radius_arg;
105 static GLfloat ntentacles_arg;
106 static GLfloat thickness_arg;
107 static GLfloat length_arg;
108 static GLfloat gravity_arg;
109 static GLfloat current_arg;
110 static GLfloat friction_arg;
111 static GLfloat opacity_arg;
112
113 static XrmOptionDescRec opts[] = {
114   { "-pulse",       ".pulse",      XrmoptionNoArg, "True"  },
115   { "+pulse",       ".pulse",      XrmoptionNoArg, "False" },
116   { "-speed",       ".speed",      XrmoptionSepArg, 0 },
117   { "-head-radius", ".headRadius", XrmoptionSepArg, 0 },
118   { "-tentacles",   ".tentacles",  XrmoptionSepArg, 0 },
119   { "-thickness",   ".thickness",  XrmoptionSepArg, 0 },
120   { "-length",      ".length",     XrmoptionSepArg, 0 },
121   { "-gravity",     ".gravity",    XrmoptionSepArg, 0 },
122   { "-current",     ".current",    XrmoptionSepArg, 0 },
123   { "-friction",    ".friction",   XrmoptionSepArg, 0 },
124   { "-opacity",     ".opacity",    XrmoptionSepArg, 0 },
125 };
126
127 static argtype vars[] = {
128   { &do_pulse,        "pulse",      "Pulse",      DEF_PULSE,       t_Bool  },
129   { &speed_arg,       "speed",      "Speed",      DEF_SPEED,       t_Float },
130   { &head_radius_arg, "headRadius", "HeadRadius", DEF_HEAD_RADIUS, t_Float },
131   { &ntentacles_arg,  "tentacles",  "Tentacles",  DEF_TENTACLES,   t_Float },
132   { &thickness_arg,   "thickness",  "Thickness",  DEF_THICKNESS,   t_Float },
133   { &length_arg,      "length",     "Length",     DEF_LENGTH,      t_Float },
134   { &gravity_arg,     "gravity",    "Gravity",    DEF_GRAVITY,     t_Float },
135   { &current_arg,     "current",    "Current",    DEF_CURRENT,     t_Float },
136   { &friction_arg,    "friction",   "Friction",   DEF_FRICTION,    t_Float },
137   { &opacity_arg,     "opacity",    "Opacity",    DEF_OPACITY,     t_Float },
138 };
139
140 ENTRYPOINT ModeSpecOpt hydrostat_opts = {countof(opts), opts,
141                                          countof(vars), vars, NULL};
142
143
144 static void
145 move_tentacle (squid *sq, tentacle *t)
146 {
147   int i, j;
148   node *prev = &t->nodes[0];
149   int rot = (int) current_device_rotation();
150
151   for (i = 1, j = 0; i < t->length; i++, j++)
152     {
153       XYZ d, p;
154       GLfloat da;
155       node *n = &t->nodes[i];
156
157       /* Sadly, this is still computing motion in a 2d plane, so the
158          tentacles look dumb if the scene is rotated. */
159
160       n->pos.x += n->v.x;
161       n->pos.y += n->v.y;
162       n->pos.z += n->v.z;
163
164       d.x = prev->pos.x - n->pos.x;
165       d.y = prev->pos.y - n->pos.y;
166       d.z = prev->pos.z - n->pos.z;
167       da = atan2 (d.z, d.x);
168
169       p.x = n->pos.x + cos (da) * t->spacing * t->length;
170       p.y = n->pos.y + cos (da) * t->spacing * t->length;
171       p.z = n->pos.z + sin (da) * t->spacing * t->length;
172
173       n->pos.x = prev->pos.x - (p.x - n->pos.x);
174       n->pos.y = prev->pos.y - (p.y - n->pos.y);
175       n->pos.z = prev->pos.z - (p.z - n->pos.z);
176
177       n->v.x = n->pos.x - n->opos.x;
178       n->v.y = n->pos.y - n->opos.y;
179       n->v.z = n->pos.z - n->opos.z;
180
181       n->v.x *= t->friction * (1 - friction_arg);
182       n->v.y *= t->friction * (1 - friction_arg);
183       n->v.z *= t->friction * (1 - friction_arg);
184
185       switch (rot) {
186       case 90: case -270:
187         n->v.x += gravity_arg;
188         n->v.y -= current_arg;
189         n->v.z -= current_arg;
190         break;
191       case -90: case 270:
192         n->v.x -= gravity_arg;
193         n->v.y += current_arg;
194         n->v.z += current_arg;
195         break;
196       case 180: case -180:
197         n->v.x -= current_arg;
198         n->v.y -= current_arg;
199         n->v.z -= gravity_arg;
200         break;
201       default:
202         n->v.x += current_arg;
203         n->v.y += current_arg;
204         n->v.z += gravity_arg;
205         break;
206       }
207
208       n->opos.x = n->pos.x;
209       n->opos.y = n->pos.y;
210       n->opos.z = n->pos.z;
211
212       prev = n;
213     }
214 }
215
216
217 static GLfloat
218 ease_fn (GLfloat r)
219 {
220   return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
221 }
222
223
224 /* Squirty motion: fast acceleration, then fade. */
225 static GLfloat
226 ease_ratio (GLfloat r)
227 {
228   GLfloat ease = 0.05;
229   GLfloat ease2 = 1-ease;
230   if      (r <= 0)     r = 0;
231   else if (r >= 1)     r = 1;
232   else if (r <= ease)  r =     ease  * ease_fn (r / ease);
233   else                 r = 1 - ease2 * ease_fn ((1 - r) / ease2);
234   return r;
235 }
236
237
238 static void
239 move_squid (ModeInfo *mi, squid *sq)
240 {
241   hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
242   GLfloat step = M_PI * 2 / sq->ntentacles;
243   int i;
244   GLfloat radius = head_radius_arg;
245   GLfloat r;
246
247   /* Move to a new position */
248
249   if (! bp->button_down_p)
250     {
251       sq->ratio += speed_arg * 0.01;
252       if (sq->ratio >= 1)
253         {
254           sq->ratio = -(frand(2.0) + frand(2.0) + frand(2.0));
255           sq->from.x = sq->to.x;
256           sq->from.y = sq->to.y;
257           sq->from.z = sq->to.z;
258           sq->to.x = 250 - frand(500);
259           sq->to.y = 250 - frand(500);
260           sq->to.z = 250 - frand(500);
261         }
262
263       r = sq->ratio > 0 ? ease_ratio (sq->ratio) : 0;
264       sq->pos.x = sq->from.x + r * (sq->to.x - sq->from.x);
265       sq->pos.y = sq->from.y + r * (sq->to.y - sq->from.y);
266       sq->pos.z = sq->from.z + r * (sq->to.z - sq->from.z);
267     }
268
269   if (do_pulse)
270     {
271       GLfloat p = pow (sin (sq->pulse * M_PI), 18);
272       sq->head_radius = (head_radius_arg * 0.7 +
273                          head_radius_arg * 0.3 * p);
274       radius = sq->head_radius * 0.25;
275       sq->pulse += sq->rate * speed_arg * 0.02;
276       if (sq->pulse > 1) sq->pulse = 0;
277     }
278
279   for (i = 0; i < sq->ntentacles; i++)
280     {
281       tentacle *tt = &sq->tentacles[i];
282       GLfloat th = i * step;
283       GLfloat px = cos (th) * radius;
284       GLfloat py = sin (th) * radius;
285       tt->th = th;
286       tt->nodes[0].pos.x = sq->pos.x + px;
287       tt->nodes[0].pos.y = sq->pos.y + py;
288       tt->nodes[0].pos.z = sq->pos.z;
289       move_tentacle (sq, tt);
290     }
291 }
292
293
294 /* Find the angle at which the head should be tilted in the XY plane.
295  */
296 static GLfloat
297 head_angle (ModeInfo *mi, squid *sq)
298 {
299   int i;
300   XYZ sum = { 0, };
301
302   for (i = 0; i < sq->ntentacles; i++)
303     {
304       tentacle *t = &sq->tentacles[i];
305       int j = t->length / 3;  /* Pick a node toward the top */
306       node *n = &t->nodes[j];
307       sum.x += n->pos.x;
308       sum.y += n->pos.y;
309       sum.z += n->pos.z;
310     }
311
312   sum.x /= sq->ntentacles;
313   sum.y /= sq->ntentacles;
314   sum.z /= sq->ntentacles;
315
316   sum.x -= sq->pos.x;
317   sum.y -= sq->pos.y;
318   sum.z -= sq->pos.z;
319
320   return (-atan2 (sum.x, sum.z) * (180 / M_PI));
321 }
322
323
324 static void
325 draw_head (ModeInfo *mi, squid *sq, GLfloat scale)
326 {
327   int wire = MI_IS_WIREFRAME(mi);
328   int i = wire ? 8 : 64;
329   GLfloat c2[4];
330   GLfloat angle = head_angle (mi, sq);
331
332   scale *= 1.1;
333
334   glPushMatrix();
335   glTranslatef (sq->pos.x, sq->pos.y, sq->pos.z);
336   glScalef (sq->head_radius, sq->head_radius, sq->head_radius);
337   glScalef (scale, scale, scale);
338   glRotatef (90, 1, 0, 0);
339
340   memcpy (c2, sq->color, sizeof(c2));
341   if (opacity_arg < 1.0 && scale >= 1.0)
342     c2[3] *= 0.6;
343   glColor4fv (c2);
344   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c2);
345
346   glTranslatef (0, 0.3, 0);
347   glRotatef (angle, 0, 0, 1);
348
349   glScalef (1, 1.1, 1);
350   unit_dome (i, i/2, wire);
351   glRotatef (180, 0, 0, 1);
352   glScalef (1, 0.5, 1);
353   unit_dome (i, i/2, wire);
354
355   mi->polygon_count += i * i;
356   glPopMatrix();
357 }
358
359
360 static void
361 draw_squid (ModeInfo *mi, squid *sq)
362 {
363   int wire = MI_IS_WIREFRAME(mi);
364   int i;
365   glPushMatrix();
366   glRotatef (90, 1, 0, 0);
367
368   if (opacity_arg < 1.0)
369     draw_head (mi, sq, 0.75);
370
371   for (i = 0; i < sq->ntentacles; i++)
372     {
373       tentacle *t = &sq->tentacles[i];
374       int j;
375
376       glColor4fv (t->color);
377       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, t->color);
378
379       if (wire)
380         {
381           glBegin (GL_LINE_STRIP);
382           for (j = 0; j < t->length; j++)
383             glVertex3f (t->nodes[j].pos.x,
384                         t->nodes[j].pos.y,
385                         t->nodes[j].pos.z);
386           glEnd();
387           mi->polygon_count += t->length;
388         }
389       else
390         {
391           GLfloat radius = t->radius * thickness_arg;
392           GLfloat rstep = radius / t->length;
393           int faces = 8;
394           glFrontFace (GL_CCW);
395           for (j = 0; j < t->length-1; j++)
396             {
397               int k;
398               node *n1 = &t->nodes[j];
399               node *n2 = &t->nodes[j+1];
400               GLfloat X = (n1->pos.x - n2->pos.x);
401               GLfloat Y = (n1->pos.y - n2->pos.y);
402               GLfloat Z = (n1->pos.z - n2->pos.z);
403               GLfloat L = sqrt (X*X + Y*Y + Z*Z);
404               GLfloat r2 = radius - rstep;
405
406               glPushMatrix();
407               glTranslatef (n1->pos.x, n1->pos.y, n1->pos.z);
408               glRotatef (-atan2 (X, Y)               * (180 / M_PI), 0, 0, 1);
409               glRotatef ( atan2 (Z, sqrt(X*X + Y*Y)) * (180 / M_PI), 1, 0, 0);
410
411               glBegin (wire ? GL_LINE_LOOP : GL_QUAD_STRIP);
412               for (k = 0; k <= faces; k++)
413                 {
414                   GLfloat th  = k * M_PI * 2 / faces;
415                   GLfloat c = cos(th);
416                   GLfloat s = sin(th);
417                   GLfloat x1 = radius * c;
418                   GLfloat y1 = radius * s;
419                   GLfloat z1 = 0;
420                   GLfloat x2 = r2 * c;
421                   GLfloat y2 = r2 * s;
422                   GLfloat z2 = L;
423
424                   glNormal3f (x1, z1, y1);
425                   glVertex3f (x1, z1, y1);
426                   glVertex3f (x2, z2, y2);
427                   mi->polygon_count++;
428                 }
429               glEnd();
430               glPopMatrix();
431               radius = r2;
432             }
433         }
434     }
435
436   draw_head (mi, sq, 1.0);
437
438   glPopMatrix();
439 }
440
441
442 static squid *
443 make_squid (ModeInfo *mi, int which)
444 {
445   squid *sq = calloc (1, sizeof(*sq));
446   int i;
447
448   sq->head_radius = head_radius_arg;
449   sq->thickness   = thickness_arg;
450   sq->ntentacles  = ntentacles_arg;
451
452   sq->color[0] = 0.1 + frand(0.7);
453   sq->color[1] = 0.5 + frand(0.5);
454   sq->color[2] = 0.1 + frand(0.7);
455   sq->color[3] = opacity_arg;
456
457   sq->from.x = sq->to.x = sq->pos.x = 200 - frand(400);
458   sq->from.y = sq->to.y = sq->pos.y = 200 - frand(400);
459   sq->from.z = sq->to.z = sq->pos.z = -frand(200);
460
461   sq->ratio = -frand(3);
462
463   if (which > 0)   /* Start others off screen, and moving in */
464     {
465       sq->from.x = sq->to.x = sq->pos.x = 800 + frand(500)
466         * (random()&1 ? 1 : -1);
467       sq->from.y = sq->to.y = sq->pos.y = 800 + frand(500)
468         * (random()&1 ? 1 : -1);
469       sq->ratio = 0;
470     }
471
472   if (do_pulse)
473     sq->pulse = frand(1.0);
474   sq->rate = 0.8 + frand(0.2);
475
476   sq->tentacles = (tentacle *)
477     calloc (sq->ntentacles, sizeof(*sq->tentacles));
478   for (i = 0; i < sq->ntentacles; i++)
479     {
480       int j;
481       tentacle *t = &sq->tentacles[i];
482       GLfloat shade = 0.75 + frand(0.25);
483
484       t->length   = 2 + length_arg * (0.8 + frand (0.4));
485       t->radius   = 0.05 + frand (0.95);
486       t->spacing  = 0.02 + frand (0.08);
487       t->friction = 0.7  + frand (0.18);
488       t->nodes = (node *) calloc (t->length + 1, sizeof (*t->nodes));
489
490       t->color[0] = shade * sq->color[0];
491       t->color[1] = shade * sq->color[1];
492       t->color[2] = shade * sq->color[2];
493       t->color[3] = sq->color[3];
494
495       for (j = 0; j < t->length; j++)
496         {
497           node *n = &t->nodes[j];
498           n->pos.x = sq->pos.x;
499           n->pos.y = sq->pos.y;
500           n->pos.z = sq->pos.z + j;
501         }
502     }
503
504   return sq;
505 }
506
507 /* qsort comparator for sorting squid by depth */
508 static int
509 cmp_squid (const void *aa, const void *bb)
510 {
511   const squid *a = (squid *) aa;
512   const squid *b = (squid *) bb;
513   return ((int) (b->pos.y * 10000) -
514           (int) (a->pos.y * 10000));
515 }
516
517
518 static void
519 free_squid (squid *sq)
520 {
521   int i;
522   for (i = 0; i < sq->ntentacles; i++)
523     free (sq->tentacles[i].nodes);
524   free (sq->tentacles);
525   free (sq);
526 }
527
528
529 /* Window management, etc
530  */
531 ENTRYPOINT void
532 reshape_hydrostat (ModeInfo *mi, int width, int height)
533 {
534   GLfloat h = (GLfloat) height / (GLfloat) width;
535
536   glViewport (0, 0, (GLint) width, (GLint) height);
537
538   glMatrixMode(GL_PROJECTION);
539   glLoadIdentity();
540   gluPerspective (30.0, 1/h, 1.0, 100.0);
541
542   glMatrixMode(GL_MODELVIEW);
543   glLoadIdentity();
544   gluLookAt( 0.0, 0.0, 30.0,
545              0.0, 0.0, 0.0,
546              0.0, 1.0, 0.0);
547
548 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
549   {
550     int o = (int) current_device_rotation();
551     if (o != 0 && o != 180 && o != -180)
552       glScalef (1/h, 1/h, 1/h);
553   }
554 # endif
555
556   glClear(GL_COLOR_BUFFER_BIT);
557 }
558
559
560
561 ENTRYPOINT Bool
562 hydrostat_handle_event (ModeInfo *mi, XEvent *event)
563 {
564   hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
565   int w = MI_WIDTH(mi);
566   int h = MI_HEIGHT(mi);
567   int x, y;
568
569 # ifdef USE_TRACKBALL
570   if (gltrackball_event_handler (event, bp->trackball,
571                                  MI_WIDTH (mi), MI_HEIGHT (mi),
572                                  &bp->button_down_p))
573     return True;
574 # endif
575
576   switch (event->xany.type) {
577   case ButtonPress: case ButtonRelease:
578     x = event->xbutton.x;
579     y = event->xbutton.y;
580     break;
581   case MotionNotify:
582     x = event->xmotion.x;
583     y = event->xmotion.y;
584     break;
585   default:
586     x = y = 0;
587   }
588
589   x -= w/2;
590   y -= h/2;
591   x *= 0.7;
592   y *= 0.7;
593
594   if (event->xany.type == ButtonPress)
595     {
596       int i;
597       GLfloat D0 = 999999;
598
599       /* This is pretty halfassed hit detection, but it works ok... */
600       for (i = 0; i < MI_COUNT(mi); i++)
601         {
602           squid *s = bp->squids[i];
603           GLfloat X = s->pos.x - x;
604           GLfloat Y = s->pos.z - y;
605           GLfloat D = sqrt(X*X + Y*Y);
606           if (D < D0)
607             {
608               bp->dragging = i;
609               D0 = D;
610             }
611         }
612
613       if (D0 > 300)     /* Too far away, missed hit */
614         {
615           bp->dragging = -1;
616           return False;
617         }
618
619       bp->squids[bp->dragging]->ratio = -3;
620       bp->button_down_p = True;
621
622       return True;
623     }
624   else if (event->xany.type == ButtonRelease && bp->dragging >= 0)
625     {
626       bp->button_down_p = False;
627       bp->dragging = -1;
628       return True;
629     }
630   else if (event->xany.type == MotionNotify && bp->dragging >= 0)
631     {
632       squid *s = bp->squids[bp->dragging];
633       s->from.x = s->to.x = s->pos.x = x;
634       s->from.z = s->to.z = s->pos.z = y;
635       s->from.y = s->to.y = s->pos.y;
636       return True;
637     }
638
639   return False;
640 }
641
642
643 ENTRYPOINT void 
644 init_hydrostat (ModeInfo *mi)
645 {
646   int wire = MI_IS_WIREFRAME(mi);
647   hydrostat_configuration *bp;
648   int i;
649
650   if (!bps) {
651     bps = (hydrostat_configuration *)
652       calloc (MI_NUM_SCREENS(mi), sizeof (hydrostat_configuration));
653     if (!bps) {
654       fprintf(stderr, "%s: out of memory\n", progname);
655       exit(1);
656     }
657   }
658
659   bp = &bps[MI_SCREEN(mi)];
660
661   bp->glx_context = init_GL(mi);
662
663   reshape_hydrostat (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
664
665   if (!wire)
666     {
667       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
668       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
669       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
670       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
671
672       glEnable(GL_LIGHTING);
673       glEnable(GL_LIGHT0);
674       glEnable(GL_DEPTH_TEST);
675       glEnable(GL_CULL_FACE);
676
677       glLightfv(GL_LIGHT0, GL_POSITION, pos);
678       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
679       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
680       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
681     }
682
683   glShadeModel(GL_SMOOTH);
684
685   glEnable(GL_DEPTH_TEST);
686   glEnable(GL_NORMALIZE);
687
688   if (MI_COUNT(mi) <= 0)
689     MI_COUNT(mi) = 1;
690
691   if (random() & 1)
692     current_arg = -current_arg;
693
694   if (MI_COUNT(mi) == 1 || wire)
695     opacity_arg = 1.0;
696   if (opacity_arg < 0.1) opacity_arg = 0.1;
697   if (opacity_arg > 1.0) opacity_arg = 1.0;
698
699   bp->squids = (squid **) calloc (MI_COUNT(mi), sizeof(*bp->squids));
700   for (i = 0; i < MI_COUNT(mi); i++)
701     bp->squids[i] = make_squid (mi, i);
702
703   bp->dragging = -1;
704
705   if (opacity_arg < 1.0)
706     {
707       glEnable (GL_BLEND);
708       glBlendFunc (GL_SRC_ALPHA, GL_ONE);
709     }
710
711 # ifdef USE_TRACKBALL
712   bp->trackball = gltrackball_init (True);
713 # endif
714 }
715
716
717 ENTRYPOINT void
718 draw_hydrostat (ModeInfo *mi)
719 {
720   hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
721   Display *dpy = MI_DISPLAY(mi);
722   Window window = MI_WINDOW(mi);
723   int i;
724
725   if (!bp->glx_context)
726     return;
727
728   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
729
730   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
731
732   glPushMatrix ();
733
734   glScalef (0.03, 0.03, 0.03);
735
736 # ifdef USE_TRACKBALL
737   gltrackball_rotate (bp->trackball);
738 # endif
739
740   mi->polygon_count = 0;
741
742   if (opacity_arg < 1.0)
743     qsort (bp->squids, MI_COUNT(mi), sizeof(*bp->squids), cmp_squid);
744
745   for (i = 0; i < MI_COUNT(mi); i++)
746     {
747       squid *sq = bp->squids[i];
748       move_squid (mi, sq);
749       draw_squid (mi, sq);
750       if (opacity_arg < 1.0)
751         glClear (GL_DEPTH_BUFFER_BIT);
752     }
753
754   if (! (random() % 700))  /* Reverse the flow every now and then */
755     current_arg = -current_arg;
756
757   glPopMatrix ();
758
759   if (mi->fps_p) do_fps (mi);
760   glFinish();
761
762   glXSwapBuffers(dpy, window);
763 }
764
765
766 ENTRYPOINT void
767 release_hydrostat (ModeInfo *mi)
768 {
769   hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
770   int i;
771   for (i = 0; i < MI_COUNT(mi); i++)
772     free_squid (bp->squids[i]);
773   free (bp->squids);
774 }
775
776 XSCREENSAVER_MODULE ("Hydrostat", hydrostat)
777
778 #endif /* USE_GL */