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