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