From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / skytentacles.c
1 /* Sky Tentacles, Copyright (c) 2008-2014 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #define DEFAULTS        "*delay:        30000       \n" \
13                         "*count:        9           \n" \
14                         "*showFPS:      False       \n" \
15                         "*wireframe:    False       \n" \
16
17 # define refresh_tentacles 0
18 # define release_tentacles 0
19 #undef countof
20 #define countof(x) (sizeof((x))/sizeof((*x)))
21
22 #include "xlockmore.h"
23 #include "colors.h"
24 #include "normals.h"
25 #include "rotator.h"
26 #include "gltrackball.h"
27 #include <ctype.h>
28
29 #include "xpm-ximage.h"
30 #include "../images/scales.xpm"
31
32 static char *grey_texture[] = {
33   "16 1 3 1",
34   "X c #808080",
35   "x c #C0C0C0",
36   ". c #FFFFFF",
37   "XXXxxxxx........"
38 };
39
40 #ifdef USE_GL /* whole file */
41
42 # ifndef HAVE_JWZGLES
43 #  define HAVE_POLYGONMODE
44 # endif
45
46 #define DEF_SPEED       "1.0"
47 #define DEF_SMOOTH      "True"
48 #define DEF_TEXTURE     "True"
49 #define DEF_CEL         "False"
50 #define DEF_INTERSECT   "False"
51 #define DEF_SLICES      "16"
52 #define DEF_SEGMENTS    "24"
53 #define DEF_WIGGLINESS  "0.35"
54 #define DEF_FLEXIBILITY "0.35"
55 #define DEF_THICKNESS   "1.0"
56 #define DEF_LENGTH      "9.0"
57 #define DEF_COLOR       "#305A30"
58 #define DEF_STRIPE      "#451A30"
59 #define DEF_SUCKER      "#453E30"
60 #define DEF_DEBUG       "False"
61
62 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
63
64 typedef struct {
65   GLfloat length;     /* length of the segment coming out of this segment */
66   GLfloat th;         /* vector tilt (on yz plane) from previous segment */
67   GLfloat phi;        /* vector rotation (on xy plane) from previous segment */
68   GLfloat thickness;  /* radius of tentacle at the bottom of this segment */
69   rotator *rot;       /* motion modeller */
70 } segment;
71
72 typedef struct {
73   ModeInfo *mi;
74   GLfloat x, y, z;      /* position of the base */
75   int nsegments;
76   segment *segments;
77   GLfloat tentacle_color[4], stripe_color[4], sucker_color[4];
78 } tentacle;
79
80 typedef struct {
81   GLXContext *glx_context;
82   trackball_state *trackball;
83   Bool button_down_p;
84
85   int ntentacles;
86   int tentacles_size;
87   tentacle **tentacles;
88   GLfloat tentacle_color[4], stripe_color[4], sucker_color[4];
89
90   int torus_polys;
91   int torus_step;
92   XYZ *torus_points;
93   XYZ *torus_normals;
94
95   GLfloat line_thickness;
96   GLfloat outline_color[4];
97   XImage *texture;
98   GLuint texid;
99
100   Bool left_p;
101   
102
103 } tentacles_configuration;
104
105 static tentacles_configuration *tcs = NULL;
106
107 static int debug_p;
108 static GLfloat arg_speed;
109 static int smooth_p;
110 static int texture_p;
111 static int cel_p;
112 static int intersect_p;
113 static int arg_slices;
114 static int arg_segments;
115 static GLfloat arg_thickness;
116 static GLfloat arg_length;
117 static GLfloat arg_wiggliness;
118 static GLfloat arg_flexibility;
119 static char *arg_color, *arg_stripe, *arg_sucker;
120
121 /* we can only have one light when doing cel shading */
122 static GLfloat light_pos[4] = {1.0, 1.0, 1.0, 0.0};
123
124
125 static XrmOptionDescRec opts[] = {
126   { "-speed",        ".speed",         XrmoptionSepArg, 0 },
127   { "-no-smooth",    ".smooth",        XrmoptionNoArg, "False" },
128   { "-texture",      ".texture",       XrmoptionNoArg, "True" },
129   { "-no-texture",   ".texture",       XrmoptionNoArg, "False" },
130   { "-cel",          ".cel",           XrmoptionNoArg, "True" },
131   { "-no-cel",       ".cel",           XrmoptionNoArg, "False" },
132   { "-intersect",    ".intersect",     XrmoptionNoArg, "True" },
133   { "-no-intersect", ".intersect",     XrmoptionNoArg, "False" },
134   { "-slices",       ".slices",        XrmoptionSepArg, 0 },
135   { "-segments",     ".segments",      XrmoptionSepArg, 0 },
136   { "-thickness",    ".thickness",     XrmoptionSepArg, 0 },
137   { "-length",       ".length",        XrmoptionSepArg, 0 },
138   { "-wiggliness",   ".wiggliness",    XrmoptionSepArg, 0 },
139   { "-flexibility",  ".flexibility",   XrmoptionSepArg, 0 },
140   { "-color",        ".tentacleColor", XrmoptionSepArg, 0 },
141   { "-stripe-color", ".stripeColor",   XrmoptionSepArg, 0 },
142   { "-sucker-color", ".suckerColor",   XrmoptionSepArg, 0 },
143   { "-debug",        ".debug",         XrmoptionNoArg, "True" },
144 };
145
146 static argtype vars[] = {
147   {&arg_speed,       "speed",         "Speed",       DEF_SPEED,       t_Float},
148   {&smooth_p,        "smooth",        "Smooth",      DEF_SMOOTH,      t_Bool},
149   {&texture_p,       "texture",       "Texture",     DEF_TEXTURE,     t_Bool},
150   {&cel_p,           "cel",           "Cel",         DEF_CEL,         t_Bool},
151   {&intersect_p,     "intersect",     "Intersect",   DEF_INTERSECT,   t_Bool},
152   {&arg_slices,      "slices",        "Slices",      DEF_SLICES,      t_Int},
153   {&arg_segments,    "segments",      "Segments",    DEF_SEGMENTS,    t_Int},
154   {&arg_thickness,   "thickness",     "Thickness",   DEF_THICKNESS,   t_Float},
155   {&arg_length,      "length",        "Length",      DEF_LENGTH,      t_Float},
156   {&arg_wiggliness,  "wiggliness",    "Wiggliness",  DEF_WIGGLINESS,  t_Float},
157   {&arg_flexibility, "flexibility",   "Flexibility", DEF_FLEXIBILITY, t_Float},
158   {&arg_color,       "tentacleColor", "Color",       DEF_COLOR,       t_String},
159   {&arg_stripe,      "stripeColor",   "Color",       DEF_STRIPE,      t_String},
160   {&arg_sucker,      "suckerColor",   "Color",       DEF_SUCKER,      t_String},
161   {&debug_p,         "debug",         "Debug",       DEF_DEBUG,       t_Bool},
162 };
163
164 ENTRYPOINT ModeSpecOpt tentacles_opts = {countof(opts), opts, countof(vars), vars, NULL};
165
166
167 /* Window management, etc
168  */
169 ENTRYPOINT void
170 reshape_tentacles (ModeInfo *mi, int width, int height)
171 {
172   tentacles_configuration *tc = &tcs[MI_SCREEN(mi)];
173   GLfloat h = (GLfloat) height / (GLfloat) width;
174
175   glViewport (0, 0, (GLint) width, (GLint) height);
176
177   glMatrixMode(GL_PROJECTION);
178   glLoadIdentity();
179   gluPerspective (30.0, 1/h, 1.0, 100.0);
180
181   glMatrixMode(GL_MODELVIEW);
182   glLoadIdentity();
183   gluLookAt( 0.0, 0.0, 30.0,
184              0.0, 0.0, 0.0,
185              0.0, 1.0, 0.0);
186
187   glClear(GL_COLOR_BUFFER_BIT);
188
189   tc->line_thickness = (MI_IS_WIREFRAME (mi) ? 1 : MAX (3, width / 200));
190 }
191
192
193 static void
194 normalize (GLfloat *x, GLfloat *y, GLfloat *z)
195 {
196   GLfloat d = sqrt((*x)*(*x) + (*y)*(*y) + (*z)*(*z));
197   *x /= d;
198   *y /= d;
199   *z /= d;
200 }
201
202 static GLfloat
203 dot (GLfloat x0, GLfloat y0, GLfloat z0,
204      GLfloat x1, GLfloat y1, GLfloat z1)
205 {
206   return x0*x1 + y0*y1 + z0*z1;
207 }
208
209
210 static void
211 compute_unit_torus (ModeInfo *mi, double ratio, int slices1, int slices2)
212 {
213   tentacles_configuration *tc = &tcs[MI_SCREEN(mi)];
214   Bool wire = MI_IS_WIREFRAME (mi);
215   int i, j, k, fp;
216
217   if (wire) slices1 /= 2;
218   if (wire) slices2 /= 4;
219   if (slices1 < 3) slices1 = 3;
220   if (slices2 < 3) slices2 = 3;
221
222   tc->torus_polys = slices1 * (slices2+1) * 2;
223   tc->torus_points  = (XYZ *) calloc (tc->torus_polys + 1,
224                                       sizeof (*tc->torus_points));
225   tc->torus_normals = (XYZ *) calloc (tc->torus_polys + 1,
226                                       sizeof (*tc->torus_normals));
227   tc->torus_step = 2 * (slices2+1);
228   fp = 0;
229   for (i = 0; i < slices1; i++)
230     for (j = 0; j <= slices2; j++)
231       for (k = 0; k <= 1; k++)
232         {
233           double s = (i + k) % slices1 + 0.5;
234           double t = j % slices2;
235           XYZ p;
236           p.x = cos(t*M_PI*2/slices2) * cos(s*M_PI*2/slices1);
237           p.y = sin(t*M_PI*2/slices2) * cos(s*M_PI*2/slices1);
238           p.z = sin(s*M_PI*2/slices1);
239           tc->torus_normals[fp] = p;
240
241           p.x = (1 + ratio * cos(s*M_PI*2/slices1)) * cos(t*M_PI*2/slices2) / 2;
242           p.y = (1 + ratio * cos(s*M_PI*2/slices1)) * sin(t*M_PI*2/slices2) / 2;
243           p.z = ratio * sin(s*M_PI*2/slices1) / 2;
244           tc->torus_points[fp] = p;
245           fp++;
246         }
247   if (fp != tc->torus_polys) abort();
248   tc->torus_polys = fp;
249 }
250
251
252 /* Initializes a new tentacle and stores it in the list.
253  */
254 static tentacle *
255 make_tentacle (ModeInfo *mi, int which, int total)
256 {
257   tentacles_configuration *tc = &tcs[MI_SCREEN(mi)];
258   tentacle *t = (tentacle *) calloc (1, sizeof (*t));
259   double brightness;
260   int i;
261
262   t->mi = mi;
263
264   /* position tentacles on a grid */
265   {
266     int cols = (int) (sqrt(total) + 0.5);
267     int rows = (total+cols-1) / cols;
268     int xx = which % cols;
269     int yy = which / cols;
270     double spc = arg_thickness * 0.8;
271     if (!intersect_p) cols = 1, xx = 0;
272     t->x = (cols * spc / 2) - (spc * (xx + 0.5));
273     t->y = (rows * spc / 2) - (spc * (yy + 0.5));
274     t->z = 0;
275   }
276
277   /* Brighten or darken the colors of this tentacle from the default.
278    */
279   brightness = 0.6 + frand(3.0);
280   memcpy (t->tentacle_color, tc->tentacle_color, 4 * sizeof(*t->tentacle_color));
281   memcpy (t->stripe_color,   tc->stripe_color,   4 * sizeof(*t->stripe_color));
282   memcpy (t->sucker_color,   tc->sucker_color,   4 * sizeof(*t->sucker_color));
283 # define FROB(X) \
284     t->X[0] *= brightness; if (t->X[0] > 1) t->X[0] = 1; \
285     t->X[1] *= brightness; if (t->X[1] > 1) t->X[1] = 1; \
286     t->X[2] *= brightness; if (t->X[2] > 1) t->X[2] = 1
287   FROB (tentacle_color);
288   FROB (stripe_color);
289   FROB (sucker_color);
290 # undef FROB
291
292   t->nsegments = (arg_segments) + BELLRAND(arg_segments);
293
294   t->segments = (segment *) calloc (t->nsegments+1, sizeof(*t->segments));
295   for (i = 0; i < t->nsegments; i++)
296     {
297       double spin_speed   = 0;
298       double spin_accel   = 0;
299       double wander_speed = arg_speed * (0.02 + BELLRAND(0.1));
300       t->segments[i].rot = make_rotator (spin_speed, spin_speed, spin_speed,
301                                          spin_accel, wander_speed, True);
302     }
303
304   t->segments[0].thickness = (((arg_thickness * 0.5) + 
305                                BELLRAND(arg_thickness * 0.6))
306                             / 1.0);
307
308   if (tc->tentacles_size <= tc->ntentacles)
309     {
310       tc->tentacles_size = (tc->tentacles_size * 1.2) + tc->ntentacles + 2;
311       tc->tentacles = (tentacle **)
312         realloc (tc->tentacles, tc->tentacles_size * sizeof(*tc->tentacles));
313       if (! tc->tentacles)
314         {
315           fprintf (stderr, "%s: out of memory (%d tentacles)\n",
316                    progname, tc->tentacles_size);
317           exit (1);
318         }
319     }
320
321   tc->tentacles[tc->ntentacles++] = t;
322   return t;
323 }
324
325
326 static void
327 draw_sucker (tentacle *t, Bool front_p)
328 {
329   tentacles_configuration *tc = &tcs[MI_SCREEN(t->mi)];
330   Bool wire = MI_IS_WIREFRAME (t->mi);
331   int i, j;
332   int strips = tc->torus_polys / tc->torus_step;
333   int points = 0;
334
335   glFrontFace (front_p ? GL_CW : GL_CCW);
336   for (i = 0; i < strips; i++)
337     {
338       int ii = i * tc->torus_step;
339
340       /* Leave off the polygons on the underside.  This reduces polygon
341          count by about 10% with the default settings. */
342       if (strips > 4 && i >= strips/2 && i < strips-1)
343         continue;
344
345       glBegin (wire ? GL_LINE_STRIP : GL_QUAD_STRIP);
346       for (j = 0; j < tc->torus_step; j++)
347         {
348           XYZ sp = tc->torus_points[ii+j];
349           XYZ sn = tc->torus_normals[ii+j];
350           glNormal3f(sn.x, sn.y, sn.z);
351           glVertex3f(sp.x, sp.y, sp.z);
352           points++;
353         }
354       glEnd();
355     }
356   t->mi->polygon_count += points/2;
357 }
358
359 static void
360 draw_tentacle_1 (tentacle *t, Bool front_p, Bool outline_p)
361 {
362   tentacles_configuration *tc = &tcs[MI_SCREEN(t->mi)];
363   int i;
364   Bool wire = MI_IS_WIREFRAME (t->mi);
365   XYZ ctr = { 0,0,0 };     /* current position of base of segment */
366   double cth  = 0;         /* overall orientation of current segment */
367   double cphi = 0;
368   double cth_cos  = 1, cth_sin  = 0;
369   double cphi_cos = 1, cphi_sin = 0;
370
371   GLfloat light[3];        /* vector to the light */
372
373   GLfloat t0 = 0.0;        /* texture coordinate */
374
375   XYZ *ring, *oring;       /* points around the edge (this, and previous) */
376   XYZ *norm, *onorm;       /* their normals */
377   XYZ *ucirc;              /* unit circle, to save some trig */
378
379   /* Which portion of the radius the indented/colored stripe takes up */
380   int indented_points = arg_slices * 0.2;
381
382   /* We do rotation this way to minimize the number of calls to sin/cos.
383      We have to hack the transformations manually instead of using
384      glRotate/glTranslate because those calls are not allowed *inside*
385      of a glBegin/glEnd block...
386    */
387 # define ROT(P) do {                                                    \
388     XYZ _p = P;                                                         \
389     _p.y = ((P.y * cth_sin - P.x * cth_cos));                           \
390     _p.x = ((P.y * cth_cos + P.x * cth_sin) * cphi_sin - (P.z * cphi_cos)); \
391     _p.z = ((P.y * cth_cos + P.x * cth_sin) * cphi_cos + (P.z * cphi_sin)); \
392     P = _p;                                                             \
393   } while(0)
394
395   ring  = (XYZ *) malloc (arg_slices * sizeof(*ring));
396   norm  = (XYZ *) malloc (arg_slices * sizeof(*norm));
397   oring = (XYZ *) malloc (arg_slices * sizeof(*oring));
398   onorm = (XYZ *) malloc (arg_slices * sizeof(*onorm));
399   ucirc = (XYZ *) malloc (arg_slices * sizeof(*ucirc));
400
401   light[0] = light_pos[0];
402   light[1] = light_pos[1];
403   light[2] = light_pos[2];
404   normalize (&light[0], &light[1], &light[2]);
405
406   for (i = 0; i < arg_slices; i++)
407     {
408       double a = M_PI * 2 * i / arg_slices;
409       ucirc[i].x = cos(a);
410       ucirc[i].y = sin(a);
411       ucirc[i].z = 0;
412     }
413
414
415 # ifdef HAVE_POLYGONMODE
416   if (cel_p)
417     glPolygonMode (GL_FRONT_AND_BACK, (front_p ? GL_FILL : GL_LINE));
418 # endif
419
420   glPushMatrix();
421   glTranslatef (t->x, t->y, t->z);
422
423   if (debug_p)
424     {
425       glPushAttrib (GL_ENABLE_BIT);
426       glDisable (GL_LIGHTING);
427       glDisable (GL_TEXTURE_1D);
428       glDisable (GL_TEXTURE_2D);
429       glColor3f (1, 1, 1);
430       glLineWidth (1);
431       glBegin(GL_LINE_LOOP);
432       for (i = 0; i < arg_slices; i++)
433         glVertex3f (arg_thickness / 2 * cos (M_PI * 2 * i / arg_slices),
434                     arg_thickness / 2 * sin (M_PI * 2 * i / arg_slices),
435                     0);
436       glEnd();
437       glPopAttrib();
438     }
439
440   if (!front_p || outline_p)
441     glColor4fv (tc->outline_color);
442   else if (wire)
443     glColor4fv (t->tentacle_color);
444   else
445     {
446       static const GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
447       static const GLfloat bshiny    = 128.0;
448       glMaterialfv (GL_FRONT, GL_SPECULAR,            bspec);
449       glMateriali  (GL_FRONT, GL_SHININESS,           bshiny);
450     }
451
452   for (i = 0; i < t->nsegments; i++)
453     {
454       int j;
455       GLfloat t1 = t0 + i / (t->nsegments * M_PI * 2);
456
457       for (j = 0; j < arg_slices; j++)
458         {
459           /* Construct a vertical disc at the origin, to use as the
460              base of this segment.
461           */
462           double r = t->segments[i].thickness / 2;
463
464           if (j <= indented_points/2 || j >= arg_slices-indented_points/2)
465             r *= 0.75;  /* indent the stripe */
466
467           if (outline_p)
468             r *= 1.1;
469
470           ring[j].x = r * ucirc[j].x;
471           ring[j].y = 0;
472           ring[j].z = r * ucirc[j].y;
473
474           /* Then rotate the points by the angle of the current segment. */
475           ROT(ring[j]);
476
477           /* Then move the ring to the base of this segment. */
478           ring[j].x += ctr.x;
479           ring[j].y += ctr.y;
480           ring[j].z += ctr.z;
481         }
482
483
484       /* Compute the normals of the faces on this segment.  We do this
485          first so that the normals of the vertexes can be the average
486          of the normals of the faces.
487          #### Uh, except I didn't actually implement that...
488          but it would be a good idea.
489       */
490       if (i > 0)
491         for (j = 0; j <= arg_slices; j++)
492           {
493             int j0 = j     % arg_slices;
494             int j1 = (j+1) % arg_slices;
495             norm[j0] = calc_normal (oring[j0], ring[j0], ring[j1]);
496           }
497
498       /* Draw!
499        */
500       if (i > 0)
501         {
502           int j;
503           glLineWidth (tc->line_thickness);
504           glFrontFace (front_p ? GL_CCW : GL_CW);
505           glBegin (wire ? GL_LINES : smooth_p ? GL_QUAD_STRIP : GL_QUADS);
506           for (j = 0; j <= arg_slices; j++)
507             {
508               int j0 = j     % arg_slices;
509               int j1 = (j+1) % arg_slices;
510
511               GLfloat ts = j / (double) arg_slices;
512
513               if (!front_p || outline_p)
514                 glColor4fv (tc->outline_color);
515               else if (j <= indented_points/2 || 
516                        j >= arg_slices-indented_points/2)
517                 {
518                   glColor4fv (t->stripe_color);
519                   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
520                                 t->stripe_color);
521                 }
522               else
523                 {
524                   glColor4fv (t->tentacle_color);
525                   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
526                                 t->tentacle_color);
527                 }
528
529               /* For cel shading, the 1d texture coordinate (s) is the 
530                  dot product of the lighting vector and the vertex normal.
531               */
532               if (cel_p)
533                 {
534                   t0 = dot (light[0], light[1], light[2],
535                             onorm[j0].x, onorm[j0].y, onorm[j0].z);
536                   t1 = dot (light[0], light[1], light[2],
537                             norm[j0].x, norm[j0].y, norm[j0].z);
538                   if (t0 < 0) t0 = 0;
539                   if (t1 < 0) t1 = 0;
540                 }
541
542               glTexCoord2f (t0, ts);
543               glNormal3f (onorm[j0].x, onorm[j0].y, onorm[j0].z);
544               glVertex3f (oring[j0].x, oring[j0].y, oring[j0].z);
545
546               glTexCoord2f (t1, ts);
547               glNormal3f ( norm[j0].x,  norm[j0].y,  norm[j0].z);
548               glVertex3f ( ring[j0].x,  ring[j0].y,  ring[j0].z);
549
550               if (!smooth_p)
551                 {
552                   ts = j1 / (double) arg_slices;
553                   glTexCoord2f (t1, ts);
554                   glVertex3f ( ring[j1].x,  ring[j1].y,  ring[j1].z);
555                   glTexCoord2f (t0, ts);
556                   glVertex3f (oring[j1].x, oring[j1].y, oring[j1].z);
557                 }
558               t->mi->polygon_count++;
559             }
560           glEnd ();
561
562           if (wire)
563             {
564               glBegin (GL_LINE_LOOP);
565               for (j = 0; j < arg_slices; j++)
566                 glVertex3f (ring[j].x, ring[j].y, ring[j].z);
567               glEnd();
568             }
569
570           /* Now draw the suckers!
571            */
572           {
573             double seg_length = arg_length / t->nsegments;
574             double sucker_size = arg_thickness / 5;
575             double sucker_spacing = sucker_size * 1.3;
576             int nsuckers = seg_length / sucker_spacing;
577             double oth  = cth  - t->segments[i-1].th;
578             double ophi = cphi - t->segments[i-1].phi;
579             int k;
580
581             if (!wire)
582               glLineWidth (MAX (2, tc->line_thickness / 2.0));
583             glDisable (GL_TEXTURE_2D);
584
585             /* Sometimes we have N suckers on one segment; 
586                sometimes we have one sucker every N segments. */
587             if (nsuckers == 0)
588               {
589                 int segs_per_sucker =
590                   (int) ((sucker_spacing / seg_length) + 0.5);
591                 nsuckers = (i % segs_per_sucker) ? 0 : 1;
592               }
593
594             if (outline_p)
595               {
596                 glColor4fv (tc->outline_color);
597                 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, 
598                               tc->outline_color);
599               }
600             else if (front_p)
601               {
602                 glColor4fv (t->sucker_color);
603                 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, 
604                               t->sucker_color);
605               }
606
607             for (k = 0; k < nsuckers; k++)
608               {
609                 double scale;
610                 XYZ p0 = ring[0];
611                 XYZ p1 = oring[0];
612                 XYZ p;
613                 p.x = p0.x + (p1.x - p0.x) * (k + 0.5) / nsuckers;
614                 p.y = p0.y + (p1.y - p0.y) * (k + 0.5) / nsuckers;
615                 p.z = p0.z + (p1.z - p0.z) * (k + 0.5) / nsuckers;
616
617                 glPushMatrix();
618                 glTranslatef (p.x, p.y, p.z);
619                 glRotatef (ophi * 180 / M_PI, 0, 1, 0);
620                 glRotatef (-oth * 180 / M_PI, 1, 0, 0);
621                 glRotatef (90, 1, 0, 0);
622
623                 { /* Not quite right: this is the slope of the outer edge
624                      if the next segment was not tilted at all...  If there
625                      is any tilt, then the angle of this wall and the 
626                      opposite wall are very different.
627                    */
628                   double slope = ((t->segments[i-1].thickness -
629                                    t->segments[i].thickness) /
630                                   t->segments[i].length);
631                   glRotatef (-45 * slope, 1, 0, 0);
632                 }
633
634                 scale = t->segments[i].thickness / arg_thickness;
635                 scale *= 0.7 * sucker_size;
636
637                 glScalef (scale, scale, scale * 4);
638
639                 glTranslatef (0, 0, -0.1);  /* embed */
640
641                 if (outline_p)
642                   {
643                     scale = 1.1;
644                     glScalef (scale, scale, scale);
645                   }
646
647                 glTranslatef (1, 0, 0);     /* left */
648                 draw_sucker (t, front_p);
649
650                 glTranslatef (-2, 0, 0);    /* right */
651                 draw_sucker (t, front_p);
652
653                 glPopMatrix();
654               }
655
656             if (texture_p) glEnable (GL_TEXTURE_2D);
657           }
658         }
659
660       /* Now draw the end caps.
661        */
662       glLineWidth (tc->line_thickness);
663       if (!outline_p && (i == 0 || i == t->nsegments-1))
664         {
665           int j;
666           GLfloat ctrz = ctr.z + ((i == 0 ? -1 : 1) *
667                                   t->segments[i].thickness / 4);
668           if (front_p)
669             {
670               glColor4fv (t->tentacle_color);
671               glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, 
672                             t->tentacle_color);
673             }
674           glFrontFace ((front_p ? i == 0 : i != 0) ? GL_CCW : GL_CW);
675           glBegin (wire ? GL_LINES : GL_TRIANGLE_FAN);
676           glNormal3f (0, 0, (i == 0 ? -1 : 1));
677           glTexCoord2f (t0 - 0.25, 0.5);
678           glVertex3f (ctr.x, ctr.y, ctrz);
679           for (j = 0; j <= arg_slices; j++)
680             {
681               int jj = j % arg_slices;
682               GLfloat ts = j / (double) arg_slices;
683               glTexCoord2f (t0, ts);
684               glNormal3f (norm[jj].x, norm[jj].y, norm[jj].z);
685               glVertex3f (ring[jj].x, ring[jj].y, ring[jj].z);
686               if (wire) glVertex3f (ctr.x, ctr.y, ctrz);
687               t->mi->polygon_count++;
688             }
689           glEnd();
690         }
691
692       /* Now move to the end of this segment in preparation for the next.
693        */
694       if (i != t->nsegments-1)
695         {
696           XYZ p;
697           p.x = 0;
698           p.y = t->segments[i].length;
699           p.z = 0;
700           ROT (p);
701           ctr.x += p.x;
702           ctr.y += p.y;
703           ctr.z += p.z;
704
705           /* Accumulate the current angle and rotation, to keep track of the
706              rotation of the upcoming segment.
707           */
708           cth  += t->segments[i].th;
709           cphi += t->segments[i].phi;
710
711           cth_sin  = sin (cth);
712           cth_cos  = cos (cth);
713           cphi_sin = sin (cphi);
714           cphi_cos = cos (cphi);
715
716           memcpy (oring, ring, arg_slices * sizeof(*ring));
717           memcpy (onorm, norm, arg_slices * sizeof(*norm));
718         }
719
720       t0 = t1;
721     }
722
723   glPopMatrix();
724
725   free (ring);
726   free (norm);
727   free (oring);
728   free (onorm);
729   free (ucirc);
730 }
731
732
733 static void
734 draw_tentacle (tentacle *t, Bool front_p)
735 {
736 # ifndef HAVE_POLYGONMODE
737   Bool wire = MI_IS_WIREFRAME (t->mi);
738   if (!wire && cel_p && front_p)
739     {
740       draw_tentacle_1 (t, front_p, True);
741       glClear (GL_DEPTH_BUFFER_BIT);
742     }
743 # endif /* HAVE_POLYGONMODE */
744
745   draw_tentacle_1 (t, front_p, False);
746 }
747
748
749 static void
750 move_tentacle (tentacle *t)
751 {
752   /* tentacles_configuration *tc = &tcs[MI_SCREEN(t->mi)]; */
753   GLfloat len = 0;
754   double pos = 0;
755   int i, j;
756   int skip = t->nsegments * (1 - (arg_wiggliness + 0.5));
757   int tick = 0;
758   int last = 0;
759
760   for (i = 0; i < t->nsegments; i++)
761     {
762       if (++tick >= skip || i == t->nsegments-1)
763         {
764           /* randomize the motion of this segment... */
765           double x, y, z;
766           double phi_range = M_PI * 0.8 * arg_flexibility;
767           double th_range  = M_PI * 0.9 * arg_flexibility;
768           get_position (t->segments[i].rot, &x, &y, &z, True);
769           t->segments[i].phi = phi_range * (0.5 - y);
770           t->segments[i].th  = th_range  * (0.5 - z);
771           t->segments[i].length = ((0.8 + ((0.5 - x) * 0.4)) * 
772                                    (arg_length / t->nsegments));
773
774           /* ...and make the previous N segments be interpolated
775              between this one and the previous randomized one. */
776           for (j = last+1; j <= i; j++)
777             {
778               t->segments[j].phi    = (t->segments[i].phi / (i - last));
779               t->segments[j].th     = (t->segments[i].th  / (i - last));
780               t->segments[j].length = (t->segments[i].length);
781             }
782
783           tick = 0;
784           last = i;
785         }
786       len += t->segments[i].length;
787     }
788
789   /* thickness of segment is relative to current position on tentacle
790      (not just the index of the segment). */
791   for (i = 0; i < t->nsegments; i++)
792     {
793       if (i > 0)
794         {
795           double tt = (t->segments[0].thickness * (len - pos) / len);
796           if (tt < 0.001) tt = 0.001;
797           t->segments[i].thickness = tt;
798         }
799       pos += t->segments[i].length;
800     }
801 }
802
803
804
805 ENTRYPOINT Bool
806 tentacles_handle_event (ModeInfo *mi, XEvent *event)
807 {
808   tentacles_configuration *tc = &tcs[MI_SCREEN(mi)];
809
810   if (gltrackball_event_handler (event, tc->trackball,
811                                  MI_WIDTH (mi), MI_HEIGHT (mi),
812                                  &tc->button_down_p))
813     return True;
814   else if (event->xany.type == KeyPress)
815     {
816       KeySym keysym;
817       char c = 0;
818       XLookupString (&event->xkey, &c, 1, &keysym, 0);
819       if (c == ' ')
820         {
821           gltrackball_reset (tc->trackball, 0, 0);
822           return True;
823         }
824     }
825
826   return False;
827 }
828
829
830 static void
831 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
832 {
833   XColor c;
834   a[3] = 1.0;  /* alpha */
835
836   if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
837     {
838       fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
839       exit (1);
840     }
841   a[0] = c.red   / 65536.0;
842   a[1] = c.green / 65536.0;
843   a[2] = c.blue  / 65536.0;
844 }
845
846
847 ENTRYPOINT void 
848 init_tentacles (ModeInfo *mi)
849 {
850   tentacles_configuration *tc;
851   int wire = MI_IS_WIREFRAME(mi);
852   int i;
853
854   MI_INIT (mi, tcs, NULL);
855
856   tc = &tcs[MI_SCREEN(mi)];
857
858   tc->glx_context = init_GL(mi);
859
860   reshape_tentacles (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
861
862   if (!wire)
863     {
864       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
865       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
866       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
867       glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
868       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
869       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
870       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
871     }
872
873   if (!wire && !cel_p)
874     {
875       glEnable (GL_LIGHTING);
876       glEnable (GL_LIGHT0);
877     }
878
879   tc->trackball = gltrackball_init (False);
880
881   tc->left_p = !(random() % 5);
882
883   if (arg_segments    < 2) arg_segments    = 2;
884   if (arg_slices      < 3) arg_slices      = 3;
885   if (arg_thickness < 0.1) arg_thickness   = 0.1;
886   if (arg_wiggliness  < 0) arg_wiggliness  = 0;
887   if (arg_wiggliness  > 1) arg_wiggliness  = 1;
888   if (arg_flexibility < 0) arg_flexibility = 0;
889   if (arg_flexibility > 1) arg_flexibility = 1;
890
891   parse_color (mi, "tentacleColor", arg_color,  tc->tentacle_color);
892   parse_color (mi, "stripeColor",   arg_stripe, tc->stripe_color);
893   parse_color (mi, "suckerColor",   arg_sucker, tc->sucker_color);
894
895   /* Black outlines for light colors, white outlines for dark colors. */
896   if (tc->tentacle_color[0] + tc->tentacle_color[1] + tc->tentacle_color[2]
897       < 0.4)
898     tc->outline_color[0] = 1;
899   tc->outline_color[1] = tc->outline_color[0];
900   tc->outline_color[2] = tc->outline_color[0];
901   tc->outline_color[3] = 1;
902
903   for (i = 0; i < MI_COUNT(mi); i++)
904     move_tentacle (make_tentacle (mi, i, MI_COUNT(mi)));
905
906   if (wire) texture_p = cel_p = False;
907   if (cel_p) texture_p = False;
908
909   if (texture_p || cel_p) {
910     glGenTextures(1, &tc->texid);
911 # ifdef HAVE_GLBINDTEXTURE
912     glBindTexture ((cel_p ? GL_TEXTURE_1D : GL_TEXTURE_2D), tc->texid);
913 # endif
914
915     tc->texture = xpm_to_ximage (MI_DISPLAY(mi), MI_VISUAL(mi), 
916                                  MI_COLORMAP(mi), 
917                                  (cel_p ? grey_texture : scales));
918     if (!tc->texture) texture_p = cel_p = False;
919   }
920
921   if (texture_p) {
922     glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
923     clear_gl_error();
924     glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
925                   tc->texture->width, tc->texture->height, 0,
926                   GL_RGBA,
927                   /* GL_UNSIGNED_BYTE, */
928                   GL_UNSIGNED_INT_8_8_8_8_REV,
929                   tc->texture->data);
930     check_gl_error("texture");
931
932     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
933     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
934
935     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
936     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
937
938     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
939
940     glEnable(GL_TEXTURE_2D);
941   } else if (cel_p) {
942     clear_gl_error();
943     glTexImage1D (GL_TEXTURE_1D, 0, GL_RGBA,
944                   tc->texture->width, 0,
945                   GL_RGBA,
946                   /* GL_UNSIGNED_BYTE, */
947                   GL_UNSIGNED_INT_8_8_8_8_REV,
948                   tc->texture->data);
949     check_gl_error("texture");
950
951     glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
952     glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT);
953
954     glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
955     glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
956
957     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
958
959     glEnable(GL_TEXTURE_1D);
960     glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
961     glEnable (GL_LINE_SMOOTH);
962     glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
963     glEnable (GL_BLEND);
964
965     /* Dark gray instead of black, so the outlines show up */
966     glClearColor (0.13, 0.13, 0.13, 1.0);
967   }
968
969   compute_unit_torus (mi, 0.5, 
970                       MAX(5, arg_slices/6),
971                       MAX(9, arg_slices/3));
972 }
973
974
975 ENTRYPOINT void
976 draw_tentacles (ModeInfo *mi)
977 {
978   tentacles_configuration *tc = &tcs[MI_SCREEN(mi)];
979   Display *dpy = MI_DISPLAY(mi);
980   Window window = MI_WINDOW(mi);
981   int i;
982
983   if (!tc->glx_context)
984     return;
985
986   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(tc->glx_context));
987
988   glShadeModel(GL_SMOOTH);
989
990   glEnable(GL_DEPTH_TEST);
991   glEnable(GL_NORMALIZE);
992   glEnable(GL_CULL_FACE);
993
994   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
995
996   glPushMatrix ();
997   glRotatef(current_device_rotation(), 0, 0, 1);
998
999 # if 1
1000   glScalef (3, 3, 3);
1001 # else
1002   glPushAttrib (GL_ENABLE_BIT);
1003   glPushMatrix();
1004   { GLfloat s = 8.7/1600; glScalef(s,s,s); }
1005   glTranslatef(-800,-514,0);
1006   glDisable(GL_LIGHTING);
1007   glDisable(GL_TEXTURE_1D);
1008   glDisable(GL_TEXTURE_2D);
1009   glColor3f (1, 1, 1);
1010   glBegin(GL_LINE_LOOP);
1011   glVertex3f(0,0,0);
1012   glVertex3f(0,1028,0);
1013   glVertex3f(1600,1028,0);
1014   glVertex3f(1600,0,0);
1015   glEnd();
1016   glPopMatrix();
1017   glPopAttrib();
1018 # endif
1019
1020   gltrackball_rotate (tc->trackball);
1021
1022   mi->polygon_count = 0;
1023
1024   if (debug_p)
1025     {
1026       glPushAttrib (GL_ENABLE_BIT);
1027       glDisable (GL_LIGHTING);
1028       glDisable (GL_TEXTURE_1D);
1029       glDisable (GL_TEXTURE_2D);
1030       glColor3f (1, 1, 1);
1031       glLineWidth (1);
1032       glBegin(GL_LINES);
1033       glVertex3f(-0.5, 0, 0); glVertex3f(0.5, 0, 0);
1034       glVertex3f(0, -0.5, 0); glVertex3f(0, 0.5, 0);
1035       glEnd();
1036       glPopAttrib();
1037     }
1038   else
1039     {
1040       GLfloat rx =  45;
1041       GLfloat ry = -45;
1042       GLfloat rz =  70;
1043       if (tc->left_p)
1044         ry = -ry, rz = -rz;
1045       glRotatef (ry, 0, 1, 0);
1046       glRotatef (rx, 1, 0, 0);
1047       glRotatef (rz, 0, 0, 1);
1048       if (intersect_p)
1049         glTranslatef (0, -2.0, -4.5);
1050       else
1051         glTranslatef (0, -2.5, -5.0);
1052     }
1053
1054   if (!tc->button_down_p)
1055     for (i = 0; i < tc->ntentacles; i++)
1056       move_tentacle (tc->tentacles[i]);
1057
1058 #if 1
1059   for (i = 0; i < tc->ntentacles; i++)
1060     {
1061       if (! intersect_p)
1062         glClear(GL_DEPTH_BUFFER_BIT);
1063       draw_tentacle (tc->tentacles[i], True);
1064       if (cel_p)
1065         draw_tentacle (tc->tentacles[i], False);
1066     }
1067 #else
1068   glScalef (3, 3, 3);
1069   glScalef (1, 1, 4);
1070   glColor3f(1,1,1);
1071   glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
1072   draw_sucker (tc->tentacles[0], True);
1073   if (cel_p)
1074     {
1075       glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
1076       glLineWidth (tc->line_thickness);
1077       glColor4fv (tc->outline_color);
1078       draw_sucker (tc->tentacles[0], False);
1079     }
1080 #endif
1081
1082   glPopMatrix ();
1083
1084   if (mi->fps_p) do_fps (mi);
1085   glFinish();
1086
1087   glXSwapBuffers(dpy, window);
1088 }
1089
1090 XSCREENSAVER_MODULE_2 ("SkyTentacles", skytentacles, tentacles)
1091
1092 #endif /* USE_GL */