From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / hacks / hexadrop.c
1 /* xscreensaver, Copyright (c) 1999-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  * Draws a grid of hexagons or other shapes and drops them out.
12  * Created 8-Jul-2013.
13  */
14
15 #include <math.h>
16 #include "screenhack.h"
17
18 #define countof(x) (sizeof(x)/sizeof(*(x)))
19 #define ABS(x) ((x)<0?-(x):(x))
20
21 /* Avoid rounding errors by using a larger fixed-point grid.
22    Without this, we got little pointy errors at some corners. */
23 #define SCALE 10
24
25 typedef struct {
26   int sides;
27   int cx, cy;
28   double th, radius, i, speed;
29   int colors[2];
30   Bool initted_p;
31 } cell;
32
33 typedef struct {
34   Display *dpy;
35   Window window;
36   XWindowAttributes xgwa;
37
38   int ncells, cells_size, gw, gh;
39   cell *cells;
40
41   int delay;
42   double speed;
43   int sides;
44   Bool lockstep_p;
45   Bool uniform_p;
46   Bool initted_p;
47
48   int ncolors;
49   XColor *colors;
50   GC gc;
51
52 } state;
53
54
55 static void
56 make_cells (state *st)
57 {
58   int grid_size = get_integer_resource (st->dpy, "size", "Size");
59   cell *cells2;
60   int size, r, gw, gh, x, y, i;
61   double th = 0;
62
63   if (grid_size < 5) grid_size = 5;
64
65   size = ((st->xgwa.width > st->xgwa.height
66            ? st->xgwa.width : st->xgwa.height)
67           / grid_size);
68   gw = st->xgwa.width  / size;
69   gh = st->xgwa.height / size;
70
71   switch (st->sides) {
72   case 8:
73     r  = size * 0.75;
74     th = M_PI / st->sides;
75     gw *= 1.25;
76     gh *= 1.25;
77     break;
78   case 6:
79     r  = size / sqrt(3);
80     th = M_PI / st->sides;
81     gh *= 1.2;
82     break;
83   case 3:
84     size *= 2;
85     r  = size / sqrt(3);
86     th = M_PI / st->sides / 2;
87     break;
88   case 4:
89     size /= 2;
90     r  = size * sqrt (2);
91     th = M_PI / st->sides;
92     break;
93   default:
94     abort();
95     break;
96   }
97
98   gw += 3;      /* leave a few extra columns off screen just in case */
99   gh += 3;
100
101   st->ncells = gw * gh;
102
103   if (st->initted_p && !st->cells) abort();
104   if (!st->initted_p && st->cells) abort();
105
106   cells2 = (cell *) calloc (st->ncells, sizeof(*cells2));
107   if (! cells2) abort();
108
109   if (st->cells)
110     {
111       for (y = 0; y < (st->gh < gh ? st->gh : gh); y++)
112         for (x = 0; x < (st->gw < gw ? st->gw : gw); x++)
113           cells2[y * gw + x] = st->cells [y * st->gw + x];
114       free (st->cells);
115       st->cells = 0;
116     }
117
118   st->cells = cells2;
119   st->gw = gw;
120   st->gh = gh;
121
122   i = 0;
123   for (y = 0; y < gh; y++)
124     for (x = 0; x < gw; x++)
125       {
126         cell *c = &st->cells[i];
127         c->sides = st->sides;
128         c->radius = SCALE * r;
129         c->th = th;
130
131         switch (st->sides) {
132         case 8:
133           if (x & 1)
134             {
135               c->cx = SCALE * x * size;
136               c->radius /= 2;
137               c->th = M_PI / 4;
138               c->sides = 4;
139               c->radius *= 1.1;
140             }
141           else
142             {
143               c->cx = SCALE * x * size;
144               c->radius *= 1.02;
145               c->radius--;
146             }
147
148           if (y & 1)
149             c->cx -= SCALE * size;
150
151           c->cy = SCALE * y * size;
152
153          break;
154         case 6:
155           c->cx = SCALE * x * size;
156           c->cy = SCALE * y * size * sqrt(3)/2;
157           if (y & 1)
158             c->cx -= SCALE * size * 0.5;
159           break;
160         case 4:
161           c->cx = SCALE * x * size * 2;
162           c->cy = SCALE * y * size * 2;
163           break;
164         case 3:
165           c->cx = SCALE * x * size * 0.5;
166           c->cy = SCALE * y * size * sqrt(3)/2;
167           if ((x & 1) ^ (y & 1))
168             {
169               c->th = th + M_PI;
170               c->cy -= SCALE * r * 0.5;
171             }
172           break;
173         default:
174           abort();
175         }
176
177         if (! c->initted_p)
178           {
179             c->speed = st->speed * (st->uniform_p ? 1 : (0.1 + frand(0.9)));
180             c->i = st->lockstep_p ? 0 : random() % r;
181             c->colors[0] = (st->lockstep_p ? 0 : random() % st->ncolors);
182             c->colors[1] = 0;
183             c->initted_p = True;
184           }
185
186         c->radius += SCALE;  /* Avoid single-pixel erase rounding errors */
187
188         if (c->i > c->radius) c->i = c->radius;
189         if (c->colors[0] >= st->ncolors) c->colors[0] = st->ncolors-1;
190         if (c->colors[1] >= st->ncolors) c->colors[1] = st->ncolors-1;
191
192         i++;
193       }
194
195   st->initted_p = True;
196 }
197
198
199 static void
200 draw_cell (state *st, cell *c)
201 {
202   XPoint points[20];
203   int i, j;
204   for (j = 0; j <= 1; j++)
205     {
206       int r = (j == 0 ? c->radius : c->i);
207       for (i = 0; i < c->sides; i++)
208         {
209           double th = i * M_PI * 2 / c->sides;
210           points[i].x = (c->cx + r * cos (th + c->th) + 0.5) / SCALE;
211           points[i].y = (c->cy + r * sin (th + c->th) + 0.5) / SCALE;
212         }
213       XSetForeground (st->dpy, st->gc, st->colors[c->colors[j]].pixel);
214       XFillPolygon (st->dpy, st->window, st->gc, points, c->sides,
215                     Convex, CoordModeOrigin);
216     }
217
218   c->i -= SCALE * c->speed;
219   if (c->i < 0)
220     {
221       c->i = c->radius;
222       c->colors[1] = c->colors[0];
223       if (c != &st->cells[0])
224         c->colors[0] = st->cells[0].colors[0];
225       else
226         c->colors[0] = random() % st->ncolors;
227     }
228 }
229
230
231 static void
232 hexadrop_init_1 (Display *dpy, Window window, state *st)
233 {
234   XGCValues gcv;
235   char *s1, *s2;
236
237   st->dpy = dpy;
238   st->window = window;
239   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
240   st->ncolors = get_integer_resource (st->dpy, "ncolors", "Integer");
241   st->speed = get_float_resource (st->dpy, "speed", "Speed");
242   if (st->speed < 0) st->speed = 0;
243
244   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
245
246   if (st->ncolors < 2) st->ncolors = 2;
247
248   st->colors = (XColor *) calloc (sizeof(*st->colors), st->ncolors);
249
250   if (st->ncolors < 10)
251     make_random_colormap (st->xgwa.screen, st->xgwa.visual, st->xgwa.colormap,
252                           st->colors, &st->ncolors, False, True, 0, True);
253   else
254     make_smooth_colormap (st->xgwa.screen, st->xgwa.visual, st->xgwa.colormap,
255                           st->colors, &st->ncolors, True, 0, True);
256   XSetWindowBackground (dpy, window, st->colors[0].pixel);
257
258   s1 = get_string_resource (st->dpy, "uniform", "Uniform");
259   s2 = get_string_resource (st->dpy, "lockstep", "Lockstep");
260
261   if ((!s1 || !*s1 || !strcasecmp(s1, "maybe")) &&
262       (!s2 || !*s2 || !strcasecmp(s2, "maybe")))
263     {
264       /* When being random, don't do both. */
265       st->uniform_p = random() & 1;
266       st->lockstep_p = st->uniform_p ? 0 : random() & 1;
267     }
268   else 
269     {
270       if (!s1 || !*s1 || !strcasecmp(s1, "maybe"))
271         st->uniform_p = random() & 1;
272       else
273         st->uniform_p = get_boolean_resource (st->dpy, "uniform", "Uniform");
274
275       if (!s2 || !*s2 || !strcasecmp(s2, "maybe"))
276         st->lockstep_p = random() & 1;
277       else
278         st->lockstep_p = get_boolean_resource (st->dpy, "lockstep","Lockstep");
279     }
280
281
282   st->sides = get_integer_resource (st->dpy, "sides", "Sides");
283   if (! (st->sides == 0 || st->sides == 3 || st->sides == 4 || 
284          st->sides == 6 || st->sides == 8))
285     {
286       printf ("%s: invalid number of sides: %d\n", progname, st->sides);
287       st->sides = 0;
288     }
289
290   if (! st->sides)
291     {
292       static int defs[] = { 3, 3, 3,
293                             4,
294                             6, 6, 6, 6,
295                             8, 8, 8 };
296       st->sides = defs[random() % countof(defs)];
297     }
298
299   make_cells (st);
300   gcv.foreground = st->colors[0].pixel;
301   st->gc = XCreateGC (dpy, window, GCForeground, &gcv);
302 }
303
304
305 static void *
306 hexadrop_init (Display *dpy, Window window)
307 {
308   state *st = (state *) calloc (1, sizeof(*st));
309   hexadrop_init_1 (dpy, window, st);
310   return st;
311 }
312
313
314
315 static unsigned long
316 hexadrop_draw (Display *dpy, Window window, void *closure)
317 {
318   state *st = (state *) closure;
319   int i;
320
321   for (i = 0; i < st->ncells; i++)
322     draw_cell (st, &st->cells[i]);
323
324   return st->delay;
325 }
326
327
328 static void
329 hexadrop_reshape (Display *dpy, Window window, void *closure, 
330                  unsigned int w, unsigned int h)
331 {
332   state *st = (state *) closure;
333   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
334   make_cells (st);
335 }
336
337
338 static void
339 hexadrop_free (Display *dpy, Window window, void *closure)
340 {
341   state *st = (state *) closure;
342   if (st->colors)
343     {
344       free_colors (st->xgwa.screen, st->xgwa.colormap, st->colors, st->ncolors);
345       free (st->colors);
346       st->colors = 0;
347     }
348   if (st->cells) 
349     {
350       free (st->cells);
351       st->cells = 0;
352     }
353   if (st->gc)
354     {
355       XFreeGC (st->dpy, st->gc);
356       st->gc = 0;
357     }
358 }
359
360
361 static Bool
362 hexadrop_event (Display *dpy, Window window, void *closure, XEvent *event)
363 {
364   state *st = (state *) closure;
365
366   if (screenhack_event_helper (dpy, window, event))
367     {
368       cell *c = st->cells;
369       int i;
370       st->cells = 0;
371       hexadrop_free (st->dpy, st->window, st);
372       free (st->cells);
373       st->cells = c;
374       for (i = 0; i < st->ncells; i++)
375         st->cells[i].initted_p = False;
376       hexadrop_init_1 (st->dpy, st->window, st);
377       return True;
378     }
379
380   return False;
381 }
382
383
384 static const char *hexadrop_defaults [] = {
385   ".background:         black",
386   ".foreground:         white",
387   "*fpsSolid:           true",
388   "*delay:              30000",
389   "*sides:              0",
390   "*size:               15",
391   "*speed:              1.0",
392   "*ncolors:            128",
393   "*uniform:            Maybe",
394   "*lockstep:           Maybe",
395 #ifdef HAVE_MOBILE
396   "*ignoreRotation:     True",
397 #endif
398   0
399 };
400
401 static XrmOptionDescRec hexadrop_options [] = {
402   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
403   { "-sides",           ".sides",       XrmoptionSepArg, 0 },
404   { "-size",            ".size",        XrmoptionSepArg, 0 },
405   { "-speed",           ".speed",       XrmoptionSepArg, 0 },
406   { "-ncolors",         ".ncolors",     XrmoptionSepArg, 0 },
407   { "-uniform-speed",   ".uniform",     XrmoptionNoArg, "True"  },
408   { "-no-uniform-speed",".uniform",     XrmoptionNoArg, "False" },
409   { "-lockstep",        ".lockstep",    XrmoptionNoArg, "True"  },
410   { "-no-lockstep",     ".lockstep",    XrmoptionNoArg, "False" },
411   { 0, 0, 0, 0 }
412 };
413
414 XSCREENSAVER_MODULE ("Hexadrop", hexadrop)