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