From http://www.jwz.org/xscreensaver/xscreensaver-5.30.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
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 (gltrackball_event_handler (event, bp->trackball,
231                                  MI_WIDTH (mi), MI_HEIGHT (mi),
232                                  &bp->button_down_p))
233     return True;
234   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
235     {
236       bp->last_time = 1;
237       return True;
238     }
239
240   return False;
241 }
242
243
244
245 ENTRYPOINT void 
246 init_knot (ModeInfo *mi)
247 {
248   knot_configuration *bp;
249   int wire = MI_IS_WIREFRAME(mi);
250
251   if (!bps) {
252     bps = (knot_configuration *)
253       calloc (MI_NUM_SCREENS(mi), sizeof (knot_configuration));
254     if (!bps) {
255       fprintf(stderr, "%s: out of memory\n", progname);
256       exit(1);
257     }
258   }
259
260   bp = &bps[MI_SCREEN(mi)];
261
262   bp->glx_context = init_GL(mi);
263
264   if (thickness <= 0) thickness = 0.001;
265   else if (thickness > 1) thickness = 1;
266
267   if (segments < 10) segments = 10;
268
269   reshape_knot (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
270
271   if (!wire)
272     {
273       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
274       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
275       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
276       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
277
278       glEnable(GL_LIGHTING);
279       glEnable(GL_LIGHT0);
280       glEnable(GL_DEPTH_TEST);
281       glEnable(GL_CULL_FACE);
282
283       glLightfv(GL_LIGHT0, GL_POSITION, pos);
284       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
285       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
286       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
287     }
288
289   {
290     Bool spinx=False, spiny=False, spinz=False;
291     double spin_speed   = 2.0;
292     double wander_speed = 0.05;
293     double spin_accel   = 0.2;
294
295     char *s = do_spin;
296     while (*s)
297       {
298         if      (*s == 'x' || *s == 'X') spinx = True;
299         else if (*s == 'y' || *s == 'Y') spiny = True;
300         else if (*s == 'z' || *s == 'Z') spinz = True;
301         else if (*s == '0') ;
302         else
303           {
304             fprintf (stderr,
305          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
306                      progname, do_spin);
307             exit (1);
308           }
309         s++;
310       }
311
312     bp->rot = make_rotator (spinx ? spin_speed : 0,
313                             spiny ? spin_speed : 0,
314                             spinz ? spin_speed : 0,
315                             spin_accel,
316                             do_wander ? wander_speed : 0,
317                             (spinx && spiny && spinz));
318     bp->trackball = gltrackball_init (True);
319   }
320
321   bp->knot_list = glGenLists (1);
322   new_knot(mi);
323
324   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
325 }
326
327
328 ENTRYPOINT void
329 draw_knot (ModeInfo *mi)
330 {
331   knot_configuration *bp = &bps[MI_SCREEN(mi)];
332   Display *dpy = MI_DISPLAY(mi);
333   Window window = MI_WINDOW(mi);
334
335   GLfloat bcolor[4] = {0.0, 0.0, 0.0, 1.0};
336   GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
337   GLfloat bshiny    = 128.0;
338
339   if (!bp->glx_context)
340     return;
341
342   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
343
344   if (bp->mode == 0)
345     {
346       if (bp->draw_tick++ > 10)
347         {
348           time_t now = time((time_t *) 0);
349           if (bp->last_time == 0) bp->last_time = now;
350           bp->draw_tick = 0;
351           if (!bp->button_down_p &&
352               now - bp->last_time >= duration)
353             {
354               bp->mode = 1;    /* go out */
355               bp->mode_tick = 10 * speed;
356               bp->last_time = now;
357             }
358         }
359     }
360   else if (bp->mode == 1)   /* out */
361     {
362       if (--bp->mode_tick <= 0)
363         {
364           new_knot (mi);
365           bp->mode_tick = 10 * speed;
366           bp->mode = 2;  /* go in */
367         }
368     }
369   else if (bp->mode == 2)   /* in */
370     {
371       if (--bp->mode_tick <= 0)
372         bp->mode = 0;  /* normal */
373     }
374   else
375     abort();
376
377   glShadeModel(GL_SMOOTH);
378
379   glEnable(GL_DEPTH_TEST);
380   glEnable(GL_NORMALIZE);
381   glEnable(GL_CULL_FACE);
382
383   if (bp->clear_p)
384     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
385
386   glPushMatrix ();
387
388   {
389     double x, y, z;
390     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
391     glTranslatef((x - 0.5) * 8,
392                  (y - 0.5) * 8,
393                  (z - 0.5) * 15);
394
395     gltrackball_rotate (bp->trackball);
396
397     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
398
399     glRotatef (x * 360, 1.0, 0.0, 0.0);
400     glRotatef (y * 360, 0.0, 1.0, 0.0);
401     glRotatef (z * 360, 0.0, 0.0, 1.0);
402   }
403
404   bcolor[0] = bp->colors[bp->ccolor].red   / 65536.0;
405   bcolor[1] = bp->colors[bp->ccolor].green / 65536.0;
406   bcolor[2] = bp->colors[bp->ccolor].blue  / 65536.0;
407   bp->ccolor++;
408   if (bp->ccolor >= bp->ncolors) bp->ccolor = 0;
409
410   glMaterialfv (GL_FRONT, GL_SPECULAR,            bspec);
411   glMateriali  (GL_FRONT, GL_SHININESS,           bshiny);
412   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bcolor);
413
414   glScalef(0.25, 0.25, 0.25);
415
416   if (bp->mode != 0)
417     {
418       GLfloat s = (bp->mode == 1
419                    ? bp->mode_tick / (10 * speed)
420                    : ((10 * speed) - bp->mode_tick + 1) / (10 * speed));
421       glScalef (s, s, s);
422     }
423
424   glCallList (bp->knot_list);
425
426   glPopMatrix ();
427
428   if (mi->fps_p) do_fps (mi);
429   glFinish();
430
431   glXSwapBuffers(dpy, window);
432 }
433
434 XSCREENSAVER_MODULE_2 ("GLKnots", glknots, knot)
435
436 #endif /* USE_GL */