http://nanonyme.dy.fi/mirrors/hvl/distfiles/xscreensaver/xscreensaver-5.03.tar.gz
[xscreensaver] / hacks / glx / voronoi.c
1 /* voronoi, Copyright (c) 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
12 #define DEFAULTS        "*delay:        20000              \n" \
13                         "*showFPS:      False              \n" \
14
15
16 # define refresh_voronoi 0
17 # define release_voronoi 0
18 #undef countof
19 #define countof(x) (sizeof((x))/sizeof((*x)))
20
21 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
22
23
24 #include "xlockmore.h"
25 #include <ctype.h>
26
27 #ifdef USE_GL /* whole file */
28
29 #define DEF_POINTS      "10"
30 #define DEF_POINT_SIZE  "9"
31 #define DEF_POINT_SPEED "1"
32 #define DEF_POINT_DELAY "0.1"
33 #define DEF_ZOOM_SPEED  "1"
34 #define DEF_ZOOM_DELAY  "10"
35
36 typedef struct node {
37   GLfloat x, y;
38   GLfloat dx, dy;
39   GLfloat ddx, ddy;
40   struct node *next;
41   GLfloat color[4], color2[4];
42   int rot;
43 } node;
44
45 typedef struct {
46   GLXContext *glx_context;
47   node *nodes;
48   int nnodes;
49   node *dragging;
50   int ncolors;
51   XColor *colors;
52
53   enum { MODE_WAITING, MODE_ADDING, MODE_ZOOMING } mode;
54   int adding;
55   double last_time;
56
57   GLfloat zooming;         /* 1.0 starting zoom, 0.0 no longer zooming. */
58   GLfloat zoom_toward[2];
59
60 } voronoi_configuration;
61
62 static voronoi_configuration *vps = NULL;
63
64 /* command line arguments */
65 static int npoints;
66 static GLfloat point_size, point_speed, point_delay;
67 static GLfloat zoom_speed, zoom_delay;
68
69 static XrmOptionDescRec opts[] = {
70   { "-points",       ".points",      XrmoptionSepArg, 0 },
71   { "-point-size",   ".pointSize",   XrmoptionSepArg, 0 },
72   { "-point-speed",  ".pointSpeed",  XrmoptionSepArg, 0 },
73   { "-point-delay",  ".pointDelay",  XrmoptionSepArg, 0 },
74   { "-zoom-speed",   ".zoomSpeed",   XrmoptionSepArg, 0 },
75   { "-zoom-delay",   ".zoomDelay",   XrmoptionSepArg, 0 },
76 };
77
78 static argtype vars[] = {
79   {&npoints,      "points",      "Points",      DEF_POINTS,       t_Int},
80   {&point_size,   "pointSize",   "PointSize",   DEF_POINT_SIZE,   t_Float},
81   {&point_speed,  "pointSpeed",  "PointSpeed",  DEF_POINT_SPEED,  t_Float},
82   {&point_delay,  "pointDelay",  "PointDelay",  DEF_POINT_DELAY,  t_Float},
83   {&zoom_speed,   "zoomSpeed",   "ZoomSpeed",   DEF_ZOOM_SPEED,   t_Float},
84   {&zoom_delay,   "zoomDelay",   "ZoomDelay",   DEF_ZOOM_DELAY,   t_Float},
85 };
86
87 ENTRYPOINT ModeSpecOpt voronoi_opts =
88   {countof(opts), opts, countof(vars), vars, NULL};
89
90
91 /* Returns the current time in seconds as a double.
92  */
93 static double
94 double_time (void)
95 {
96   struct timeval now;
97 # ifdef GETTIMEOFDAY_TWO_ARGS
98   struct timezone tzp;
99   gettimeofday(&now, &tzp);
100 # else
101   gettimeofday(&now);
102 # endif
103
104   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
105 }
106
107
108 static node *
109 add_node (voronoi_configuration *vp, GLfloat x, GLfloat y)
110 {
111   node *nn = (node *) calloc (1, sizeof (*nn));
112   int i;
113   nn->x = x;
114   nn->y = y;
115
116   i = random() % vp->ncolors;
117   nn->color[0] = vp->colors[i].red   / 65536.0;
118   nn->color[1] = vp->colors[i].green / 65536.0;
119   nn->color[2] = vp->colors[i].blue  / 65536.0;
120   nn->color[3] = 1.0;
121
122   nn->color2[0] = nn->color[0] * 0.7;
123   nn->color2[1] = nn->color[1] * 0.7;
124   nn->color2[2] = nn->color[2] * 0.7;
125   nn->color2[3] = 1.0;
126
127   nn->ddx = frand (0.000001 * point_speed) * (random() & 1 ? 1 : -1);
128   nn->ddy = frand (0.000001 * point_speed) * (random() & 1 ? 1 : -1);
129
130   nn->rot = (random() % 360) * (random() & 1 ? 1 : -1);
131
132   nn->next = vp->nodes;
133   vp->nodes = nn;
134   vp->nnodes++;
135   return nn;
136 }
137
138
139 static int
140 cone (void)
141 {
142   int i;
143   int faces = 64;
144   GLfloat step = M_PI * 2 / faces;
145   GLfloat s2 = step/2;
146   GLfloat th;
147   GLfloat x, y, x0, y0;
148
149   glBegin(GL_TRIANGLES);
150
151   th = 0;
152   x = 1;
153   y = 0;
154   x0 = cos (s2);
155   y0 = sin (s2);
156
157   for (i = 0; i < faces; i++)
158     {
159       glVertex3f(0,  0, 1);
160       glVertex3f(x, y, 0);
161
162       th += step;
163       x0 = cos (th + s2);
164       y0 = sin (th + s2);
165       x  = cos (th);
166       y  = sin (th);
167
168       glVertex3f(x, y, 0);
169     }
170   glEnd();
171   return faces;
172 }
173
174
175 static void
176 move_points (voronoi_configuration *vp)
177 {
178   node *nn;
179   for (nn = vp->nodes; nn; nn = nn->next)
180     {
181       if (nn == vp->dragging) continue;
182       nn->x  += nn->dx;
183       nn->y  += nn->dy;
184
185       if (vp->mode == MODE_WAITING)
186         {
187           nn->dx += nn->ddx;
188           nn->dy += nn->ddy;
189         }
190     }
191 }
192
193
194 static void
195 prune_points (voronoi_configuration *vp)
196 {
197   node *nn;
198   node *prev = 0;
199   int lim = 5;
200
201   for (nn = vp->nodes; nn; prev = nn, nn = (nn ? nn->next : 0))
202     if (nn->x < -lim || nn->x > lim ||
203         nn->y < -lim || nn->y > lim)
204       {
205         if (prev)
206           prev->next = nn->next;
207         else
208           vp->nodes = nn->next;
209         free (nn);
210         vp->nnodes--;
211         nn = prev;
212      }
213 }
214
215
216 static void
217 zoom_points (voronoi_configuration *vp)
218 {
219   node *nn;
220
221   GLfloat tick = sin (vp->zooming * M_PI);
222   GLfloat scale = 1 + (tick * 0.02 * zoom_speed);
223
224   vp->zooming -= (0.01 * zoom_speed);
225   if (vp->zooming < 0) vp->zooming = 0;
226
227   if (vp->zooming <= 0) return;
228
229   if (scale < 1) scale = 1;
230
231   for (nn = vp->nodes; nn; nn = nn->next)
232     {
233       GLfloat x = nn->x - vp->zoom_toward[0];
234       GLfloat y = nn->y - vp->zoom_toward[1];
235       x *= scale;
236       y *= scale;
237       nn->x = x + vp->zoom_toward[0];
238       nn->y = y + vp->zoom_toward[1];
239     }
240 }
241
242
243
244 static void
245 draw_cells (ModeInfo *mi)
246 {
247   voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
248   node *nn;
249   int lim = 5;
250
251   for (nn = vp->nodes; nn; nn = nn->next)
252     {
253       if (nn->x < -lim || nn->x > lim ||
254           nn->y < -lim || nn->y > lim)
255         continue;
256
257       glPushMatrix();
258       glTranslatef (nn->x, nn->y, 0);
259       glScalef (lim*2, lim*2, 1);
260       glColor4fv (nn->color);
261       mi->polygon_count += cone ();
262       glPopMatrix();
263     }
264
265   glClear (GL_DEPTH_BUFFER_BIT);
266
267   if (point_size <= 0)
268     ;
269   else if (point_size < 3)
270     {
271       glPointSize (point_size);
272       for (nn = vp->nodes; nn; nn = nn->next)
273         {
274           glBegin (GL_POINTS);
275           glColor4fv (nn->color2);
276           glVertex2f (nn->x, nn->y);
277           glEnd();
278         }
279     }
280   else
281     {
282       for (nn = vp->nodes; nn; nn = nn->next)
283         {
284           int w = MI_WIDTH (mi);
285           int h = MI_HEIGHT (mi);
286           int s = point_size;
287           int i;
288
289           glColor4fv (nn->color2);
290           glPushMatrix();
291           glTranslatef (nn->x, nn->y, 0);
292           glScalef (1.0 / w * s, 1.0 / h * s, 1);
293
294           glLineWidth (point_size / 10);
295           nn->rot += (nn->rot < 0 ? -1 : 1);
296           glRotatef (nn->rot, 0, 0, 1);
297
298           glRotatef (180, 0, 0, 1);
299           for (i = 0; i < 5; i++)
300             {
301               glBegin (GL_TRIANGLES);
302               glVertex2f (0, 1);
303               glVertex2f (-0.2, 0);
304               glVertex2f ( 0.2, 0);
305               glEnd ();
306               glRotatef (360.0/5, 0, 0, 1);
307             }
308           glPopMatrix();
309         }
310     }
311 }
312
313
314 /* Window management, etc
315  */
316 ENTRYPOINT void
317 reshape_voronoi (ModeInfo *mi, int width, int height)
318 {
319 /*  voronoi_configuration *vp = &vps[MI_SCREEN(mi)];*/
320
321   glViewport (0, 0, (GLint) width, (GLint) height);
322
323   glMatrixMode(GL_PROJECTION);
324   glLoadIdentity();
325   glOrtho (0, 1, 1, 0, -1, 1);
326
327   glMatrixMode(GL_MODELVIEW);
328   glLoadIdentity();
329
330   glClear(GL_COLOR_BUFFER_BIT);
331 }
332
333
334 static node *
335 find_node (ModeInfo *mi, GLfloat x, GLfloat y)
336 {
337   voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
338   int ps = (point_size < 5 ? 5 : point_size);
339   GLfloat hysteresis = (1.0 / MI_WIDTH (mi)) * ps;
340   node *nn;
341   for (nn = vp->nodes; nn; nn = nn->next)
342     if (nn->x > x - hysteresis && nn->x < x + hysteresis &&
343         nn->y > y - hysteresis && nn->y < y + hysteresis)
344       return nn;
345   return 0;
346 }
347
348
349 ENTRYPOINT Bool
350 voronoi_handle_event (ModeInfo *mi, XEvent *event)
351 {
352   voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
353
354   if (event->xany.type == ButtonPress)
355     {
356       GLfloat x = (GLfloat) event->xbutton.x / MI_WIDTH (mi);
357       GLfloat y = (GLfloat) event->xbutton.y / MI_HEIGHT (mi);
358       node *nn = find_node (mi, x, y);
359       if (!nn)
360         nn = add_node (vp, x, y);
361       vp->dragging = nn;
362
363       return True;
364     }
365   else if (event->xany.type == ButtonRelease && vp->dragging)
366     {
367       vp->dragging = 0;
368       return True;
369     }
370   else if (event->xany.type == MotionNotify && vp->dragging)
371     {
372       vp->dragging->x = (GLfloat) event->xmotion.x / MI_WIDTH (mi);
373       vp->dragging->y = (GLfloat) event->xmotion.y / MI_HEIGHT (mi);
374       return True;
375     }
376
377   return False;
378 }
379
380 static void
381 state_change (ModeInfo *mi)
382 {
383   voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
384   double now = double_time();
385
386   if (vp->dragging)
387     {
388       vp->last_time = now;
389       vp->adding = 0;
390       vp->zooming = 0;
391       return;
392     }
393
394   switch (vp->mode)
395     {
396     case MODE_WAITING:
397       if (vp->last_time + zoom_delay <= now)
398         {
399           node *tn = vp->nodes;
400           vp->zoom_toward[0] = (tn ? tn->x : 0.5);
401           vp->zoom_toward[1] = (tn ? tn->y : 0.5);
402
403           vp->mode = MODE_ZOOMING;
404           vp->zooming = 1;
405
406           vp->last_time = now;
407         }
408       break;
409
410     case MODE_ADDING:
411       if (vp->last_time + point_delay <= now)
412         {
413           add_node (vp, 
414                     BELLRAND(0.5) + 0.25, 
415                     BELLRAND(0.5) + 0.25);
416           vp->last_time = now;
417           vp->adding--;
418           if (vp->adding <= 0)
419             {
420               vp->adding = 0;
421               vp->mode = MODE_WAITING;
422               vp->last_time = now;
423             }
424         }
425       break;
426
427     case MODE_ZOOMING:
428       {
429         zoom_points (vp);
430         if (vp->zooming <= 0)
431           {
432             vp->mode = MODE_ADDING;
433             vp->adding = npoints;
434             vp->last_time = now;
435           }
436       }
437       break;
438
439     default:
440       abort();
441     }
442 }
443
444
445 ENTRYPOINT void 
446 init_voronoi (ModeInfo *mi)
447 {
448   voronoi_configuration *vp;
449
450   if (!vps) {
451     vps = (voronoi_configuration *)
452       calloc (MI_NUM_SCREENS(mi), sizeof (voronoi_configuration));
453     if (!vps) {
454       fprintf(stderr, "%s: out of memory\n", progname);
455       exit(1);
456     }
457
458     vp = &vps[MI_SCREEN(mi)];
459   }
460
461   vp = &vps[MI_SCREEN(mi)];
462
463   vp->glx_context = init_GL(mi);
464
465   if (point_size < 0) point_size = 10;
466
467   vp->ncolors = 64;
468   vp->colors = (XColor *) calloc (vp->ncolors, sizeof(XColor));
469 #if 0
470   make_random_colormap (0, 0, 0,
471                         vp->colors, &vp->ncolors,
472                         True, False, 0, False);
473 #else
474   make_smooth_colormap (0, 0, 0,
475                         vp->colors, &vp->ncolors,
476                         False, False, False);
477 #endif
478
479   reshape_voronoi (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
480
481   vp->mode = MODE_ADDING;
482   vp->adding = npoints * 2;
483   vp->last_time = 0;
484 }
485
486
487 ENTRYPOINT void
488 draw_voronoi (ModeInfo *mi)
489 {
490   voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
491   Display *dpy = MI_DISPLAY(mi);
492   Window window = MI_WINDOW(mi);
493
494   if (!vp->glx_context)
495     return;
496
497   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(vp->glx_context));
498
499   glShadeModel(GL_SMOOTH);
500   glEnable(GL_POINT_SMOOTH);
501   glEnable(GL_LINE_SMOOTH);
502   glEnable(GL_POLYGON_SMOOTH);
503
504   glEnable (GL_DEPTH_TEST);
505   glDepthFunc (GL_LEQUAL);
506
507   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
508
509   mi->polygon_count = 0;
510   draw_cells (mi);
511   move_points (vp);
512   prune_points (vp);
513   state_change (mi);
514
515   if (mi->fps_p) do_fps (mi);
516   glFinish();
517
518   glXSwapBuffers(dpy, window);
519 }
520
521 XSCREENSAVER_MODULE ("Voronoi", voronoi)
522
523 #endif /* USE_GL */