http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / hacks / intermomentary.c
1 /*
2  *  InterMomentary (dragorn@kismetwireless.net)
3  *  Directly ported code from complexification.net InterMomentary art
4  *  http://www.complexification.net/gallery/machines/interMomentary/applet_l/interMomentary_l.pde
5  *
6  * Intersecting Circles, Instantaneous
7  * J. Tarbell                              + complexification.net
8  * Albuquerque, New Mexico
9  * May, 2004
10  * 
11  * a REAS collaboration for the            + groupc.net
12  * Whitney Museum of American Art ARTPORT  + artport.whitney.org
13  * Robert Hodgin                           + flight404.com
14  * William Ngan                            + metaphorical.net
15  * 
16  *
17  * 1.0  Oct 10 2004  dragorn  Completed first port 
18  *
19  *
20  * Based, of course, on other hacks in:
21  *
22  * xscreensaver, Copyright (c) 1997, 1998, 2002 Jamie Zawinski <jwz@jwz.org>
23  *
24  * Permission to use, copy, modify, distribute, and sell this software and its
25  * documentation for any purpose is hereby granted without fee, provided that
26  * the above copyright notice appear in all copies and that both that
27  * copyright notice and this permission notice appear in supporting
28  * documentation.  No representations are made about the suitability of this
29  * software for any purpose.  It is provided "as is" without express or 
30  * implied warranty.
31  */
32
33 #include <math.h>
34 #include "screenhack.h"
35 #include "hsv.h"
36
37 /* this program goes faster if some functions are inline.  The following is
38  * borrowed from ifs.c */
39 #if !defined( __GNUC__ ) && !defined(__cplusplus) && !defined(c_plusplus)
40 #undef inline
41 #define inline                  /* */
42 #endif
43
44 /* Pixel rider */
45 typedef struct {
46     float t;
47     float vt;
48     float mycharge;
49 } PxRider;
50
51 /* disc of light */
52 typedef struct {
53     /* index identifier */
54     int id;
55     /* position */
56     float x, y;
57     /* radius */
58     float r, dr;
59     /* velocity */
60     float vx, vy;
61
62     /* pixel riders */
63     int numr;
64     PxRider *pxRiders;
65 } Disc;
66
67 struct field {
68     unsigned int height;
69     unsigned int width;
70
71     int initial_discs;
72     Disc *discs;
73     
74     unsigned int num;
75
76     unsigned int maxrider;
77     unsigned int maxradius;
78
79     /* color parms */
80     unsigned long fgcolor;
81     unsigned long bgcolor;
82     int visdepth;
83
84     unsigned int cycles;
85
86     /* Offscreen image we draw to */
87     Pixmap off_map;
88     unsigned char *off_alpha;
89 };
90
91
92 struct state {
93   Display *dpy;
94   Window window;
95
96   struct field *f;
97   GC fgc, copygc;
98   XWindowAttributes xgwa;
99   int draw_delay;
100
101   XColor *colors;
102   int ncolors;
103 };
104
105
106 static void *xrealloc(void *p, size_t size) 
107 {
108     void *ret;
109     if ((ret = realloc(p, size)) == NULL) {
110         fprintf(stderr, "%s: out of memory\n", progname);
111         exit(1);
112     }
113     return ret;
114 }
115
116 static struct field *init_field(void) 
117 {
118     struct field *f = xrealloc(NULL, sizeof(struct field));
119     f->height = 0;
120     f->width = 0;
121     f->initial_discs = 0;
122     f->discs = NULL;
123     f->num = 0;
124     f->maxrider = 0;
125     f->maxradius = 0;
126     f->cycles = 0;
127     f->fgcolor = 0;
128     f->bgcolor = 0;
129     f->off_alpha = NULL;
130     f->visdepth = 0;
131     return f;
132 }
133
134 /* Quick-ref to pixels in the alpha map */
135 #define ref_pixel(f, x, y)   ((f)->off_alpha[(y) * (f)->width + (x)])
136
137 static inline void make_disc(struct field *f, float x, float y, float vx, float vy, float r) 
138 {
139     /* Synthesis of Disc::Disc and PxRider::PxRider */
140     Disc *nd;
141     int ix;
142
143     /* allocate a new disc */
144     f->discs = (Disc *) xrealloc(f->discs, sizeof(Disc) * (f->num + 1));
145
146     nd = &(f->discs[f->num]);
147
148     nd->id = f->num++;
149     nd->x = x;
150     nd->y = y;
151     nd->vx = vx;
152     nd->vy = vy;
153     nd->dr = r;
154     nd->r = frand(r) / 3;
155
156     nd->numr = (frand(r) / 2.62);
157     if (nd->numr > f->maxrider)
158         nd->numr = f->maxrider;
159
160     nd->pxRiders = NULL;
161     nd->pxRiders = (PxRider *) xrealloc(nd->pxRiders, sizeof(PxRider) * (f->maxrider));
162     for (ix = 0; ix < f->maxrider; ix++) {
163         nd->pxRiders[ix].vt = 0.0;
164         nd->pxRiders[ix].t = frand(M_PI * 2);
165         nd->pxRiders[ix].mycharge = 0;
166     }
167 }
168
169
170 /* alpha blended point drawing */
171 static inline unsigned long
172 trans_point(struct state *st,
173             int x1, int y1, unsigned char myc, float a, struct field *f) 
174 {
175     if ((x1 >= 0) && (x1 < f->width) && (y1 >= 0) && (y1 < f->height)) {
176         if (a >= 1.0) {
177             ref_pixel(f, x1, y1) = myc;
178         } else {
179             unsigned long c = ref_pixel(f, x1, y1);
180             c = c + (myc - c) * a;
181             ref_pixel(f, x1, y1) = c;
182             return c;
183         }
184     }
185
186     return 0;
187 }
188
189 static inline unsigned long
190 get_pixel (struct state *st, unsigned char v)
191 {
192   return st->colors [v * (st->ncolors-1) / 255].pixel;
193 }
194
195
196 static inline void move_disc(struct field *f, int dnum) 
197 {
198     Disc *d = &(f->discs[dnum]);
199
200     /* add velocity to position */
201     d->x += d->vx;
202     d->y += d->vy;
203
204     /* bound check */
205     if (d->x + d->r < 0)
206         d->x += f->width + d->r + d->r;
207     if (d->x - d->r > f->width)
208         d->x -= f->width + d->r + d->r;
209     if (d->y + d->r < 0)
210         d->y += f->height + d->r + d->r;
211     if (d->y - d->r > f->height)
212         d->y -= f->height + d->r + d->r;
213
214     /* increase to destination radius */
215     if (d->r < d->dr)
216         d->r += 0.1;
217 }
218
219 static inline void 
220 draw_glowpoint(struct state *st, Drawable drawable, 
221                GC fgc, struct field *f, float px, float py) 
222 {
223     int i, j;
224     float a;
225     unsigned long c;
226
227     for (i =- 2; i < 3; i++) {
228         for (j =- 2; j < 3; j++) {
229             a = 0.8 - i * i * 0.1 - j * j * 0.1;
230
231             c = trans_point(st, px+i, py+j, 255, a, f);
232             XSetForeground(st->dpy, fgc, get_pixel (st, c));
233             XDrawPoint(st->dpy, drawable, fgc, px + i, py + j);
234             XSetForeground(st->dpy, fgc, f->fgcolor);
235         }
236     }
237 }
238
239 static inline void 
240 moverender_rider(struct state *st, Drawable drawable, 
241                  GC fgc, struct field *f, PxRider *rid, 
242                  float x, float y, float r) 
243 {
244     float px, py;
245     unsigned long int c;
246     double cv;
247
248     /* add velocity to theta */
249     rid->t = fmod((rid->t + rid->vt + M_PI), (2 * M_PI)) - M_PI;
250     
251     rid->vt += frand(0.002) - 0.001;
252
253     /* apply friction brakes */
254     if (abs(rid->vt) > 0.02)
255         rid->vt *= 0.9;
256
257     /* draw */
258     px = x + r * cos(rid->t);
259     py = y + r * sin(rid->t);
260
261     if ((px < 0) || (px >= f->width) || (py < 0) || (py >= f->height))
262         return;
263
264     /* max brightness seems to be 0.003845 */
265
266     c = ref_pixel(f, (int) px, (int) py);
267     cv = c / 255.0;
268
269     /* guestimated - 40 is 18% of 255, so scale this to 0.0 to 0.003845 */
270     if (cv > 0.0006921) {
271         draw_glowpoint(st, drawable, fgc, f, px, py); 
272
273         rid->mycharge = 0.003845;
274     } else {
275         rid->mycharge *= 0.98;
276
277         c = 255 * rid->mycharge;
278
279         trans_point(st, px, py, c, 0.5, f);
280
281         XSetForeground(st->dpy, fgc, get_pixel(st, c));
282         XDrawPoint(st->dpy, drawable, fgc, px, py);
283         XSetForeground(st->dpy, fgc, f->fgcolor);
284     }
285 }
286
287 static inline void 
288 render_disc(struct state *st, Drawable drawable, GC fgc, struct field *f, int dnum) 
289 {
290     Disc *di = &(f->discs[dnum]);
291     int n, m;
292     float dx, dy, d;
293     float a, p2x, p2y, h, p3ax, p3ay, p3bx, p3by;
294     unsigned long c;
295
296     /* Find intersecting points with all ascending discs */
297     for (n = di->id + 1; n < f->num; n++) {
298         dx = f->discs[n].x - di->x;
299         dy = f->discs[n].y - di->y;
300         d = sqrt(dx * dx + dy * dy);
301
302         /* intersection test */
303         if (d < (f->discs[n].r + di->r)) {
304             /* complete containment test */
305             if (d > abs(f->discs[n].r - di->r)) {
306                 /* find solutions */
307                 a = (di->r * di->r - f->discs[n].r * f->discs[n].r + d * d) / (2 * d);
308                 p2x = di->x + a * (f->discs[n].x - di->x) / d;
309                 p2y = di->y + a * (f->discs[n].y - di->y) / d;
310
311                 h = sqrt(di->r * di->r - a * a);
312
313                 p3ax = p2x + h * (f->discs[n].y - di->y) / d;
314                 p3ay = p2y - h * (f->discs[n].x - di->x) / d;
315
316                 p3bx = p2x - h * (f->discs[n].y - di->y) / d;
317                 p3by = p2y + h * (f->discs[n].x - di->x) / d;
318
319                 /* bounds check */
320                 if ((p3ax < 0) || (p3ax >= f->width) || (p3ay < 0) || (p3ay >= f->height) ||
321                     (p3bx < 0) || (p3bx >= f->width) || (p3by < 0) || (p3by >= f->height))
322                     continue;
323                 
324                 /* p3a and p3b might be identical, ignore this case for now */
325                 /* XPutPixel(f->off_map, p3ax, p3ay, f->fgcolor); */
326                 c = trans_point(st, p3ax, p3ay, 255, 0.75, f);
327                 XSetForeground(st->dpy, fgc, get_pixel (st, c));
328                 XDrawPoint(st->dpy, drawable, fgc, p3ax, p3ay);
329
330                 /* XPutPixel(f->off_map, p3bx, p3by, f->fgcolor); */
331                 c = trans_point(st, p3bx, p3by, 255, 0.75, f);
332                 XSetForeground(st->dpy, fgc, get_pixel (st, c));
333                 XDrawPoint(st->dpy, drawable, fgc, p3bx, p3by);
334                 XSetForeground(st->dpy, fgc, f->fgcolor);
335             }
336         }
337
338     }
339
340     /* Render all the pixel riders */
341     for (m = 0; m < di->numr; m++) {
342         moverender_rider(st, drawable, fgc, f, &(di->pxRiders[m]), 
343                          di->x, di->y, di->r);
344     }
345 }
346
347 static void build_img(Display *dpy, Window window, struct field *f) 
348 {
349     if (f->off_alpha) {
350         free(f->off_alpha);
351         f->off_alpha = NULL;
352
353         /* Assume theres also an off pixmap */
354         if (f->off_map != window)
355           XFreePixmap(dpy, f->off_map);
356     }
357
358     f->off_alpha = (unsigned char *) 
359       xrealloc(f->off_alpha, sizeof(unsigned char) * f->width * f->height);
360
361     memset(f->off_alpha, 0, sizeof(unsigned char) * f->width * f->height);
362
363 # ifdef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
364     f->off_map = window;
365 # else
366     f->off_map = XCreatePixmap(dpy, window, f->width, f->height, f->visdepth);
367 # endif
368
369 }
370
371 static inline void blank_img(Display *dpy, Window window, XWindowAttributes xgwa, GC fgc, struct field *f) 
372 {
373     memset(f->off_alpha, 0, sizeof(unsigned char) * f->width * f->height);
374
375     XSetForeground(dpy, fgc, f->bgcolor);
376     XFillRectangle(dpy, window, fgc, 0, 0, xgwa.width, xgwa.height);
377     XSetForeground(dpy, fgc, f->fgcolor);
378 }
379
380
381 static void *
382 intermomentary_init (Display *dpy, Window window)
383 {
384   struct state *st = (struct state *) calloc (1, sizeof(*st));
385
386 #ifdef TIME_ME
387     time_t start_time = time(NULL);
388 #endif
389
390     int tempx;
391     XGCValues gcv;
392
393     st->dpy = dpy;
394     st->window = window;
395
396     XGetWindowAttributes(dpy, window, &st->xgwa);
397  
398     st->ncolors = get_integer_resource (st->dpy, "colors", "Colors");
399     st->ncolors++;
400     st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
401
402     gcv.foreground = get_pixel_resource(dpy, st->xgwa.colormap,
403                                         "foreground", "Foreground");
404     gcv.background = get_pixel_resource(dpy, st->xgwa.colormap,
405                                         "background", "Background");
406
407     {
408       XColor fgc, bgc;
409       int fgh, bgh;
410       double fgs, fgv, bgs, bgv;
411       fgc.pixel = gcv.foreground;
412       bgc.pixel = gcv.background;
413       XQueryColor (st->dpy, st->xgwa.colormap, &fgc);
414       XQueryColor (st->dpy, st->xgwa.colormap, &bgc);
415       rgb_to_hsv (fgc.red, fgc.green, fgc.blue, &fgh, &fgs, &fgv);
416       rgb_to_hsv (bgc.red, bgc.green, bgc.blue, &bgh, &bgs, &bgv);
417 #if 0
418       bgh = fgh;
419       bgs = fgs;
420       bgv = fgv / 10.0;
421 #endif
422       make_color_ramp (st->dpy, st->xgwa.colormap,
423                        bgh, bgs, bgv,
424                        fgh, fgs, fgv,
425                        st->colors, &st->ncolors,
426                        False, /* closed */
427                        True, False);
428     }
429
430     st->f = init_field();
431
432     st->f->height = st->xgwa.height;
433     st->f->width = st->xgwa.width;
434     st->f->visdepth = st->xgwa.depth;
435
436     st->draw_delay = (get_integer_resource(dpy, "drawDelay", "Integer"));
437     st->f->maxrider = (get_integer_resource(dpy, "maxRiders", "Integer"));
438     st->f->maxradius = (get_integer_resource(dpy, "maxRadius", "Integer"));
439     st->f->initial_discs = (get_integer_resource(dpy, "numDiscs", "Integer"));
440
441     if (st->f->initial_discs <= 10) {
442         fprintf(stderr, "%s: Initial discs must be greater than 10\n", progname);
443         exit (1);
444     }
445
446     if (st->f->maxradius <= 30) {
447         fprintf(stderr, "%s: Max radius must be greater than 30\n", progname);
448         exit (1);
449     }
450
451     if (st->f->maxrider <= 10) {
452         fprintf(stderr, "%s: Max riders must be greater than 10\n", progname);
453         exit (1);
454     }
455     
456     st->fgc = XCreateGC(dpy, window, GCForeground, &gcv);
457     st->copygc = XCreateGC(dpy, window, GCForeground, &gcv);
458
459     st->f->fgcolor = gcv.foreground;
460     st->f->bgcolor = gcv.background;
461
462     /* Initialize stuff */
463     build_img(dpy, window, st->f);
464
465     for (tempx = 0; tempx < st->f->initial_discs; tempx++) {
466         float fx, fy, x, y, r;
467         int bt;
468
469         /* Arrange in anti-collapsing circle */
470         fx = 0.4 * st->f->width * cos((2 * M_PI) * tempx / st->f->initial_discs);
471         fy = 0.4 * st->f->height * sin((2 * M_PI) * tempx / st->f->initial_discs);
472         x = frand(st->f->width / 2) + fx;
473         y = frand(st->f->height / 2) + fy;
474         r = 5 + frand(st->f->maxradius);
475         bt = 1;
476
477         if ((random() % 100) < 50)
478             bt = -1;
479
480         make_disc(st->f, x, y, bt * fx / 1000.0, bt * fy / 1000.0, r);
481         
482     }
483     
484     return st;
485 }
486
487 static unsigned long
488 intermomentary_draw (Display *dpy, Window window, void *closure)
489 {
490   struct state *st = (struct state *) closure;
491   int tempx;
492
493   if ((st->f->cycles % 10) == 0) {
494     /* Restart if the window size changes */
495     XGetWindowAttributes(dpy, window, &st->xgwa);
496
497     if (st->f->height != st->xgwa.height || st->f->width != st->xgwa.width) {
498       st->f->height = st->xgwa.height;
499       st->f->width = st->xgwa.width;
500       st->f->visdepth = st->xgwa.depth;
501
502       build_img(dpy, window, st->f);
503     }
504   }
505
506   blank_img(dpy, st->f->off_map, st->xgwa, st->fgc, st->f);
507   for (tempx = 0; tempx < st->f->num; tempx++) {
508     move_disc(st->f, tempx);
509     render_disc(st, st->f->off_map, st->fgc, st->f, tempx);
510   }
511
512 #if 0
513   XSetFillStyle(dpy, st->copygc, FillTiled);
514   XSetTile(dpy, st->copygc, st->f->off_map);
515   XFillRectangle(dpy, window, st->copygc, 0, 0, st->f->width, st->f->height);
516 #else
517   if (st->f->off_map != window)
518     XCopyArea (dpy, st->f->off_map, window, st->copygc, 0, 0, 
519                st->f->width, st->f->height, 0, 0);
520
521 #endif
522
523   st->f->cycles++;
524
525   return st->draw_delay;
526 }
527
528
529 static void
530 intermomentary_reshape (Display *dpy, Window window, void *closure, 
531                  unsigned int w, unsigned int h)
532 {
533 }
534
535 static Bool
536 intermomentary_event (Display *dpy, Window window, void *closure, XEvent *event)
537 {
538   return False;
539 }
540
541 static void
542 intermomentary_free (Display *dpy, Window window, void *closure)
543 {
544   struct state *st = (struct state *) closure;
545   free (st);
546 }
547
548
549 static const char *intermomentary_defaults[] = {
550     ".background: black",
551     ".foreground: yellow",
552     "*drawDelay: 30000",
553     "*numDiscs: 85",
554     "*maxRiders: 40",
555     "*maxRadius: 100",
556     "*colors: 256",
557     0
558 };
559
560 static XrmOptionDescRec intermomentary_options[] = {
561     {"-background", ".background", XrmoptionSepArg, 0},
562     {"-foreground", ".foreground", XrmoptionSepArg, 0},
563     {"-draw-delay", ".drawDelay", XrmoptionSepArg, 0},
564     {"-num-discs", ".numDiscs", XrmoptionSepArg, 0},
565     {"-max-riders", ".maxRiders", XrmoptionSepArg, 0},
566     {"-max-radius", ".maxRadius", XrmoptionSepArg, 0},
567     { "-colors",    ".colors",    XrmoptionSepArg, 0 },
568     {0, 0, 0, 0}
569 };
570
571
572 XSCREENSAVER_MODULE ("Intermomentary", intermomentary)