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