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