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