http://www.jwz.org/xscreensaver/xscreensaver-5.12.tar.gz
[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
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 ();
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     tc->texture = xpm_to_ximage (MI_DISPLAY(mi), MI_VISUAL(mi), 
915                                  MI_COLORMAP(mi), 
916                                  (cel_p ? grey_texture : scales));
917     if (!tc->texture) texture_p = cel_p = False;
918   }
919
920   if (texture_p) {
921     glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
922     clear_gl_error();
923     glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
924                   tc->texture->width, tc->texture->height, 0,
925                   GL_RGBA,
926                   /* GL_UNSIGNED_BYTE, */
927                   GL_UNSIGNED_INT_8_8_8_8_REV,
928                   tc->texture->data);
929     check_gl_error("texture");
930
931     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
932     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
933
934     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
935     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
936
937     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
938
939     glEnable(GL_TEXTURE_2D);
940   } else if (cel_p) {
941     clear_gl_error();
942     glTexImage1D (GL_TEXTURE_1D, 0, GL_RGBA,
943                   tc->texture->width, 0,
944                   GL_RGBA,
945                   /* GL_UNSIGNED_BYTE, */
946                   GL_UNSIGNED_INT_8_8_8_8_REV,
947                   tc->texture->data);
948     check_gl_error("texture");
949
950     glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
951     glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT);
952
953     glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
954     glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
955
956     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
957
958     glEnable(GL_TEXTURE_1D);
959     glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
960     glEnable (GL_LINE_SMOOTH);
961     glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
962     glEnable (GL_BLEND);
963
964     /* Dark gray instead of black, so the outlines show up */
965     glClearColor (0.08, 0.08, 0.08, 1.0);
966   }
967
968   compute_unit_torus (mi, 0.5, 
969                       MAX(5, arg_slices/6),
970                       MAX(9, arg_slices/3));
971 }
972
973
974 ENTRYPOINT void
975 draw_tentacles (ModeInfo *mi)
976 {
977   tentacles_configuration *tc = &tcs[MI_SCREEN(mi)];
978   Display *dpy = MI_DISPLAY(mi);
979   Window window = MI_WINDOW(mi);
980   int i;
981
982   if (!tc->glx_context)
983     return;
984
985   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(tc->glx_context));
986
987   glShadeModel(GL_SMOOTH);
988
989   glEnable(GL_DEPTH_TEST);
990   glEnable(GL_NORMALIZE);
991   glEnable(GL_CULL_FACE);
992
993   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
994
995   glPushMatrix ();
996
997
998 # if 1
999   glScalef (3, 3, 3);
1000 # else
1001   glPushAttrib (GL_ENABLE_BIT);
1002   glPushMatrix();
1003   { GLfloat s = 8.7/1600; glScalef(s,s,s); }
1004   glTranslatef(-800,-514,0);
1005   glDisable(GL_LIGHTING);
1006   glDisable(GL_TEXTURE_1D);
1007   glDisable(GL_TEXTURE_2D);
1008   glColor3f (1, 1, 1);
1009   glBegin(GL_LINE_LOOP);
1010   glVertex3f(0,0,0);
1011   glVertex3f(0,1028,0);
1012   glVertex3f(1600,1028,0);
1013   glVertex3f(1600,0,0);
1014   glEnd();
1015   glPopMatrix();
1016   glPopAttrib();
1017 # endif
1018
1019   gltrackball_rotate (tc->trackball);
1020
1021   mi->polygon_count = 0;
1022
1023   if (debug_p)
1024     {
1025       glPushAttrib (GL_ENABLE_BIT);
1026       glDisable (GL_LIGHTING);
1027       glDisable (GL_TEXTURE_1D);
1028       glDisable (GL_TEXTURE_2D);
1029       glColor3f (1, 1, 1);
1030       glLineWidth (1);
1031       glBegin(GL_LINES);
1032       glVertex3f(-0.5, 0, 0); glVertex3f(0.5, 0, 0);
1033       glVertex3f(0, -0.5, 0); glVertex3f(0, 0.5, 0);
1034       glEnd();
1035       glPopAttrib();
1036     }
1037   else
1038     {
1039       GLfloat rx =  45;
1040       GLfloat ry = -45;
1041       GLfloat rz =  70;
1042       if (tc->left_p)
1043         ry = -ry, rz = -rz;
1044       glRotatef (ry, 0, 1, 0);
1045       glRotatef (rx, 1, 0, 0);
1046       glRotatef (rz, 0, 0, 1);
1047       if (intersect_p)
1048         glTranslatef (0, -2.0, -4.5);
1049       else
1050         glTranslatef (0, -2.5, -5.0);
1051     }
1052
1053   if (!tc->button_down_p)
1054     for (i = 0; i < tc->ntentacles; i++)
1055       move_tentacle (tc->tentacles[i]);
1056
1057 #if 1
1058   for (i = 0; i < tc->ntentacles; i++)
1059     {
1060       if (! intersect_p)
1061         glClear(GL_DEPTH_BUFFER_BIT);
1062       draw_tentacle (tc->tentacles[i], True);
1063       if (cel_p)
1064         draw_tentacle (tc->tentacles[i], False);
1065     }
1066 #else
1067   glScalef (3, 3, 3);
1068   glScalef (1, 1, 4);
1069   glColor3f(1,1,1);
1070   glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
1071   draw_sucker (tc->tentacles[0], True);
1072   if (cel_p)
1073     {
1074       glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
1075       glLineWidth (tc->line_thickness);
1076       glColor4fv (tc->outline_color);
1077       draw_sucker (tc->tentacles[0], False);
1078     }
1079 #endif
1080
1081   glPopMatrix ();
1082
1083   if (mi->fps_p) do_fps (mi);
1084   glFinish();
1085
1086   glXSwapBuffers(dpy, window);
1087 }
1088
1089 XSCREENSAVER_MODULE_2 ("SkyTentacles", skytentacles, tentacles)
1090
1091 #endif /* USE_GL */