http://ftp.x.org/contrib/applications/xscreensaver-2.23.tar.gz
[xscreensaver] / hacks / rocks.c
1 /* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997
2  *  Jamie Zawinski <jwz@netscape.com>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /* 18-Sep-97: Johannes Keukelaar <johannes@nada.kth.se>: Added some color.
14  * Using -mono gives the old behaviour.  (Modified by jwz.)
15  */
16 /*   Flying through an asteroid field.  Based on TI Explorer Lisp code by 
17    John Nguyen <johnn@hx.lcs.mit.edu>
18  */
19
20 #include <stdio.h>
21 #include <math.h>
22 #include "screenhack.h"
23
24 #define MIN_ROCKS 1
25 #define MIN_DEPTH 2             /* rocks disappear when they get this close */
26 #define MAX_DEPTH 60            /* this is where rocks appear */
27 #define MIN_SIZE 3              /* how small where pixmaps are not used */
28 #define MAX_SIZE 200            /* how big (in pixels) rocks are at depth 1 */
29 #define DEPTH_SCALE 100         /* how many ticks there are between depths */
30 #define SIN_RESOLUTION 1000
31
32 #define MAX_DEP 0.3             /* how far the displacement can be (percent) */
33 #define DIRECTION_CHANGE_RATE 60
34 #define MAX_DEP_SPEED 5         /* Maximum speed for movement */
35 #define MOVE_STYLE 0            /* Only 0 and 1. Distinguishes the fact that
36                                    these are the rocks that are moving (1) 
37                                    or the rocks source (0). */
38
39 /* there's not much point in the above being user-customizable, but those
40    numbers might want to be tweaked for displays with an order of magnitude
41    higher resolution or compute power.
42  */
43
44 static double sins [SIN_RESOLUTION];
45 static double coss [SIN_RESOLUTION];
46 static double depths [(MAX_DEPTH + 1) * DEPTH_SCALE];
47
48 static Display *dpy;
49 static Window window;
50 static int width, height, midx, midy;
51 static int dep_x, dep_y;
52 static int ncolors;
53 static XColor *colors;
54 static float max_dep;
55 static GC erase_gc;
56 static GC *draw_gcs;
57 static Bool rotate_p;
58 static Bool move_p;
59 static int speed;
60 static Bool threed;
61 static GC threed_left_gc, threed_right_gc;
62 static double threed_delta;
63
64 #define GETZDIFF(z) \
65         (threed_delta * 40.0 * \
66          (1.0 - ((MAX_DEPTH * DEPTH_SCALE / 2) / \
67                  ((z) + 20.0 * DEPTH_SCALE))))
68
69 struct rock {
70   int real_size;
71   int r;
72   int theta;
73   int depth;
74   int size, x, y;
75   int diff;
76   int color;
77 };
78
79 static struct rock *rocks;
80 static int nrocks;
81 static Pixmap pixmaps [MAX_SIZE];
82 static int delay;
83
84 static void rock_compute (struct rock *);
85 static void rock_draw (struct rock *, Bool draw_p);
86
87 static void
88 rock_reset (struct rock *rock)
89 {
90   rock->real_size = MAX_SIZE;
91   rock->r = (SIN_RESOLUTION * 0.7) + (random () % (30 * SIN_RESOLUTION));
92   rock->theta = random () % SIN_RESOLUTION;
93   rock->depth = MAX_DEPTH * DEPTH_SCALE;
94   rock->color = random() % ncolors;
95   rock_compute (rock);
96   rock_draw (rock, True);
97 }
98
99 static void
100 rock_tick (struct rock *rock, int d)
101 {
102   if (rock->depth > 0)
103     {
104       rock_draw (rock, False);
105       rock->depth -= speed;
106       if (rotate_p)
107         {
108           rock->theta = (rock->theta + d) % SIN_RESOLUTION;
109         }
110       while (rock->theta < 0)
111         rock->theta += SIN_RESOLUTION;
112       if (rock->depth < (MIN_DEPTH * DEPTH_SCALE))
113         rock->depth = 0;
114       else
115         {
116           rock_compute (rock);
117           rock_draw (rock, True);
118         }
119     }
120   else if ((random () % 40) == 0)
121     rock_reset (rock);
122 }
123
124 static void
125 rock_compute (struct rock *rock)
126 {
127   double factor = depths [rock->depth];
128   double rsize = rock->real_size * factor;
129
130   rock->size = (int) (rsize + 0.5);
131   rock->diff = (int) GETZDIFF(rock->depth);
132   rock->x = midx + (coss [rock->theta] * rock->r * factor);
133   rock->y = midy + (sins [rock->theta] * rock->r * factor);
134
135   if (move_p)
136     {
137       double move_factor = (((double) MOVE_STYLE) -
138                             (((double) rock->depth) /
139                              (((double) (MAX_DEPTH + 1)) *
140                               ((double) DEPTH_SCALE))));
141       /* move_factor is 0 when the rock is close, 1 when far */
142       rock->x += (((double) dep_x) * move_factor);
143       rock->y += (((double) dep_y) * move_factor);
144     }
145 }
146
147 static void
148 rock_draw (rock, draw_p)
149      struct rock *rock;
150      Bool draw_p;
151 {
152   GC gc = (draw_p 
153            ? (threed ? erase_gc : draw_gcs[rock->color])
154            : erase_gc);
155
156   if (rock->x <= 0 || rock->y <= 0 || rock->x >= width || rock->y >= height)
157     {
158       /* this means that if a rock were to go off the screen at 12:00, but
159          would have been visible at 3:00, it won't come back once the observer
160          rotates around so that the rock would have been visible again.
161          Oh well.
162        */
163       if (!move_p)
164         rock->depth = 0;
165       return;
166     }
167   if (rock->size <= 1)
168     {
169       if (threed)
170         {
171           if (draw_p) gc = threed_left_gc;
172           XDrawPoint (dpy, window, gc, rock->x - rock->diff, rock->y);
173           if (draw_p) gc = threed_right_gc;
174           XDrawPoint (dpy, window, gc, rock->x + rock->diff, rock->y);
175         }
176       else
177         {
178           XDrawPoint (dpy, window, gc, rock->x, rock->y);
179         }
180     }
181   else if (rock->size <= MIN_SIZE || !draw_p)
182     {
183       if (threed)
184         {
185           if (draw_p) gc = threed_left_gc;
186           XFillRectangle(dpy, window, gc,
187                          rock->x - rock->size / 2 - rock->diff,
188                          rock->y - rock->size / 2,
189                          rock->size, rock->size);
190           if (draw_p) gc = threed_right_gc;
191           XFillRectangle(dpy, window, gc,
192                          rock->x - rock->size / 2 + rock->diff,
193                          rock->y - rock->size / 2,
194                          rock->size, rock->size);
195         }
196       else
197         {
198           XFillRectangle (dpy, window, gc,
199                           rock->x - rock->size/2, rock->y - rock->size/2,
200                           rock->size, rock->size);
201         }
202     }
203   else if (rock->size < MAX_SIZE)
204     {
205       if (threed)
206         {
207           gc = threed_left_gc;
208           XCopyPlane(dpy, pixmaps[rock->size], window, gc,
209                      0, 0, rock->size, rock->size,
210                      rock->x - rock->size / 2 - rock->diff,
211                      rock->y - rock->size / 2, 1L);
212           gc = threed_right_gc;
213           XCopyPlane(dpy, pixmaps[rock->size], window, gc,
214                      0, 0, rock->size, rock->size,
215                      rock->x - rock->size / 2 + rock->diff,
216                      rock->y - rock->size / 2, 1L);
217         }
218       else
219         {
220           XCopyPlane (dpy, pixmaps [rock->size], window, gc,
221                       0, 0, rock->size, rock->size,
222                       rock->x - rock->size/2, rock->y - rock->size/2,
223                       1L);
224         }
225     }
226 }
227
228
229 static void
230 init_pixmaps (Display *dpy, Window window)
231 {
232   int i;
233   XGCValues gcv;
234   GC fg_gc = 0, bg_gc = 0;
235   pixmaps [0] = pixmaps [1] = 0;
236   for (i = MIN_DEPTH; i < MAX_SIZE; i++)
237     {
238       int w = (1+(i/32))<<5; /* server might be faster if word-aligned */
239       int h = i;
240       Pixmap p = XCreatePixmap (dpy, window, w, h, 1);
241       XPoint points [7];
242       pixmaps [i] = p;
243       if (! p)
244         {
245           fprintf (stderr, "%s: couldn't allocate pixmaps", progname);
246           exit (1);
247         }
248       if (! fg_gc)
249         {       /* must use drawable of pixmap, not window (fmh) */
250           gcv.foreground = 1;
251           fg_gc = XCreateGC (dpy, p, GCForeground, &gcv);
252           gcv.foreground = 0;
253           bg_gc = XCreateGC (dpy, p, GCForeground, &gcv);
254         }
255       XFillRectangle (dpy, p, bg_gc, 0, 0, w, h);
256       points [0].x = i * 0.15; points [0].y = i * 0.85;
257       points [1].x = i * 0.00; points [1].y = i * 0.20;
258       points [2].x = i * 0.30; points [2].y = i * 0.00;
259       points [3].x = i * 0.40; points [3].y = i * 0.10;
260       points [4].x = i * 0.90; points [4].y = i * 0.10;
261       points [5].x = i * 1.00; points [5].y = i * 0.55;
262       points [6].x = i * 0.45; points [6].y = i * 1.00;
263       XFillPolygon (dpy, p, fg_gc, points, 7, Nonconvex, CoordModeOrigin);
264     }
265   XFreeGC (dpy, fg_gc);
266   XFreeGC (dpy, bg_gc);
267 }
268
269
270 static int
271 compute_move(int axe)                           /* 0 for x, 1 for y */
272 {
273   static int current_dep[2] = {0, 0};
274   static int speed[2] = {0, 0};
275   static short direction[2] = {0, 0};
276   static int limit[2] = {0, 0};
277   int change = 0;
278
279   limit[0] = midx;
280   limit[1] = midy;
281
282   current_dep[axe] += speed[axe];       /* We adjust the displacement */
283
284   if (current_dep[axe] > (int) (limit[axe] * max_dep))
285     {
286       if (current_dep[axe] > limit[axe])
287         current_dep[axe] = limit[axe];
288       direction[axe] = -1;
289     }                   /* This is when we reach the upper screen limit */
290   if (current_dep[axe] < (int) (-limit[axe] * max_dep))
291     {
292       if (current_dep[axe] < -limit[axe])
293         current_dep[axe] = -limit[axe];
294       direction[axe] = 1;
295     }                   /* This is when we reach the lower screen limit */
296   if (direction[axe] == 1)      /* We adjust the speed */
297     speed[axe] += 1;
298   else if (direction[axe] == -1)
299     speed[axe] -= 1;
300
301   if (speed[axe] > MAX_DEP_SPEED)
302     speed[axe] = MAX_DEP_SPEED;
303   else if (speed[axe] < -MAX_DEP_SPEED)
304     speed[axe] = -MAX_DEP_SPEED;
305
306   if (move_p && !(random() % DIRECTION_CHANGE_RATE))
307     {
308       /* We change direction */
309       change = random() & 1;
310       if (change != 1)
311         {
312           if (direction[axe] == 0)
313             direction[axe] = change - 1;        /* 0 becomes either 1 or -1 */
314           else
315             direction[axe] = 0;                 /* -1 or 1 become 0 */
316         }
317     }
318   return (current_dep[axe]);
319 }
320
321 static void
322 tick_rocks (int d)
323 {
324   int i;
325
326   if (move_p)
327     {
328       dep_x = compute_move(0);
329       dep_y = compute_move(1);
330     }
331
332   for (i = 0; i < nrocks; i++)
333     rock_tick (&rocks [i], d);
334 }
335
336
337 static void
338 rocks_once (void)
339 {
340   static int current_delta = 0; /* observer Z rotation */
341   static int window_tick = 50;
342         static int  new_delta = 0;
343         static int  dchange_tick = 0;
344
345   if (window_tick++ == 50)
346     {
347       XWindowAttributes xgwa;
348       XGetWindowAttributes (dpy, window, &xgwa);
349       window_tick = 0;
350       width = xgwa.width;
351       height = xgwa.height;
352       midx = width/2;
353       midy = height/2;
354     }
355
356   if (current_delta != new_delta)
357     {
358       if (dchange_tick++ == 5)
359         {
360           dchange_tick = 0;
361           if (current_delta < new_delta)
362             current_delta++;
363           else
364             current_delta--;
365         }
366     }
367   else
368     {
369       if (! (random() % 50))
370         {
371           new_delta = ((random() % 11) - 5);
372           if (! (random() % 10))
373             new_delta *= 5;
374         }
375     }
376   tick_rocks (current_delta);
377 }
378
379 static void
380 init_rocks (Display *d, Window w)
381 {
382   int i;
383   XGCValues gcv;
384   Colormap cmap;
385   XWindowAttributes xgwa;
386   unsigned int bg;
387   dpy = d;
388   window = w;
389   XGetWindowAttributes (dpy, window, &xgwa);
390   cmap = xgwa.colormap;
391   delay = get_integer_resource ("delay", "Integer");
392   if (delay < 0) delay = 0;
393   speed = get_integer_resource ("speed", "Integer");
394   if (speed < 1) speed = 1;
395   if (speed > 100) speed = 100;
396   rotate_p = get_boolean_resource ("rotate", "Boolean");
397   move_p = get_boolean_resource ("move", "Boolean");
398   if (mono_p)
399     ncolors = 2;
400   else
401     ncolors = get_integer_resource ("colors", "Colors");
402
403   if (ncolors < 2)
404     {
405       ncolors = 2;
406       mono_p = True;
407   }
408
409   colors = (XColor *) malloc(ncolors * sizeof(*colors));
410   draw_gcs = (GC *) malloc(ncolors * sizeof(*draw_gcs));
411
412   bg = get_pixel_resource ("background", "Background", dpy, cmap);
413   colors[0].pixel = bg;
414   colors[0].flags = DoRed|DoGreen|DoBlue;
415   XQueryColor(dpy, cmap, &colors[0]);
416
417   ncolors--;
418   make_random_colormap(dpy, xgwa.visual, cmap, colors+1, &ncolors, True,
419                        True, 0, True);
420   ncolors++;
421
422   if (ncolors < 2)
423     {
424       ncolors = 2;
425       mono_p = True;
426   }
427
428   if (mono_p)
429     {
430       unsigned int fg = get_pixel_resource("foreground", "Foreground",
431                                            dpy, cmap);
432       colors[1].pixel = fg;
433       colors[1].flags = DoRed|DoGreen|DoBlue;
434       XQueryColor(dpy, cmap, &colors[1]);
435       gcv.foreground = fg;
436       gcv.background = bg;
437       draw_gcs[0] = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
438       draw_gcs[1] = draw_gcs[0];
439     }
440   else
441     for( i = 0; i < ncolors; i++ )
442       {
443         gcv.foreground = colors[i].pixel;
444         gcv.background = bg;
445         draw_gcs[i] = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
446       }
447
448   gcv.foreground = bg;
449   erase_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
450
451   max_dep = (move_p ? MAX_DEP : 0);
452
453   for (i = 0; i < SIN_RESOLUTION; i++)
454     {
455       sins [i] = sin ((((double) i) / (SIN_RESOLUTION / 2)) * M_PI);
456       coss [i] = cos ((((double) i) / (SIN_RESOLUTION / 2)) * M_PI);
457     }
458   /* we actually only need i/speed of these, but wtf */
459   for (i = 1; i < (sizeof (depths) / sizeof (depths[0])); i++)
460     depths [i] = atan (((double) 0.5) / (((double) i) / DEPTH_SCALE));
461   depths [0] = M_PI/2; /* avoid division by 0 */
462
463   threed = get_boolean_resource("use3d", "Boolean");
464   if (threed)
465     {
466       gcv.background = bg;
467       gcv.foreground = get_pixel_resource ("left3d", "Foreground", dpy, cmap);
468       threed_left_gc = XCreateGC (dpy, window, GCForeground|GCBackground,&gcv);
469       gcv.foreground = get_pixel_resource ("right3d", "Foreground", dpy, cmap);
470       threed_right_gc = XCreateGC (dpy, window,GCForeground|GCBackground,&gcv);
471       threed_delta = get_float_resource("delta3d", "Integer");
472     }
473
474   /* don't want any exposure events from XCopyPlane */
475   for( i = 0; i < ncolors; i++)
476     XSetGraphicsExposures (dpy, draw_gcs[i], False);
477   XSetGraphicsExposures (dpy, erase_gc, False);
478
479   nrocks = get_integer_resource ("count", "Count");
480   if (nrocks < 1) nrocks = 1;
481   rocks = (struct rock *) calloc (nrocks, sizeof (struct rock));
482   init_pixmaps (dpy, window);
483   XClearWindow (dpy, window);
484 }
485
486
487 \f
488 char *progclass = "Rocks";
489
490 char *defaults [] = {
491   ".background: Black",
492   ".foreground: #E9967A",
493   "*colors:     5",
494   "*count:      100",
495   "*delay:      50000",
496   "*speed:      100",
497   "*rotate:     true",
498   "*move:       true",
499   "*use3d:      False",
500   "*left3d:     Blue",
501   "*right3d:    Red",
502   "*delta3d:    1.5",
503   0
504 };
505
506 XrmOptionDescRec options [] = {
507   { "-count",           ".count",       XrmoptionSepArg, 0 },
508   { "-rotate",          ".rotate",      XrmoptionNoArg,  "true" },
509   { "-norotate",        ".rotate",      XrmoptionNoArg,  "false" },
510   { "-move",            ".move",        XrmoptionNoArg,  "true" },
511   { "-nomove",          ".move",        XrmoptionNoArg,  "false" },
512   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
513   { "-speed",           ".speed",       XrmoptionSepArg, 0 },
514   {"-3d",               ".use3d",       XrmoptionNoArg, "True"},
515   {"-no-3d",            ".use3d",       XrmoptionNoArg, "False"},
516   {"-left3d",           ".left3d",      XrmoptionSepArg, 0 },
517   {"-right3d",          ".right3d",     XrmoptionSepArg, 0 },
518   {"-delta3d",          ".delta3d",     XrmoptionSepArg, 0 },
519   { "-colors",          ".colors",      XrmoptionSepArg, 0 },
520   { 0, 0, 0, 0 }
521 };
522
523 void
524 screenhack (Display *dpy, Window window)
525 {
526   init_rocks (dpy, window);
527   while (1)
528     {
529       rocks_once ();
530       XSync (dpy, True);
531       if (delay) usleep (delay);
532     }
533 }