1 /* hydrostat, Copyright (C) 2012 by Justin Windle
2 * Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
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:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
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
22 * Tentacle simulation using inverse kinematics.
24 * http://soulwire.co.uk/experiments/muscular-hydrostats/
25 * https://github.com/soulwire/Muscular-Hydrostats/
27 * Ported to C from Javascript by jwz, May 2016
30 #define DEFAULTS "*delay: 20000 \n" \
32 "*showFPS: False \n" \
33 "*wireframe: False \n" \
34 "*suppressRotationAnimation: True\n" \
36 # define release_hydrostat 0
38 #define countof(x) (sizeof((x))/sizeof((*x)))
40 #include "xlockmore.h"
44 #include "gltrackball.h"
47 #ifdef USE_GL /* whole file */
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
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"
66 #define TENTACLE_FACES 5
84 GLfloat ratio, pulse, rate;
93 GLXContext *glx_context;
97 GLfloat cos_sin_table[2 * (TENTACLE_FACES + 1)];
100 # ifdef USE_TRACKBALL
101 trackball_state *trackball;
103 } hydrostat_configuration;
105 static hydrostat_configuration *bps = NULL;
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;
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 },
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 { ¤t_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 },
145 ENTRYPOINT ModeSpecOpt hydrostat_opts = {countof(opts), opts,
146 countof(vars), vars, NULL};
150 move_tentacle (squid *sq, tentacle *t)
153 node *prev = &t->nodes[0];
154 int rot = (int) current_device_rotation();
156 for (i = 1, j = 0; i < t->length; i++, j++)
160 node *n = &t->nodes[i];
162 /* Sadly, this is still computing motion in a 2d plane, so the
163 tentacles look dumb if the scene is rotated. */
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);
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;
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);
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;
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);
192 n->v.x += gravity_arg;
193 n->v.y -= current_arg;
194 n->v.z -= current_arg;
197 n->v.x -= gravity_arg;
198 n->v.y += current_arg;
199 n->v.z += current_arg;
202 n->v.x -= current_arg;
203 n->v.y -= current_arg;
204 n->v.z -= gravity_arg;
207 n->v.x += current_arg;
208 n->v.y += current_arg;
209 n->v.z += gravity_arg;
213 n->opos.x = n->pos.x;
214 n->opos.y = n->pos.y;
215 n->opos.z = n->pos.z;
225 return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
229 /* Squirty motion: fast acceleration, then fade. */
231 ease_ratio (GLfloat r)
234 GLfloat ease2 = 1-ease;
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);
244 move_squid (ModeInfo *mi, squid *sq)
246 hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
247 GLfloat step = M_PI * 2 / sq->ntentacles;
249 GLfloat radius = head_radius_arg;
252 /* Move to a new position */
254 if (! bp->button_down_p)
256 sq->ratio += speed_arg * 0.01;
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);
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);
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;
284 for (i = 0; i < sq->ntentacles; i++)
286 tentacle *tt = &sq->tentacles[i];
287 GLfloat th = i * step;
288 GLfloat px = cos (th) * radius;
289 GLfloat py = sin (th) * radius;
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);
299 /* Find the angle at which the head should be tilted in the XY plane.
302 head_angle (ModeInfo *mi, squid *sq)
307 for (i = 0; i < sq->ntentacles; i++)
309 tentacle *t = &sq->tentacles[i];
310 int j = t->length / 3; /* Pick a node toward the top */
311 node *n = &t->nodes[j];
317 sum.x /= sq->ntentacles;
318 sum.y /= sq->ntentacles;
319 sum.z /= sq->ntentacles;
325 return (-atan2 (sum.x, sum.z) * (180 / M_PI));
330 draw_head (ModeInfo *mi, squid *sq, GLfloat scale)
332 hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
334 GLfloat angle = head_angle (mi, sq);
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);
344 memcpy (c2, sq->color, sizeof(c2));
345 if (opacity_arg < 1.0 && scale >= 1.0)
348 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c2);
350 glTranslatef (0, 0.3, 0);
351 glRotatef (angle, 0, 0, 1);
353 glCallList (bp->head);
354 mi->polygon_count += bp->head_polys;
361 draw_squid (ModeInfo *mi, squid *sq)
363 hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
364 int wire = MI_IS_WIREFRAME(mi);
367 glRotatef (90, 1, 0, 0);
369 if (opacity_arg < 1.0)
370 draw_head (mi, sq, 0.75);
373 glFrontFace (GL_CCW);
374 glBegin (GL_TRIANGLE_STRIP);
377 for (i = 0; i < sq->ntentacles; i++)
379 tentacle *t = &sq->tentacles[i];
382 glColor4fv (t->color);
383 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, t->color);
387 glBegin (GL_LINE_STRIP);
388 for (j = 0; j < t->length; j++)
389 glVertex3f (t->nodes[j].pos.x,
393 mi->polygon_count += t->length;
397 GLfloat radius = t->radius * thickness_arg;
398 GLfloat rstep = radius / t->length;
399 for (j = 0; j < t->length-1; j++)
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);
411 for (k = 0; k <= TENTACLE_FACES; k++)
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;
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;
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;
430 glNormal3f (x1t, z1t, y1t);
437 glVertex3f (x1t, z1t, y1t);
438 glVertex3f (x1t, z1t, y1t);
440 glVertex3f (x2t, z2t, y2t);
441 if (k == TENTACLE_FACES)
442 glVertex3f (x2t, z2t, y2t);
454 draw_head (mi, sq, 1.0);
461 make_squid (ModeInfo *mi, int which)
463 squid *sq = calloc (1, sizeof(*sq));
466 sq->head_radius = head_radius_arg;
467 sq->thickness = thickness_arg;
468 sq->ntentacles = ntentacles_arg;
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;
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);
479 sq->ratio = -frand(3);
481 if (which > 0) /* Start others off screen, and moving in */
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);
491 sq->pulse = frand(1.0);
492 sq->rate = 0.8 + frand(0.2);
494 sq->tentacles = (tentacle *)
495 calloc (sq->ntentacles, sizeof(*sq->tentacles));
496 for (i = 0; i < sq->ntentacles; i++)
499 tentacle *t = &sq->tentacles[i];
500 GLfloat shade = 0.75 + frand(0.25);
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));
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];
513 for (j = 0; j < t->length; j++)
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;
525 /* qsort comparator for sorting squid by depth */
527 cmp_squid (const void *aa, const void *bb)
529 squid * const *a = aa;
530 squid * const *b = bb;
531 return ((int) ((*b)->pos.y * 10000) -
532 (int) ((*a)->pos.y * 10000));
537 free_squid (squid *sq)
540 for (i = 0; i < sq->ntentacles; i++)
541 free (sq->tentacles[i].nodes);
542 free (sq->tentacles);
547 /* Window management, etc
550 reshape_hydrostat (ModeInfo *mi, int width, int height)
552 GLfloat h = (GLfloat) height / (GLfloat) width;
554 glViewport (0, 0, (GLint) width, (GLint) height);
556 glMatrixMode(GL_PROJECTION);
558 gluPerspective (30.0, 1/h, 1.0, 100.0);
560 glMatrixMode(GL_MODELVIEW);
562 gluLookAt( 0.0, 0.0, 30.0,
566 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
568 int o = (int) current_device_rotation();
569 if (o != 0 && o != 180 && o != -180)
570 glScalef (1/h, 1/h, 1/h);
574 glClear(GL_COLOR_BUFFER_BIT);
580 hydrostat_handle_event (ModeInfo *mi, XEvent *event)
582 hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
583 int w = MI_WIDTH(mi);
584 int h = MI_HEIGHT(mi);
587 # ifdef USE_TRACKBALL
588 if (gltrackball_event_handler (event, bp->trackball,
589 MI_WIDTH (mi), MI_HEIGHT (mi),
594 switch (event->xany.type) {
595 case ButtonPress: case ButtonRelease:
596 x = event->xbutton.x;
597 y = event->xbutton.y;
600 x = event->xmotion.x;
601 y = event->xmotion.y;
612 if (event->xany.type == ButtonPress)
617 /* This is pretty halfassed hit detection, but it works ok... */
618 for (i = 0; i < MI_COUNT(mi); i++)
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);
631 if (D0 > 300) /* Too far away, missed hit */
637 bp->squids[bp->dragging]->ratio = -3;
638 bp->button_down_p = True;
642 else if (event->xany.type == ButtonRelease && bp->dragging >= 0)
644 bp->button_down_p = False;
648 else if (event->xany.type == MotionNotify && bp->dragging >= 0)
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;
662 init_hydrostat (ModeInfo *mi)
664 int wire = MI_IS_WIREFRAME(mi);
665 hydrostat_configuration *bp;
670 bp = &bps[MI_SCREEN(mi)];
672 bp->glx_context = init_GL(mi);
674 reshape_hydrostat (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
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};
684 glEnable(GL_LIGHTING);
686 glEnable(GL_DEPTH_TEST);
687 glEnable(GL_CULL_FACE);
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);
694 for (k = 0; k <= TENTACLE_FACES; k++)
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);
702 glShadeModel(GL_SMOOTH);
704 glEnable(GL_DEPTH_TEST);
705 glEnable(GL_NORMALIZE);
707 if (MI_COUNT(mi) <= 0)
711 current_arg = -current_arg;
713 if (MI_COUNT(mi) == 1 || wire)
715 if (opacity_arg < 0.1) opacity_arg = 0.1;
716 if (opacity_arg > 1.0) opacity_arg = 1.0;
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);
724 if (opacity_arg < 1.0)
727 glBlendFunc (GL_SRC_ALPHA, GL_ONE);
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);
740 # ifdef USE_TRACKBALL
741 bp->trackball = gltrackball_init (True);
747 draw_hydrostat (ModeInfo *mi)
749 hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
750 Display *dpy = MI_DISPLAY(mi);
751 Window window = MI_WINDOW(mi);
754 if (!bp->glx_context)
757 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
759 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
763 glScalef (0.03, 0.03, 0.03);
765 # ifdef USE_TRACKBALL
766 gltrackball_rotate (bp->trackball);
769 mi->polygon_count = 0;
771 if (opacity_arg < 1.0)
772 qsort (bp->squids, MI_COUNT(mi), sizeof(*bp->squids), cmp_squid);
774 for (i = 0; i < MI_COUNT(mi); i++)
776 squid *sq = bp->squids[i];
779 if (opacity_arg < 1.0)
780 glClear (GL_DEPTH_BUFFER_BIT);
783 if (! (random() % 700)) /* Reverse the flow every now and then */
784 current_arg = -current_arg;
788 if (mi->fps_p) do_fps (mi);
791 glXSwapBuffers(dpy, window);
796 free_hydrostat (ModeInfo *mi)
798 hydrostat_configuration *bp = &bps[MI_SCREEN(mi)];
802 for (i = 0; i < MI_COUNT(mi); i++)
803 free_squid (bp->squids[i]);
807 XSCREENSAVER_MODULE ("Hydrostat", hydrostat)