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