From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / glx / glknots.c
1 /* glknots, Copyright (c) 2003-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  * Generates some 3D knots (closed loops).
12  * Inspired by Paul Bourke <pbourke@swin.edu.au> at
13  * http://astronomy.swin.edu.au/~pbourke/curves/knot/
14  */
15
16 #define DEFAULTS        "*delay:        30000       \n" \
17                         "*showFPS:      False       \n" \
18                         "*wireframe:    False       \n" \
19                         "*suppressRotationAnimation: True\n" \
20
21 # define refresh_knot 0
22 # define release_knot 0
23 #undef countof
24 #define countof(x) (sizeof((x))/sizeof((*x)))
25
26 #include "xlockmore.h"
27 #include "colors.h"
28 #include "tube.h"
29 #include "rotator.h"
30 #include "gltrackball.h"
31 #include <ctype.h>
32
33 #ifdef USE_GL /* whole file */
34
35 #define DEF_SPIN        "XYZ"
36 #define DEF_WANDER      "True"
37 #define DEF_SPEED       "1.0"
38 #define DEF_THICKNESS   "0.3"
39 #define DEF_SEGMENTS    "800"
40 #define DEF_DURATION    "8"
41
42 typedef struct {
43   GLXContext *glx_context;
44   rotator *rot;
45   trackball_state *trackball;
46   Bool button_down_p;
47
48   GLuint knot_list;
49
50   int ncolors;
51   XColor *colors;
52   int ccolor;
53
54   int mode;  /* 0 = normal, 1 = out, 2 = in */
55   int mode_tick;
56   Bool clear_p;
57
58   time_t last_time;
59   int draw_tick;
60
61 } knot_configuration;
62
63 static knot_configuration *bps = NULL;
64
65 static char *do_spin;
66 static GLfloat speed;
67 static Bool do_wander;
68 static GLfloat thickness;
69 static unsigned int segments;
70 static int duration;
71
72 static XrmOptionDescRec opts[] = {
73   { "-spin",   ".spin",   XrmoptionSepArg, 0 },
74   { "+spin",   ".spin",   XrmoptionNoArg, "" },
75   { "-wander", ".wander", XrmoptionNoArg, "True" },
76   { "+wander", ".wander", XrmoptionNoArg, "False" },
77   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
78   { "-thickness", ".thickness",  XrmoptionSepArg, 0 },
79   { "-segments",  ".segments",   XrmoptionSepArg, 0 },
80   { "-duration",  ".duration",   XrmoptionSepArg, 0 },
81 };
82
83 static argtype vars[] = {
84   {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_String},
85   {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
86   {&speed,     "speed",  "Speed",  DEF_SPEED,  t_Float},
87   {&thickness, "thickness", "Thickness",  DEF_THICKNESS, t_Float},
88   {&segments,  "segments",  "Segments",   DEF_SEGMENTS,  t_Int},
89   {&duration,  "duration",  "Duration",   DEF_DURATION,  t_Int},
90 };
91
92 ENTRYPOINT ModeSpecOpt knot_opts = {countof(opts), opts, countof(vars), vars, NULL};
93
94
95 static void
96 make_knot (ModeInfo *mi)
97 {
98   int wire = MI_IS_WIREFRAME(mi);
99
100   GLfloat diam = (4 * thickness);
101   int faces = (wire ? 3 : 6);
102
103   unsigned int i;
104   double x, y, z, ox=0, oy=0, oz=0;
105   double mu;
106
107   double p[9];
108
109   Bool blobby_p = (0 == (random() % 5));
110   Bool type = (random() % 2);
111
112   for (i = 0; i < countof(p); i++)
113     {
114       p[i] = 1 + (random() % 4);
115       if (! (random() % 3))
116         p[i] += (random() % 5);
117     }
118
119   if (type == 1)
120     {
121       p[0] += 4;
122       p[1] *= ((p[0] + p[0]) / 10);
123       blobby_p = False;
124     }
125
126   mi->polygon_count = 0;
127
128   for (i = 0; i <= segments; i++)
129     {
130       if (type == 0)
131         {
132           mu = i * (M_PI * 2) / segments;
133           x = 10 * (cos(mu) + cos(p[0]*mu)) + cos(p[1]*mu) + cos(p[2]*mu);
134           y = 6 * sin(mu) + 10 * sin(p[3]*mu);
135           z = 16 * sin(p[4]*mu) * sin(p[5]*mu/2) + p[6]*sin(p[7]*mu) -
136             2 * sin(p[8]*mu);
137         }
138       else if (type == 1)
139         {
140           mu = i * (M_PI * 2) * p[0] / (double) segments;
141           x = 10 * cos(mu) * (1 + cos(p[1] * mu/ p[0]) / 2.0);
142           y = 25 * sin(p[1] * mu / p[0]) / 2.0;
143           z = 10 * sin(mu) * (1 + cos(p[1] * mu/ p[0]) / 2.0);
144         }
145       else
146         abort();
147
148       if (i != 0)
149         {
150           GLfloat dist = sqrt ((x-ox)*(x-ox) +
151                                (y-oy)*(y-oy) +
152                                (z-oz)*(z-oz));
153           GLfloat di;
154           if (!blobby_p)
155             di = diam;
156           else
157             {
158               di = dist * (segments / 500.0);
159               di = (di * di * 3);
160             }
161
162           mi->polygon_count += tube (ox, oy, oz,
163                                      x, y, z,
164                                      di, dist/3,
165                                      faces, True, wire, wire);
166         }
167
168       ox = x;
169       oy = y;
170       oz = z;
171    }
172 }
173
174
175 /* Window management, etc
176  */
177 ENTRYPOINT void
178 reshape_knot (ModeInfo *mi, int width, int height)
179 {
180   GLfloat h = (GLfloat) height / (GLfloat) width;
181
182   glViewport (0, 0, (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 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
195   {
196     int o = (int) current_device_rotation();
197     if (o != 0 && o != 180 && o != -180)
198       glScalef (1/h, 1/h, 1/h);
199   }
200 # endif
201
202   glClear(GL_COLOR_BUFFER_BIT);
203 }
204
205
206 static void
207 new_knot (ModeInfo *mi)
208 {
209   knot_configuration *bp = &bps[MI_SCREEN(mi)];
210   int i;
211
212   bp->clear_p = !!(random() % 15);
213
214   bp->ncolors = 128;
215   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
216   make_smooth_colormap (0, 0, 0,
217                         bp->colors, &bp->ncolors,
218                         False, 0, False);
219
220   for (i = 0; i < bp->ncolors; i++)
221     {
222       /* make colors twice as bright */
223       bp->colors[i].red   = (bp->colors[i].red   >> 2) + 0x7FFF;
224       bp->colors[i].green = (bp->colors[i].green >> 2) + 0x7FFF;
225       bp->colors[i].blue  = (bp->colors[i].blue  >> 2) + 0x7FFF;
226     }
227
228   glNewList (bp->knot_list, GL_COMPILE);
229   make_knot (mi);
230   glEndList ();
231 }
232
233
234 ENTRYPOINT Bool
235 knot_handle_event (ModeInfo *mi, XEvent *event)
236 {
237   knot_configuration *bp = &bps[MI_SCREEN(mi)];
238
239   if (gltrackball_event_handler (event, bp->trackball,
240                                  MI_WIDTH (mi), MI_HEIGHT (mi),
241                                  &bp->button_down_p))
242     return True;
243   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
244     {
245       bp->last_time = 1;
246       return True;
247     }
248
249   return False;
250 }
251
252
253
254 ENTRYPOINT void 
255 init_knot (ModeInfo *mi)
256 {
257   knot_configuration *bp;
258   int wire = MI_IS_WIREFRAME(mi);
259
260   if (!bps) {
261     bps = (knot_configuration *)
262       calloc (MI_NUM_SCREENS(mi), sizeof (knot_configuration));
263     if (!bps) {
264       fprintf(stderr, "%s: out of memory\n", progname);
265       exit(1);
266     }
267   }
268
269   bp = &bps[MI_SCREEN(mi)];
270
271   bp->glx_context = init_GL(mi);
272
273   if (thickness <= 0) thickness = 0.001;
274   else if (thickness > 1) thickness = 1;
275
276   if (segments < 10) segments = 10;
277
278   reshape_knot (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
279
280   if (!wire)
281     {
282       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
283       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
284       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
285       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
286
287       glEnable(GL_LIGHTING);
288       glEnable(GL_LIGHT0);
289       glEnable(GL_DEPTH_TEST);
290       glEnable(GL_CULL_FACE);
291
292       glLightfv(GL_LIGHT0, GL_POSITION, pos);
293       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
294       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
295       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
296     }
297
298   {
299     Bool spinx=False, spiny=False, spinz=False;
300     double spin_speed   = 2.0;
301     double wander_speed = 0.05;
302     double spin_accel   = 0.2;
303
304     char *s = do_spin;
305     while (*s)
306       {
307         if      (*s == 'x' || *s == 'X') spinx = True;
308         else if (*s == 'y' || *s == 'Y') spiny = True;
309         else if (*s == 'z' || *s == 'Z') spinz = True;
310         else if (*s == '0') ;
311         else
312           {
313             fprintf (stderr,
314          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
315                      progname, do_spin);
316             exit (1);
317           }
318         s++;
319       }
320
321     bp->rot = make_rotator (spinx ? spin_speed : 0,
322                             spiny ? spin_speed : 0,
323                             spinz ? spin_speed : 0,
324                             spin_accel,
325                             do_wander ? wander_speed : 0,
326                             (spinx && spiny && spinz));
327     bp->trackball = gltrackball_init (True);
328   }
329
330   bp->knot_list = glGenLists (1);
331   new_knot(mi);
332
333   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
334 }
335
336
337 ENTRYPOINT void
338 draw_knot (ModeInfo *mi)
339 {
340   knot_configuration *bp = &bps[MI_SCREEN(mi)];
341   Display *dpy = MI_DISPLAY(mi);
342   Window window = MI_WINDOW(mi);
343
344   GLfloat bcolor[4] = {0.0, 0.0, 0.0, 1.0};
345   GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
346   GLfloat bshiny    = 128.0;
347
348   if (!bp->glx_context)
349     return;
350
351   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
352
353   if (bp->mode == 0)
354     {
355       if (bp->draw_tick++ > 10)
356         {
357           time_t now = time((time_t *) 0);
358           if (bp->last_time == 0) bp->last_time = now;
359           bp->draw_tick = 0;
360           if (!bp->button_down_p &&
361               now - bp->last_time >= duration)
362             {
363               bp->mode = 1;    /* go out */
364               bp->mode_tick = 10 / speed;
365               bp->last_time = now;
366             }
367         }
368     }
369   else if (bp->mode == 1)   /* out */
370     {
371       if (--bp->mode_tick <= 0)
372         {
373           new_knot (mi);
374           bp->mode_tick = 10 / speed;
375           bp->mode = 2;  /* go in */
376         }
377     }
378   else if (bp->mode == 2)   /* in */
379     {
380       if (--bp->mode_tick <= 0)
381         bp->mode = 0;  /* normal */
382     }
383   else
384     abort();
385
386   glShadeModel(GL_SMOOTH);
387
388   glEnable(GL_DEPTH_TEST);
389   glEnable(GL_NORMALIZE);
390   glEnable(GL_CULL_FACE);
391
392   if (bp->clear_p)
393     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
394
395   glPushMatrix ();
396
397   {
398     double x, y, z;
399     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
400     glTranslatef((x - 0.5) * 8,
401                  (y - 0.5) * 8,
402                  (z - 0.5) * 15);
403
404     gltrackball_rotate (bp->trackball);
405
406     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
407
408     glRotatef (x * 360, 1.0, 0.0, 0.0);
409     glRotatef (y * 360, 0.0, 1.0, 0.0);
410     glRotatef (z * 360, 0.0, 0.0, 1.0);
411   }
412
413   bcolor[0] = bp->colors[bp->ccolor].red   / 65536.0;
414   bcolor[1] = bp->colors[bp->ccolor].green / 65536.0;
415   bcolor[2] = bp->colors[bp->ccolor].blue  / 65536.0;
416   bp->ccolor++;
417   if (bp->ccolor >= bp->ncolors) bp->ccolor = 0;
418
419   glMaterialfv (GL_FRONT, GL_SPECULAR,            bspec);
420   glMateriali  (GL_FRONT, GL_SHININESS,           bshiny);
421   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bcolor);
422
423   glScalef(0.25, 0.25, 0.25);
424
425   if (bp->mode != 0)
426     {
427       GLfloat s = (bp->mode == 1
428                    ? bp->mode_tick / (10 / speed)
429                    : ((10 / speed) - bp->mode_tick + 1) / (10 / speed));
430       glScalef (s, s, s);
431     }
432
433   glCallList (bp->knot_list);
434
435   glPopMatrix ();
436
437   if (mi->fps_p) do_fps (mi);
438   glFinish();
439
440   glXSwapBuffers(dpy, window);
441 }
442
443 XSCREENSAVER_MODULE_2 ("GLKnots", glknots, knot)
444
445 #endif /* USE_GL */