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