6060798181928ddc2482e2a95a5e8a8fa95ea2a2
[xscreensaver] / hacks / interference.c
1 /* interference.c --- colored fields via decaying sinusoidal waves.
2  * An entry for the RHAD Labs Screensaver Contest.
3  *
4  * Author: Hannu Mallat <hmallat@cs.hut.fi>
5  *
6  * Copyright (C) 1998 Hannu Mallat.
7  *
8  * Permission to use, copy, modify, distribute, and sell this software and its
9  * documentation for any purpose is hereby granted without fee, provided that
10  * the above copyright notice appear in all copies and that both that
11  * copyright notice and this permission notice appear in supporting
12  * documentation.  No representations are made about the suitability of this
13  * software for any purpose.  It is provided "as is" without express or 
14  * implied warranty.
15  *
16  * decaying sinusoidal waves, which extend spherically from their
17  * respective origins, move around the plane. a sort of interference
18  * between them is calculated and the resulting twodimensional wave
19  * height map is plotted in a grid, using softly changing colours.
20  *
21  * not physically (or in any sense) accurate, but fun to look at for 
22  * a while. you may tune the speed/resolution/interestingness tradeoff 
23  * with X resources, see below.
24  *
25  * Created      : Wed Apr 22 09:30:30 1998, hmallat
26  * Last modified: Wed Apr 22 09:30:30 1998, hmallat
27  * Last modified: Sun Aug 31 23:40:14 2003, 
28  *              david slimp <rock808@DavidSlimp.com>
29  *              added -hue option to specify base color hue
30  *
31  * TODO:
32  *
33  *    This really needs to be sped up.
34  *
35  *    I've tried making it use XPutPixel/XPutImage instead of XFillRectangle,
36  *    but that doesn't seem to help (it's the same speed at gridsize=1, and
37  *    it actually makes it slower at larger sizes.)
38  *
39  *    I played around with shared memory, but clearly I still don't know how
40  *    to use the XSHM extension properly, because it doesn't work yet.
41  *
42  *    Hannu had put in code to use the double-buffering extension, but that
43  *    code didn't work for me on Irix.  I don't think it would help anyway,
44  *    since it's not the swapping of frames that is the bottleneck (or a source
45  *    of visible flicker.)
46  *
47  *     -- jwz, 4-Jun-98
48  */
49
50 #include <math.h>
51
52 #include "screenhack.h"
53
54 /* I thought it would be faster this way, but it turns out not to be... -jwz */
55 #undef USE_XIMAGE
56
57 #ifndef USE_XIMAGE
58 # undef HAVE_XSHM_EXTENSION  /* only applicable when using XImages */
59 #endif /* USE_XIMAGE */
60
61
62 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
63 # include "xdbe.h"
64 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
65
66 #ifdef HAVE_XSHM_EXTENSION
67 # include "xshm.h"
68 #endif /* HAVE_XSHM_EXTENSION */
69
70 static const char *interference_defaults [] = {
71   ".background:  black",
72   ".foreground:  white",
73   "*count:       3",     /* number of waves */
74   "*gridsize:    4",     /* pixel size, smaller values for better resolution */
75   "*ncolors:     128",   /* number of colours used */
76   "*hue:         0",     /* hue to use for base color (0-360) */
77   "*speed:       30",    /* speed of wave origins moving around */
78   "*delay:       30000", /* or something */
79   "*color-shift: 60",    /* h in hsv space, smaller values for smaller
80                           * color gradients */
81   "*radius:      800",   /* wave extent */
82   "*gray:        false", /* color or grayscale */
83   "*mono:        false", /* monochrome, not very much fun */
84
85   "*doubleBuffer: True",
86 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
87   "*useDBE:      True", /* use double buffering extension */
88 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
89
90 #ifdef HAVE_XSHM_EXTENSION
91   "*useSHM:      True", /* use shared memory extension */
92 #endif /*  HAVE_XSHM_EXTENSION */
93   0
94 };
95
96 static XrmOptionDescRec interference_options [] = {
97   { "-count",       ".count",       XrmoptionSepArg, 0 }, 
98   { "-ncolors",     ".ncolors",     XrmoptionSepArg, 0 }, 
99   { "-gridsize",    ".gridsize",    XrmoptionSepArg, 0 }, 
100   { "-hue",         ".hue",         XrmoptionSepArg, 0 },
101   { "-speed",       ".speed",       XrmoptionSepArg, 0 },
102   { "-delay",       ".delay",       XrmoptionSepArg, 0 },
103   { "-color-shift", ".color-shift", XrmoptionSepArg, 0 },
104   { "-radius",      ".radius",      XrmoptionSepArg, 0 },
105   { "-gray",        ".gray",        XrmoptionNoArg,  "True" },
106   { "-mono",        ".mono",        XrmoptionNoArg,  "True" },
107   { "-db",          ".doubleBuffer", XrmoptionNoArg,  "True" },
108   { "-no-db",       ".doubleBuffer", XrmoptionNoArg,  "False" },
109 #ifdef HAVE_XSHM_EXTENSION
110   { "-shm",     ".useSHM",      XrmoptionNoArg, "True" },
111   { "-no-shm",  ".useSHM",      XrmoptionNoArg, "False" },
112 #endif /*  HAVE_XSHM_EXTENSION */
113   { 0, 0, 0, 0 }
114 };
115
116 struct inter_source {
117   int x; 
118   int y;
119   double x_theta;
120   double y_theta;
121 };
122
123 struct inter_context {
124   /*
125    * Display-related entries 
126    */
127   Display* dpy;
128   Window   win;
129
130 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
131   XdbeBackBuffer back_buf;
132 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
133   Pixmap   pix_buf;
134
135   GC       copy_gc;
136 #ifdef USE_XIMAGE
137   XImage  *ximage;
138 #endif /* USE_XIMAGE */
139
140 #ifdef HAVE_XSHM_EXTENSION
141   Bool use_shm;
142   XShmSegmentInfo shm_info;
143 #endif /* HAVE_XSHM_EXTENSION */
144
145   /*
146    * Resources
147    */
148   int count;
149   int grid_size;
150   int colors;
151   float hue;
152   int speed;
153   int delay;
154   int shift;
155   int radius;
156
157   /*
158    * Drawing-related entries
159    */
160   int w;
161   int h;
162   Colormap cmap;
163   XColor* pal;
164   GC* gcs;
165
166   /*
167    * lookup tables
168    */
169   int* wave_height;
170     
171   /*
172    * Interference sources
173    */
174   struct inter_source* source;
175 };
176
177 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
178 # define TARGET(c) ((c)->back_buf ? (c)->back_buf : \
179                     (c)->pix_buf ? (c)->pix_buf : (c)->win)
180 #else  /* HAVE_DOUBLE_BUFFER_EXTENSION */
181 # define TARGET(c) ((c)->pix_buf ? (c)->pix_buf : (c)->win)
182 #endif /* !HAVE_DOUBLE_BUFFER_EXTENSION */
183
184 static void inter_init(Display* dpy, Window win, struct inter_context* c) 
185 {
186   XWindowAttributes xgwa;
187   double H[3], S[3], V[3];
188   int i;
189   int mono;
190   int gray;
191   XGCValues val;
192   unsigned long valmask = 0;
193   Bool dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
194
195 # ifdef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
196   dbuf = False;
197 # endif
198
199   memset (c, 0, sizeof(*c));
200
201   c->dpy = dpy;
202   c->win = win;
203
204   c->delay = get_integer_resource(dpy, "delay", "Integer");
205
206
207   XGetWindowAttributes(c->dpy, c->win, &xgwa);
208   c->w = xgwa.width;
209   c->h = xgwa.height;
210   c->cmap = xgwa.colormap;
211
212 #ifdef HAVE_XSHM_EXTENSION
213   c->use_shm = get_boolean_resource(dpy, "useSHM", "Boolean");
214 #endif /*  HAVE_XSHM_EXTENSION */
215
216   if (dbuf)
217     {
218 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
219       c->back_buf = xdbe_get_backbuffer (c->dpy, c->win, XdbeUndefined);
220 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
221
222 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
223       if (!c->back_buf)
224 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
225         c->pix_buf = XCreatePixmap (dpy, win, xgwa.width, xgwa.height,
226                                     xgwa.depth);
227     }
228
229   val.function = GXcopy;
230   c->copy_gc = XCreateGC(c->dpy, TARGET(c), GCFunction, &val);
231
232   c->count = get_integer_resource(dpy, "count", "Integer");
233   if(c->count < 1)
234     c->count = 1;
235   c->grid_size = get_integer_resource(dpy, "gridsize", "Integer");
236   if(c->grid_size < 1)
237     c->grid_size = 1;
238   mono = get_boolean_resource(dpy, "mono", "Boolean");
239   if(!mono) {
240     c->colors = get_integer_resource(dpy, "ncolors", "Integer");
241     if(c->colors < 2)
242       c->colors = 2;
243   }
244   c->hue = get_integer_resource(dpy, "hue", "Float");
245   while (c->hue < 0 || c->hue >= 360)
246     c->hue = frand(360.0);
247   c->speed = get_integer_resource(dpy, "speed", "Integer");
248   c->shift = get_float_resource(dpy, "color-shift", "Float");
249   while(c->shift >= 360.0)
250     c->shift -= 360.0;
251   while(c->shift <= -360.0)
252     c->shift += 360.0;
253   c->radius = get_integer_resource(dpy, "radius", "Integer");;
254   if(c->radius < 1)
255     c->radius = 1;
256
257 #ifdef USE_XIMAGE
258
259   c->ximage = 0;
260
261 # ifdef HAVE_XSHM_EXTENSION
262   if (c->use_shm)
263     {
264       c->ximage = create_xshm_image(dpy, xgwa.visual, xgwa.depth,
265                                     ZPixmap, 0, &c->shm_info,
266                                     xgwa.width, c->grid_size);
267       if (!c->ximage)
268         c->use_shm = False;
269     }
270 # endif /* HAVE_XSHM_EXTENSION */
271
272   if (!c->ximage)
273     {
274       c->ximage =
275         XCreateImage (dpy, xgwa.visual,
276                       xgwa.depth, ZPixmap, 0, 0, /* depth, fmt, offset, data */
277                       xgwa.width, c->grid_size,  /* width, height */
278                       8, 0);                     /* pad, bpl */
279       c->ximage->data = (unsigned char *)
280         calloc(c->ximage->height, c->ximage->bytes_per_line);
281     }
282 #endif /* USE_XIMAGE */
283
284   if(!mono) {
285     c->pal = calloc(c->colors, sizeof(XColor));
286
287     gray = get_boolean_resource(dpy, "gray", "Boolean");
288     if(!gray) {
289       H[0] = c->hue;
290       H[1] = H[0] + c->shift < 360.0 ? H[0]+c->shift : H[0] + c->shift-360.0; 
291       H[2] = H[1] + c->shift < 360.0 ? H[1]+c->shift : H[1] + c->shift-360.0; 
292       S[0] = S[1] = S[2] = 1.0;
293       V[0] = V[1] = V[2] = 1.0;
294     } else {
295       H[0] = H[1] = H[2] = 0.0;
296       S[0] = S[1] = S[2] = 0.0;
297       V[0] = 1.0; V[1] = 0.5; V[2] = 0.0;
298     }
299
300     make_color_loop(c->dpy, c->cmap, 
301                     H[0], S[0], V[0], 
302                     H[1], S[1], V[1], 
303                     H[2], S[2], V[2], 
304                     c->pal, &(c->colors), 
305                     True, False);
306     if(c->colors < 2) { /* color allocation failure */
307       mono = 1;
308       free(c->pal);
309     }
310   }
311
312   if(mono) { /* DON'T else this with the previous if! */
313     c->colors = 2;
314     c->pal = calloc(2, sizeof(XColor));
315     c->pal[0].pixel = BlackPixel(c->dpy, DefaultScreen(c->dpy));
316     c->pal[1].pixel = WhitePixel(c->dpy, DefaultScreen(c->dpy));
317   }    
318
319   valmask = GCForeground;
320   c->gcs = calloc(c->colors, sizeof(GC));
321   for(i = 0; i < c->colors; i++) {
322     val.foreground = c->pal[i].pixel;    
323     c->gcs[i] = XCreateGC(c->dpy, TARGET(c), valmask, &val);
324   }
325
326   c->wave_height = calloc(c->radius, sizeof(int));
327   for(i = 0; i < c->radius; i++) {
328     float max = 
329       ((float)c->colors) * 
330       ((float)c->radius - (float)i) /
331       ((float)c->radius);
332     c->wave_height[i] = 
333       (int)
334       ((max + max*cos((double)i/50.0)) / 2.0);
335   }
336
337   c->source = calloc(c->count, sizeof(struct inter_source));
338   for(i = 0; i < c->count; i++) {
339     c->source[i].x_theta = frand(2.0)*3.14159;
340     c->source[i].y_theta = frand(2.0)*3.14159;
341   }
342
343 }
344
345 #define source_x(c, i) \
346   (c->w/2 + ((int)(cos(c->source[i].x_theta)*((float)c->w/2.0))))
347 #define source_y(c, i) \
348   (c->h/2 + ((int)(cos(c->source[i].y_theta)*((float)c->h/2.0))))
349
350 /*
351  * this is rather suboptimal. the sqrt() doesn't seem to be a big
352  * performance hit, but all those separate XFillRectangle()'s are.
353  * hell, it's just a quick hack anyway -- if someone wishes to tune
354  * it, go ahead! 
355  */
356
357 static void do_inter(struct inter_context* c) 
358 {
359   int i, j, k;
360   int result;
361   int dist;
362   int g;
363
364   int dx, dy;
365
366   for(i = 0; i < c->count; i++) {
367     c->source[i].x_theta += (c->speed/1000.0);
368     if(c->source[i].x_theta > 2.0*3.14159)
369       c->source[i].x_theta -= 2.0*3.14159;
370     c->source[i].y_theta += (c->speed/1000.0);
371     if(c->source[i].y_theta > 2.0*3.14159)
372       c->source[i].y_theta -= 2.0*3.14159;
373     c->source[i].x = source_x(c, i);
374     c->source[i].y = source_y(c, i);
375   }
376
377   g = c->grid_size;
378
379   for(j = 0; j < c->h/g; j++) {
380     for(i = 0; i < c->w/g; i++) {
381       result = 0;
382       for(k = 0; k < c->count; k++) {
383         dx = i*g + g/2 - c->source[k].x;
384         dy = j*g + g/2 - c->source[k].y;
385         dist = sqrt(dx*dx + dy*dy); /* what's the performance penalty here? */
386         result += (dist >= c->radius ? 0 : c->wave_height[dist]);
387       }
388       result %= c->colors;
389
390 #ifdef USE_XIMAGE
391       /* Fill in these `gridsize' horizontal bits in the scanline */
392       for(k = 0; k < g; k++)
393         XPutPixel(c->ximage, (g*i)+k, 0, c->pal[result].pixel);
394
395 #else  /* !USE_XIMAGE */
396       XFillRectangle(c->dpy, TARGET(c), c->gcs[result], g*i, g*j, g, g); 
397 #endif /* !USE_XIMAGE */
398     }
399
400 #ifdef USE_XIMAGE
401
402     /* Only the first scanline of the image has been filled in; clone that
403        scanline to the rest of the `gridsize' lines in the ximage */
404     for(k = 0; k < (g-1); k++)
405       memcpy(c->ximage->data + (c->ximage->bytes_per_line * (k + 1)),
406              c->ximage->data + (c->ximage->bytes_per_line * k),
407              c->ximage->bytes_per_line);
408
409     /* Move the bits for this horizontal stripe to the server. */
410 # ifdef HAVE_XSHM_EXTENSION
411     if (c->use_shm)
412       XShmPutImage(c->dpy, TARGET(c), c->copy_gc, c->ximage,
413                    0, 0, 0, g*j, c->ximage->width, c->ximage->height,
414                    False);
415     else
416 # endif /*  HAVE_XSHM_EXTENSION */
417       XPutImage(c->dpy, TARGET(c), c->copy_gc, c->ximage,
418                 0, 0, 0, g*j, c->ximage->width, c->ximage->height);
419
420 #endif /* USE_XIMAGE */
421   }
422
423 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
424   if (c->back_buf)
425     {
426       XdbeSwapInfo info[1];
427       info[0].swap_window = c->win;
428       info[0].swap_action = XdbeUndefined;
429       XdbeSwapBuffers(c->dpy, info, 1);
430     }
431   else
432 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
433     if (c->pix_buf)
434       {
435         XCopyArea (c->dpy, c->pix_buf, c->win, c->copy_gc,
436                    0, 0, c->w, c->h, 0, 0);
437       }
438 }
439
440 static void *
441 interference_init (Display *dpy, Window win)
442 {
443   struct inter_context *c = (struct inter_context *) calloc (1, sizeof(*c));
444   inter_init(dpy, win, c);
445   return c;
446 }
447
448 static unsigned long
449 interference_draw (Display *dpy, Window win, void *closure)
450 {
451   struct inter_context *c = (struct inter_context *) closure;
452   do_inter(c);
453   return c->delay;
454 }
455
456 static void
457 interference_reshape (Display *dpy, Window window, void *closure, 
458                  unsigned int w, unsigned int h)
459 {
460   struct inter_context *c = (struct inter_context *) closure;
461   c->w = w;
462   c->h = h;
463 }
464
465 static Bool
466 interference_event (Display *dpy, Window window, void *closure, XEvent *event)
467 {
468   return False;
469 }
470
471 static void
472 interference_free (Display *dpy, Window window, void *closure)
473 {
474 }
475
476 XSCREENSAVER_MODULE ("Interference", interference)