75880b5b7da4e36a1b7b33ce16f97d7db3036273
[xscreensaver] / hacks / starfish.c
1 /* xscreensaver, Copyright (c) 1997 Jamie Zawinski <jwz@netscape.com>
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 #include <math.h>
13 #include <sys/time.h> /* for gettimeofday() */
14 #include "screenhack.h"
15 #include "spline.h"
16
17
18 static Colormap cmap;
19 static Bool cycle_p;
20 static XColor *colors;
21 static int ncolors;
22 static int fg_index;
23 static GC gc;
24
25 #define SCALE        1000       /* fixed-point math, for sub-pixel motion */
26
27
28 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
29 #define RANDSIGN() ((random() & 1) ? 1 : -1)
30
31 enum starfish_mode {
32   pulse,
33   zoom
34 };
35   
36
37 struct starfish {
38   enum starfish_mode mode;
39   Bool blob_p;
40   int skip;
41   long x, y;            /* position of midpoint */
42   double th;            /* angle of rotation */
43   double rotv;          /* rotational velocity */
44   double rota;          /* rotational acceleration */
45   long elasticity;      /* how fast it deforms: radial velocity */
46   double rot_max;
47   long min_r, max_r;    /* radius range */
48   int npoints;          /* control points */
49   long *r;              /* radii */
50   spline *spline;
51   XPoint *prev;
52   int n_prev;
53 };
54
55 static struct starfish *
56 make_starfish (int maxx, int maxy, int size)
57 {
58   struct starfish *s = (struct starfish *) calloc(1, sizeof(*s));
59   int i;
60   int mid;
61
62   s->blob_p = get_boolean_resource ("blob", "Blob");
63   s->elasticity = SCALE * get_float_resource ("thickness", "Thickness");
64
65   if (s->elasticity == 0)
66     /* bell curve from 0-15, avg 7.5 */
67     s->elasticity = RAND(5*SCALE) + RAND(5*SCALE) + RAND(5*SCALE);
68
69   s->rotv = get_float_resource ("rotation", "Rotation");
70   if (s->rotv == -1)
71     /* bell curve from 0-12 degrees, avg 6 */
72     s->rotv = frand(4) + frand(4) + frand(4);
73
74   s->rotv /= 360;  /* convert degrees to ratio */
75
76   if (s->blob_p)
77     {
78       s->elasticity *= 3;
79       s->rotv *= 3;
80     }
81
82   s->rot_max = s->rotv * 2;
83   s->rota = 0.0004 + frand(0.0002);
84
85
86   if (! (random() % 20))
87     size *= frand(0.35) + frand(0.35) + 0.3;
88
89   {
90     static char skips[] = { 2, 2, 2, 2,
91                             3, 3, 3,
92                             6, 6,
93                             12 };
94     s->skip = skips[random() % sizeof(skips)];
95   }
96
97   if (! (random() % (s->skip == 2 ? 3 : 12)))
98     s->mode = zoom;
99   else
100     s->mode = pulse;
101
102   maxx *= SCALE;
103   maxy *= SCALE;
104   size *= SCALE;
105
106   s->max_r = size;
107   s->min_r = 0;
108
109   if (s->min_r < (5*SCALE)) s->min_r = (5*SCALE);
110   mid = ((s->min_r + s->max_r) / 2);
111
112   s->x = maxx/2;
113   s->y = maxy/2;
114
115   s->th = frand(M_PI+M_PI) * RANDSIGN();
116
117   {
118     static char sizes[] = { 3, 3, 3, 3, 3,
119                             4, 4, 4, 4,
120                             5, 5, 5, 5, 5, 5,
121                             8, 8, 8,
122                             10,
123                             35 };
124     int nsizes = sizeof(sizes);
125     if (s->skip > 3)
126       nsizes -= 4;
127     s->npoints = s->skip * sizes[random() % nsizes];
128   }
129
130   s->spline = make_spline (s->npoints);
131   s->r = (long *) malloc (sizeof(*s->r) * s->npoints);
132
133   for (i = 0; i < s->npoints; i++)
134     s->r[i] = ((i % s->skip) == 0) ? 0 : size;
135
136   return s;
137 }
138
139
140 static void
141 free_starfish (struct starfish *s)
142 {
143   if (s->r) free (s->r);
144   if (s->prev) free (s->prev);
145   if (s->spline)
146     {
147       if (s->spline->control_x) free (s->spline->control_x);
148       if (s->spline->control_y) free (s->spline->control_y);
149       if (s->spline->points) free (s->spline->points);
150       free (s->spline);
151     }
152   free (s);
153 }
154
155
156 static void 
157 throb_starfish (struct starfish *s)
158 {
159   int i;
160   double frac = ((M_PI+M_PI) / s->npoints);
161
162   for (i = 0; i < s->npoints; i++)
163     {
164       long r = s->r[i];
165       long ra = (r > 0 ? r : -r);
166       double th = (s->th > 0 ? s->th : -s->th);
167       long x, y;
168       long elasticity = s->elasticity;
169
170       /* place control points evenly around perimiter, shifted by theta */
171       x = s->x + ra * cos (i * frac + th);
172       y = s->y + ra * sin (i * frac + th);
173
174       s->spline->control_x[i] = x / SCALE;
175       s->spline->control_y[i] = y / SCALE;
176
177       if (s->mode == zoom && ((i % s->skip) == 0))
178         continue;
179
180       /* Slow down near the end points: move fastest in the middle. */
181       {
182         double ratio = (double)ra / (double)(s->max_r - s->min_r);
183         if (ratio > 0.5) ratio = 1-ratio;       /* flip */
184         ratio *= 2;                             /* normalize */
185         ratio = (ratio * 0.9) + 0.1;            /* fudge */
186         elasticity *= ratio;
187       }
188
189
190       /* Increase/decrease radius by elasticity */
191       ra += (r >= 0 ? elasticity : -elasticity);
192       if ((i % s->skip) == 0)
193         ra += (elasticity / 2);
194
195       r = ra * (r >= 0 ? 1 : -1);
196
197       /* If we've reached the end (too long or too short) reverse direction. */
198       if ((ra > s->max_r && r >= 0) ||
199           (ra < s->min_r && r < 0))
200         r = -r;
201
202       s->r[i] = r;
203     }
204 }
205
206
207 static void
208 spin_starfish (struct starfish *s)
209 {
210   double th = s->th;
211   if (th < 0)
212     th = -(th + s->rotv);
213   else
214     th += s->rotv;
215
216   if (th > (M_PI+M_PI))
217     th -= (M_PI+M_PI);
218   else if (th < 0)
219     th += (M_PI+M_PI);
220
221   s->th = (s->th > 0 ? th : -th);
222
223   s->rotv += s->rota;
224
225   if (s->rotv > s->rot_max || 
226       s->rotv < -s->rot_max)
227     {
228       s->rota = -s->rota;
229     }
230   /* If it stops, start it going in the other direction. */
231   else if (s->rotv < 0)
232     {
233       if (random() & 1)
234         {
235           /* keep going in the same direction */
236           s->rotv = 0;
237           if (s->rota < 0)
238             s->rota = -s->rota;
239         }
240       else
241         {
242           /* reverse gears */
243           s->rotv = -s->rotv;
244           s->rota = -s->rota;
245           s->th = -s->th;
246         }
247     }
248
249
250   /* Alter direction of rotational acceleration randomly. */
251   if (! (random() % 120))
252     s->rota = -s->rota;
253
254   /* Change acceleration very occasionally. */
255   if (! (random() % 200))
256     {
257       if (random() & 1)
258         s->rota *= 1.2;
259       else
260         s->rota *= 0.8;
261     }
262 }
263
264
265 static void
266 draw_starfish (Display *dpy, Drawable drawable, GC gc, struct starfish *s,
267            Bool fill_p)
268 {
269   compute_closed_spline (s->spline);
270   if (s->prev)
271     {
272       XPoint *points = (XPoint *)
273         malloc (sizeof(XPoint) * (s->n_prev + s->spline->n_points + 2));
274       int i = s->spline->n_points;
275       int j = s->n_prev;
276       memcpy (points, s->spline->points, (i * sizeof(*points)));
277       memcpy (points+i, s->prev, (j * sizeof(*points)));
278
279       if (s->blob_p)
280         XClearWindow (dpy, drawable);
281       XFillPolygon (dpy, drawable, gc, points, i+j, Complex, CoordModeOrigin);
282       free (points);
283
284       free (s->prev);
285       s->prev = 0;
286     }
287
288   s->prev = (XPoint *) malloc (s->spline->n_points * sizeof(XPoint));
289   memcpy (s->prev, s->spline->points, s->spline->n_points * sizeof(XPoint));
290   s->n_prev = s->spline->n_points;
291
292 #ifdef DEBUG
293   if (s->blob_p)
294     {
295       int i;
296       for (i = 0; i < s->npoints; i++)
297         XDrawLine (dpy, drawable, gc, s->x/SCALE, s->y/SCALE,
298                    s->spline->control_x[i], s->spline->control_y[i]);
299     }
300 #endif
301 }
302
303
304 static struct starfish *
305 make_window_starfish (Display *dpy, Window window)
306 {
307   XWindowAttributes xgwa;
308   int size;
309   Bool blob_p = get_boolean_resource ("blob", "Blob");
310   XGetWindowAttributes (dpy, window, &xgwa);
311   size = (xgwa.width < xgwa.height ? xgwa.width : xgwa.height);
312   if (blob_p) size /= 2;
313   else size *= 1.3;
314   return make_starfish (xgwa.width, xgwa.height, size);
315 }
316
317
318 static struct starfish *
319 init_starfish (Display *dpy, Window window)
320 {
321   static Bool first_time = True;
322   XGCValues gcv;
323   XWindowAttributes xgwa;
324   Bool blob_p = get_boolean_resource ("blob", "Blob");
325   XGetWindowAttributes (dpy, window, &xgwa);
326
327   cmap = xgwa.colormap;
328   cycle_p = get_boolean_resource ("cycle", "Cycle");
329
330   if (!first_time)
331     {
332       if (colors && ncolors)
333         free_colors (dpy, cmap, colors, ncolors);
334       if (colors)
335         free (colors);
336       colors = 0;
337     }
338
339   ncolors = get_integer_resource ("colors", "Colors");
340   if (ncolors < 2) ncolors = 2;
341   if (ncolors <= 2) mono_p = True;
342
343   if (mono_p)
344     colors = 0;
345   else
346     colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
347
348   if (mono_p || blob_p)
349     cycle_p = False;
350
351   if (mono_p)
352     ;
353   else if (random() % 3)
354     make_smooth_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
355                           True, &cycle_p, True);
356   else
357     make_uniform_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
358                            True, &cycle_p, True);
359
360   if (ncolors < 2) ncolors = 2;
361   if (ncolors <= 2) mono_p = True;
362
363   if (mono_p) cycle_p = False;
364
365   fg_index = 0;
366
367   if (!mono_p && !blob_p)
368     {
369       gcv.foreground = colors[fg_index].pixel;
370       XSetWindowBackground (dpy, window, gcv.foreground);
371     }
372
373   if (first_time)
374     {
375       XClearWindow (dpy, window);
376       first_time = False;
377     }
378
379   gcv.fill_rule = EvenOddRule;
380   gc = XCreateGC (dpy, window, GCForeground | GCFillRule, &gcv);
381
382   return make_window_starfish (dpy, window);
383 }
384
385
386
387 static void
388 run_starfish (Display *dpy, Window window, struct starfish *s)
389 {
390   throb_starfish (s);
391   spin_starfish (s);
392   draw_starfish (dpy, window, gc, s, False);
393
394   if (mono_p)
395     {
396       static Bool init = False;
397       static unsigned long black, white;
398       if (!init)
399         {
400           black = get_pixel_resource ("background", "Background", dpy, cmap);
401           white = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
402           init = True;
403           fg_index = white;
404           XSetForeground (dpy, gc, fg_index);
405         }
406       else if (!s->blob_p)
407         {
408           fg_index = (fg_index == black ? white : black);
409           XSetForeground (dpy, gc, fg_index);
410         }
411     }
412   else
413     {
414       fg_index = (fg_index + 1) % ncolors;
415       XSetForeground (dpy, gc, colors [fg_index].pixel);
416     }
417 }
418
419
420
421 \f
422 char *progclass = "Starfish";
423
424 char *defaults [] = {
425   "*background:         black",
426   "*foreground:         white",
427   "*delay:              10000",
428   "*cycleDelay:         100000",
429   "*thickness:          0",             /* pixels, 0 = random */
430   "*rotation:           -1",            /* degrees, -1 = "random" */
431   "*colors:             200",
432   "*cycle:              true",
433   "*duration:           30",
434   "*delay2:             5",
435   "*blob:               false",
436   0
437 };
438
439 XrmOptionDescRec options [] = {
440   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
441   { "-delay2",          ".delay2",      XrmoptionSepArg, 0 },
442   { "-cycle-delay",     ".cycleDelay",  XrmoptionSepArg, 0 },
443   { "-thickness",       ".thickness",   XrmoptionSepArg, 0 },
444   { "-rotation",        ".rotation",    XrmoptionSepArg, 0 },
445   { "-colors",          ".colors",      XrmoptionSepArg, 0 },
446   { "-cycle",           ".cycle",       XrmoptionNoArg, "True" },
447   { "-no-cycle",        ".cycle",       XrmoptionNoArg, "False" },
448   { "-duration",        ".duration",    XrmoptionSepArg, 0 },
449   { "-blob",            ".blob",        XrmoptionNoArg, "True" },
450   { "-no-blob",         ".blob",        XrmoptionNoArg, "False" },
451   { 0, 0, 0, 0 }
452 };
453
454 void
455 screenhack (Display *dpy, Window window)
456 {
457   struct starfish *s = init_starfish (dpy, window);
458   int delay = get_integer_resource ("delay", "Delay");
459   int delay2 = get_integer_resource ("delay2", "Delay") * 1000000;
460   int cycle_delay = get_integer_resource ("cycleDelay", "Delay");
461   int duration = get_seconds_resource ("duration", "Seconds");
462   Bool blob_p = get_boolean_resource ("blob", "Blob");
463   time_t start = time ((time_t) 0);
464   time_t now;
465   int direction = (random() % 1) ? 1 : -1;
466
467   if (blob_p)
468     delay *= 3;
469
470   while (1)
471     {
472       run_starfish (dpy, window, s);
473       XSync (dpy, True);
474
475       if (cycle_p && cycle_delay)
476         {
477           if (cycle_delay <= delay)
478             {
479               int i = 0;
480               while (i < delay)
481                 {
482                   rotate_colors (dpy, cmap, colors, ncolors, direction);
483                   usleep(cycle_delay);
484                   i += cycle_delay;
485                 }
486             }
487           else
488             {
489               static long tick = 0;
490               if (tick >= cycle_delay)
491                 {
492                   rotate_colors (dpy, cmap, colors, ncolors, direction);
493                   tick = 0;
494                 }
495               if (delay)
496                 usleep(delay);
497               tick += delay;
498             }
499
500           if (! (random() % 1000))
501             direction = -direction;
502         }
503       else if (delay)
504         usleep (delay);
505
506       if (duration > 0)
507         {
508           now = time ((time_t) 0);
509           if (start + duration < now)
510             {
511               start = now;
512
513               free_starfish (s);
514
515               if (delay2 && !blob_p && cycle_p)
516                 {
517                   int i = 0;
518                   while (i < delay2)
519                     {
520                       rotate_colors (dpy, cmap, colors, ncolors, direction);
521                       usleep(cycle_delay);
522                       i += cycle_delay;
523                     }
524                 }
525
526               /* Every now and then, pick new colors; otherwise, just build
527                  a new starfish with the current colors. */
528               if (! (random () % 10))
529                 s = init_starfish (dpy, window);
530               else
531                 s = make_window_starfish(dpy, window);
532             }
533         }
534     }
535 }