From http://www.jwz.org/xscreensaver/xscreensaver-5.38.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 free_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   int y = 0;
182
183   if (width > height * 5) {   /* tiny window: show middle */
184     height = width * 9/16;
185     y = -height/2;
186     h = height / (GLfloat) width;
187   }
188
189   glViewport (0, y, (GLint) width, (GLint) height);
190
191   glMatrixMode(GL_PROJECTION);
192   glLoadIdentity();
193   gluPerspective (30.0, 1/h, 1.0, 100.0);
194
195   glMatrixMode(GL_MODELVIEW);
196   glLoadIdentity();
197   gluLookAt( 0.0, 0.0, 30.0,
198              0.0, 0.0, 0.0,
199              0.0, 1.0, 0.0);
200
201 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
202   {
203     int o = (int) current_device_rotation();
204     if (o != 0 && o != 180 && o != -180)
205       glScalef (1/h, 1/h, 1/h);
206   }
207 # endif
208
209   glClear(GL_COLOR_BUFFER_BIT);
210 }
211
212
213 static void
214 new_knot (ModeInfo *mi)
215 {
216   knot_configuration *bp = &bps[MI_SCREEN(mi)];
217   int i;
218
219   bp->clear_p = !!(random() % 15);
220
221   bp->ncolors = 128;
222   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
223   make_smooth_colormap (0, 0, 0,
224                         bp->colors, &bp->ncolors,
225                         False, 0, False);
226
227   for (i = 0; i < bp->ncolors; i++)
228     {
229       /* make colors twice as bright */
230       bp->colors[i].red   = (bp->colors[i].red   >> 2) + 0x7FFF;
231       bp->colors[i].green = (bp->colors[i].green >> 2) + 0x7FFF;
232       bp->colors[i].blue  = (bp->colors[i].blue  >> 2) + 0x7FFF;
233     }
234
235   glNewList (bp->knot_list, GL_COMPILE);
236   make_knot (mi);
237   glEndList ();
238 }
239
240
241 ENTRYPOINT Bool
242 knot_handle_event (ModeInfo *mi, XEvent *event)
243 {
244   knot_configuration *bp = &bps[MI_SCREEN(mi)];
245
246   if (gltrackball_event_handler (event, bp->trackball,
247                                  MI_WIDTH (mi), MI_HEIGHT (mi),
248                                  &bp->button_down_p))
249     return True;
250   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
251     {
252       bp->last_time = 1;
253       return True;
254     }
255
256   return False;
257 }
258
259
260
261 ENTRYPOINT void 
262 init_knot (ModeInfo *mi)
263 {
264   knot_configuration *bp;
265   int wire = MI_IS_WIREFRAME(mi);
266
267   MI_INIT (mi, bps);
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 */