/* * InterAggregate (dagraz@gmail.com) * Based on code from complexification.net Intersection Aggregate * http://www.complexification.net/gallery/machines/interAggregate/index.php * * Intersection Aggregate code: * j.tarbell May, 2004 * Albuquerque, New Mexico * complexification.net * * Also based on substrate, a port of j.tarbell's Substrate Art done * by dragorn@kismetwireless.net * * * Interesting command line options, all of which serve to * concentrate the painting in some way: * * -percent-orbits 100 -base-orbits 50 -base-on-center -growth-delay 0 * * Paint should be concentrated in the center of the canvas, orbiting * about it. -percent-orbits 100 implies -base-on-center, so that's * not really needed. * * * -percent-orbits 99 -base-orbits 50 -growth-delay 0 * * Like the above example, but the 'center' will rove about the screen. * * -percent-orbits 98 -base-orbits 50 -growth-delay 0 * * Like the above example, but there will be two roving centers. * * * TODO: * -fix alpha blending / byte ordering * * CHANGES * * * Directly based the hacks of: * * xscreensaver, Copyright (c) 1997, 1998, 2002 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. */ #include #include "screenhack.h" /* this program goes faster if some functions are inline. The following is * borrowed from ifs.c */ #if !defined( __GNUC__ ) && !defined(__cplusplus) && !defined(c_plusplus) #undef inline #define inline /* */ #endif #ifndef MIN #define MIN(x,y) ((x < y) ? x : y) #endif #ifndef MAX #define MAX(x,y) ((x < y) ? y : x) #endif static const char *interaggregate_defaults[] = { ".background: white", ".foreground: black", "*fpsSolid: true", "*maxCycles: 100000", #ifdef TIME_ME "*growthDelay: 0", #else "*growthDelay: 18000", #endif "*numCircles: 100", "*percentOrbits: 0", "*baseOrbits: 75", "*baseOnCenter: False", "*drawCenters: False", #ifdef HAVE_MOBILE "*ignoreRotation: True", #endif 0 }; static XrmOptionDescRec interaggregate_options[] = { {"-background", ".background", XrmoptionSepArg, 0}, {"-foreground", ".foreground", XrmoptionSepArg, 0}, {"-max-cycles", ".maxCycles", XrmoptionSepArg, 0}, {"-growth-delay", ".growthDelay", XrmoptionSepArg, 0}, {"-num-circles", ".numCircles", XrmoptionSepArg, 0}, {"-percent-orbits", ".percentOrbits", XrmoptionSepArg, 0}, {"-base-orbits", ".baseOrbits", XrmoptionSepArg, 0}, {"-base-on-center", ".baseOnCenter", XrmoptionNoArg, "true"}, {"-draw-centers", ".drawCenters", XrmoptionNoArg, "true"}, {0, 0, 0, 0} }; /* Raw colormap extracted from pollockEFF.gif */ #if 0 char *rgb_colormap[] = { "#FFFFFF", /* white */ "#000000", /* black */ "#000000", /* more black */ /* "#736451", */ "#4e3e2e", /* olive */ /* "#666666", */ "#694d35", /* camel */ "#b9a88c", /* tan */ 0 }; #endif static const char *rgb_colormap[] = { "#FFFFFF", /* white */ "#000000", /* more black */ "#000000", /* more black */ "#4e3e2e", /* olive */ "#694d35", /* camel */ "#b0a085", /* tan */ "#e6d3ae", 0 }; /* black white brown olive grey camel */ typedef enum { LINEAR, ORBIT } PathType; typedef struct { unsigned long color; double gain; double p; } SandPainter; typedef struct _circle { double radius; double x; double y; PathType path_type; /* for a linear path */ double dx; double dy; /* for orbital path */ double theta; double r; double dtheta; struct _circle* center; int num_painters; SandPainter* painters; } Circle; struct field { int height; int width; int num_circles; Circle* circles; int percent_orbits; int base_orbits; Bool base_on_center; /* used for orbits circling the center of the screen */ Circle center_of_universe; /* Raw map of pixels we need to keep for alpha blending */ unsigned long int *off_img; /* color parms */ int numcolors; unsigned long *parsedcolors; unsigned long fgcolor; unsigned long bgcolor; int visdepth; unsigned int cycles; double max_gain; /* for debugging */ Bool draw_centers; /* for profiling whatnot */ int possible_intersections; int intersection_count; }; static struct field * init_field(void) { struct field *f = (struct field*) malloc(sizeof(struct field)); if ( f == NULL ) { fprintf(stderr, "%s: Failed to allocate field!\n", progname); exit(1); } f->height = 0; f->width = 0; f->num_circles = 0; f->circles = NULL; f->percent_orbits = 0; f->base_orbits = 0; f->base_on_center = False; f->off_img = NULL; f->numcolors = 0; f->parsedcolors = NULL; f->fgcolor = 0; f->bgcolor = 0; f->visdepth = 0; f->cycles = 0; f->max_gain = 0.22; f->draw_centers = False; f->possible_intersections = 0; f->intersection_count = 0; return f; } /* Quick references to pixels in the offscreen map and in the crack grid */ #define ref_pixel(f, x, y) ((f)->off_img[(y) * (f)->width + (x)]) #define in_bounds(f, x, y) ((x >= 0) && (x < f->width) && (y >= 0) && (y < f->height)) /* Consider rewriting with XQueryColor, or ImageByteOrder */ static inline void point2rgb(int depth, unsigned long c, int *r, int *g, int *b) { switch(depth) { case 32: case 24: #ifdef HAVE_JWXYZ /* This program idiotically does not go through a color map, so we have to hardcode in knowledge of how jwxyz.a packs pixels! Fix it to go through st->colors[st->ncolors] instead! */ *r = (c & 0x00ff0000) >> 16; *g = (c & 0x0000ffff) >> 8; *b = (c & 0x000000ff); #else *b = c & 0xff; *g = (c & 0xff00) >> 8; *r = (c & 0xff0000) >> 16; #endif break; case 16: *b = (int) (c & 0x1f) << 3; *g = (int) ((c >> 5) & 0x3f) << 2; *r = (int) ((c >> 11) & 0x1f) << 3; break; case 15: *b = (int) (c & 0x1f) << 3; *g = (int) ((c >> 5) & 0x1f) << 3; *r = (int) ((c >> 10) & 0x1f) << 3; break; } } static inline unsigned long rgb2point(int depth, int r, int g, int b) { unsigned long ret = 0; switch(depth) { case 32: case 24: #ifdef HAVE_JWXYZ /* This program idiotically does not go through a color map, so we have to hardcode in knowledge of how jwxyz.a packs pixels! Fix it to go through st->colors[st->ncolors] instead! */ ret = 0xFF000000 | (r << 16) | (g << 8) | b; #else ret |= (r << 16) | (g << 8) | b; #endif break; case 16: ret = ((r>>3) << 11) | ((g>>2)<<5) | (b>>3); break; case 15: ret = ((r>>3) << 10) | ((g>>3)<<5) | (b>>3); break; } return ret; } /* alpha blended point drawing -- this is Not Right and will likely fail on * non-intel platforms as it is now, needs fixing */ static inline unsigned long trans_point(int x1, int y1, unsigned long myc, double a, struct field *f) { if (a >= 1.0) { ref_pixel(f, x1, y1) = myc; return myc; } else { int or=0, og=0, ob=0; int r=0, g=0, b=0; int nr, ng, nb; unsigned long c; c = ref_pixel(f, x1, y1); point2rgb(f->visdepth, c, &or, &og, &ob); point2rgb(f->visdepth, myc, &r, &g, &b); nr = or + (r - or) * a; ng = og + (g - og) * a; nb = ob + (b - ob) * a; c = rgb2point(f->visdepth, nr, ng, nb); ref_pixel(f, x1, y1) = c; return c; } } static inline void drawPoint(int x, int y, unsigned long color, double intensity, Display *dpy, Window window, GC fgc, struct field *f) { unsigned long c; while ( x >= f->width ) x -= f->width; while ( x < 0 ) x += f->width; while ( y >= f->height ) y -= f->height; while ( y < 0 ) y += f->height; /* if ( in_bounds(f, x, y) ) ... */ c = trans_point(x, y, color, intensity, f); XSetForeground(dpy, fgc, c); XDrawPoint(dpy, window, fgc, x, y); } static inline void paint(SandPainter* painter, double ax, double ay, double bx, double by, Display *dpy, Window window, GC fgc, struct field *f) { /* the sand painter */ double inc, sandp; int i; /* XXX try adding tpoint here, like orig */ /* jitter the painter's values */ painter->gain += frand(0.05) - 0.025; if ( painter->gain > f->max_gain ) painter->gain = -f->max_gain; else if ( painter->gain < -f->max_gain ) painter->gain = f->max_gain; painter->p += frand(0.1) - 0.05; if ( 0 < painter->p ) painter->p = 0; else if ( painter->p > 1.0 ) painter->p = 1.0; /* replace 0.1 with 1 / f->grains */ inc = painter->gain * 0.1; sandp = 0; for(i = 0; i <= 10; ++i) { int drawx, drawy; double sp, sm; double intensity = 0.1 - 0.009 * i; sp = sin(painter->p + sandp); drawx = ax + (bx - ax) * sp; drawy = ay + (by - ay) * sp; drawPoint(drawx, drawy, painter->color, intensity, dpy, window, fgc, f); sm = sin(painter->p - sandp); drawx = ax + (bx - ax) * sm; drawy = ay + (by - ay) * sm; drawPoint(drawx, drawy, painter->color, intensity, dpy, window, fgc, f); sandp += inc; } } static void build_colors(struct field *f, Display *dpy, XWindowAttributes *xgwa) { XColor tmpcolor; int i; /* Count the colors in our map and assign them in a horrifically inefficient * manner but it only happens once */ for( f->numcolors = 0; rgb_colormap[f->numcolors] != NULL; ++f->numcolors ) { ; } if (f->numcolors < 1) f->numcolors = 1; f->parsedcolors = (unsigned long *) calloc(f->numcolors, sizeof(unsigned long)); if ( f->parsedcolors == NULL ) { fprintf(stderr, "%s: Failed to allocate parsedcolors\n", progname); exit(1); } for(i = 0; i < f->numcolors; ++i) { if (!XParseColor(dpy, xgwa->colormap, rgb_colormap[i], &tmpcolor)) { fprintf(stderr, "%s: couldn't parse color %s\n", progname, rgb_colormap[i]); exit(1); } if (!XAllocColor(dpy, xgwa->colormap, &tmpcolor)) { fprintf(stderr, "%s: couldn't allocate color %s\n", progname, rgb_colormap[i]); exit(1); } f->parsedcolors[i] = tmpcolor.pixel; } } /* used when the window is resized */ static void build_img(struct field *f) { if (f->off_img) { free(f->off_img); f->off_img = NULL; } f->off_img = (unsigned long *) calloc(f->width * f->height, sizeof(unsigned long)); if ( f->off_img == NULL ) { fprintf(stderr, "%s: Failed to allocate off_img\n", progname); exit(1); } memset(f->off_img, f->bgcolor, sizeof(unsigned long) * f->width * f->height); } static void free_circles(struct field *f) { int i; if ( f->circles != NULL ) { for(i = 0; i < f->num_circles; ++i) { free (f->circles[i].painters); } free (f->circles); f->circles = NULL; } } static void build_field(Display *dpy, Window window, XWindowAttributes xgwa, GC fgc, struct field *f) { int i; int num_orbits; int base_orbits; int orbit_start; build_img(f); f->center_of_universe.x = f->width / 2.0; f->center_of_universe.y = f->height / 2.0; f->center_of_universe.r = MAX(f->width, f->height) / 2.0; num_orbits = (f->percent_orbits * f->num_circles) / 100; orbit_start = f->num_circles - num_orbits; base_orbits = orbit_start + (num_orbits * f->base_orbits) / 100; free_circles(f); f->circles = (Circle*) calloc(f->num_circles, sizeof(Circle)); if ( f->circles == NULL ) { fprintf(stderr, "%s: Failed to allocate off_img\n", progname); exit(1); } for(i = 0; i < f->num_circles; ++i) { int j; Circle *circle = f->circles + i; /* make this a pref */ if ( i >= orbit_start ) circle->path_type = ORBIT; else circle->path_type = LINEAR; if ( circle->path_type == LINEAR ) { circle->x = frand(f->width); circle->y = frand(f->height); circle->dx = frand(0.5) - 0.25; circle->dy = frand(0.5) - 0.25; /* circle->dy = 0; */ /* circle->r = f->height * (0.05 + frand(0.1)); */ circle->radius = 5 + frand(55); /* in case we want orbits based on lines */ circle->r = MIN(f->width, f->height) / 2.0; circle->center = NULL; } else /* == ORBIT */ { if (i < base_orbits ) { if ( f->base_on_center ) circle->center = &f->center_of_universe; else { circle->center = f->circles + ((int)frand(orbit_start - 0.1)); } circle->r = 1 + frand(MIN(f->width, f->height) / 2.0); /* circle->radius = 5 + frand(55); */ } else { /* give a preference for the earlier circles */ double p = frand(0.9); circle->center = f->circles + (int) (p*i); circle->r = 1 + 0.5 * circle->center->r + 0.5 * frand(circle->center->r); /* circle->r = 1 + frand(circle->center->r / 2); */ /* circle->radius = MAX(5, frand(circle->r)); */ /* circle->radius = 5 + frand(55); */ } circle->radius = 5 + frand(MIN(55, circle->r)); circle->dtheta = (frand(0.5) - 0.25) / circle->r; circle->theta = frand(2 * M_PI); circle->x = circle->r * cos(circle->theta) + circle->center->x; circle->y = circle->r * sin(circle->theta) + circle->center->y; } /* make this a command line option */ circle->num_painters = 3; circle->painters = (SandPainter*) calloc(circle->num_painters, sizeof(SandPainter)); if ( circle->painters == NULL ) { fprintf(stderr, "%s: failed to allocate painters", progname); exit(1); } for(j = 0; j < circle->num_painters; ++j) { SandPainter *painter = circle->painters + j; painter->gain = frand(0.09) + 0.01; painter->p = frand(1.0); painter->color = f->parsedcolors[(int)(frand(0.999) * f->numcolors)]; } } } static void moveCircles(struct field *f) { int i; for(i = 0; i < f->num_circles; ++i) { Circle *circle = f->circles + i; if ( circle->path_type == LINEAR ) { circle->x += circle->dx; circle->y += circle->dy; #if 0 if ( circle->x < -circle->radius ) circle->x = f->width + circle->radius; else if ( circle->x >= f->width + circle->radius ) circle->x = -circle->radius; if ( circle->y < -circle->radius ) circle->y = f->height + circle->radius; else if ( circle->y >= f->height + circle->radius ) circle->y = -circle->radius; #else if ( circle->x < 0 ) circle->x += f->width; else if ( circle->x >= f->width ) circle->x -= f->width; if ( circle->y < 0 ) circle->y += f->height; else if ( circle->y >= f->height ) circle->y -= f->height; #endif } else /* (circle->path_type == ORBIT) */ { circle->theta += circle->dtheta; if ( circle->theta < 0 ) circle->theta += 2 * M_PI; else if ( circle->theta > 2 * M_PI ) circle->theta -= 2 * M_PI; circle->x = circle->r * cos(circle->theta) + circle->center->x; circle->y = circle->r * sin(circle->theta) + circle->center->y; #if 0 if ( circle->x < -circle->radius ) circle->x += f->width + 2 * circle->radius; else if ( circle->x >= f->width + circle->radius ) circle->x -= f->width + 2 * circle->radius; if ( circle->y < -circle->radius ) circle->y += f->height + 2 * circle->radius; else if ( circle->y >= f->height + circle->radius ) circle->y -= f->height + 2 * circle->radius; #else if ( circle->x < 0 ) circle->x += f->width; else if ( circle->x >= f->width ) circle->x -= f->width; if ( circle->y < 0 ) circle->y += f->height; else if ( circle->y >= f->height ) circle->y -= f->height; #endif } } } static void drawIntersections(Display *dpy, Window window, GC fgc, struct field *f) { int i,j; /* One might be tempted to think 'hey, this is a crude algorithm * that is going to check each of the n (n-1) / 2 possible * intersections! Why not try bsp trees, quad trees, etc, etc, * etc' * * In practice the time spent drawing the intersection of two * circles dwarfs the time takes to check for intersection. * Profiling on a 640x480 screen with 100 circles shows possible * speed gains to be only a couple of percent. * * But hey, if you're bored, go have fun. Let me know how it * turns out. */ for(i = 0; i < f->num_circles; ++i) { Circle *c1 = f->circles + i; if ( !f->draw_centers ) { /* the default branch */ for(j = i + 1; j < f->num_circles; ++j) { double d, dsqr, dx, dy; Circle *c2 = f->circles + j; #ifdef TIME_ME ++f->possible_intersections; #endif dx = c2->x - c1->x; dy = c2->y - c1->y; dsqr = dx * dx + dy * dy; d = sqrt(dsqr); if ( (fabs(dx) < (c1->radius + c2->radius)) && (fabs(dy) < (c1->radius + c2->radius)) && ( d < (c1->radius + c2->radius) ) && ( d > fabs(c1->radius - c2->radius) ) ) { double d1, d2, r1sqr; double bx, by; double midpx, midpy; double int1x, int1y; double int2x, int2y; int s; /* woo-hoo. the circles are neither outside nor * inside each other. they intersect. * * Now, compute the coordinates of the points of * intersection */ #ifdef TIME_ME ++f->intersection_count; #endif /* unit vector in direction of c1 to c2 */ bx = dx / d; by = dy / d; r1sqr = c1->radius * c1->radius; /* distance from c1's center midpoint of intersection * points */ d1 = 0.5 * (r1sqr - c2->radius * c2->radius + dsqr) / d; midpx = c1->x + d1 * bx; midpy = c1->y + d1 * by; /* distance from midpoint to points of intersection */ d2 = sqrt(r1sqr - d1 * d1); int1x = midpx + d2 * by; int1y = midpy - d2 * bx; int2x = midpx - d2 * by; int2y = midpy + d2 * bx; for(s = 0; s < c1->num_painters; ++s) { paint(c1->painters + s, int1x, int1y, int2x, int2y, dpy, window, fgc, f); } } } } else /* f->draw_centers */ { XDrawPoint(dpy, window, fgc, c1->x, c1->y); } } } struct state { Display *dpy; Window window; unsigned int max_cycles; int growth_delay; GC fgc; XGCValues gcv; XWindowAttributes xgwa; struct field *f; }; static void * interaggregate_init (Display *dpy, Window window) { struct state *st = (struct state *) calloc (1, sizeof(*st)); #ifdef TIME_ME int frames; struct timeval tm1, tm2; double tdiff; #endif st->dpy = dpy; st->window = window; st->f = init_field(); st->growth_delay = (get_integer_resource(st->dpy, "growthDelay", "Integer")); st->max_cycles = (get_integer_resource(st->dpy, "maxCycles", "Integer")); st->f->num_circles = (get_integer_resource(st->dpy, "numCircles", "Integer")); st->f->percent_orbits = (get_integer_resource(st->dpy, "percentOrbits", "Integer")); st->f->base_orbits = (get_integer_resource(st->dpy, "baseOrbits", "Integer")); st->f->base_on_center = (get_boolean_resource(st->dpy, "baseOnCenter", "Boolean")); st->f->draw_centers = (get_boolean_resource(st->dpy, "drawCenters", "Boolean")); if (st->f->num_circles <= 1) { fprintf(stderr, "%s: Minimum number of circles is 2\n", progname); exit (1); } if ( (st->f->percent_orbits < 0) || (st->f->percent_orbits > 100) ) { fprintf(stderr, "%s: percent-oribts must be between 0 and 100\n", progname); exit (1); } if ( (st->f->base_orbits < 0) || (st->f->base_orbits > 100) ) { fprintf(stderr, "%s: base-oribts must be between 0 and 100\n", progname); exit (1); } if ( st->f->percent_orbits == 100 ) st->f->base_on_center = True; XGetWindowAttributes(st->dpy, st->window, &st->xgwa); build_colors(st->f, st->dpy, &st->xgwa); st->gcv.foreground = get_pixel_resource(st->dpy, st->xgwa.colormap, "foreground", "Foreground"); st->gcv.background = get_pixel_resource(st->dpy, st->xgwa.colormap, "background", "Background"); st->fgc = XCreateGC(st->dpy, st->window, GCForeground, &st->gcv); st->f->height = st->xgwa.height; st->f->width = st->xgwa.width; st->f->visdepth = st->xgwa.depth; st->f->fgcolor = st->gcv.foreground; st->f->bgcolor = st->gcv.background; /* Initialize stuff */ build_field(st->dpy, st->window, st->xgwa, st->fgc, st->f); #ifdef TIME_ME gettimeofday(&tm1, NULL); frames = 0; #endif return st; } static unsigned long interaggregate_draw (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; if ((st->f->cycles % 10) == 0) { /* Restart if the window size changes */ XGetWindowAttributes(st->dpy, st->window, &st->xgwa); if (st->f->height != st->xgwa.height || st->f->width != st->xgwa.width) { st->f->height = st->xgwa.height; st->f->width = st->xgwa.width; st->f->visdepth = st->xgwa.depth; build_field(st->dpy, st->window, st->xgwa, st->fgc, st->f); XSetForeground(st->dpy, st->fgc, st->gcv.background); XFillRectangle(st->dpy, st->window, st->fgc, 0, 0, st->xgwa.width, st->xgwa.height); XSetForeground(st->dpy, st->fgc, st->gcv.foreground); } } moveCircles(st->f); drawIntersections(st->dpy, st->window, st->fgc, st->f); st->f->cycles++; if (st->f->cycles >= st->max_cycles && st->max_cycles != 0) { build_field(st->dpy, st->window, st->xgwa, st->fgc, st->f); XSetForeground(st->dpy, st->fgc, st->gcv.background); XFillRectangle(st->dpy, st->window, st->fgc, 0, 0, st->xgwa.width, st->xgwa.height); XSetForeground(st->dpy, st->fgc, st->gcv.foreground); } #ifdef TIME_ME frames++; gettimeofday(&tm2, NULL); tdiff = (tm2.tv_sec - tm1.tv_sec) + (tm2.tv_usec - tm1.tv_usec) * 0.00001; if ( tdiff > 1 ) { fprintf(stderr, "fps: %d %f %f\n", frames, tdiff, frames / tdiff ); fprintf(stderr, "intersections: %d %d %f\n", f->intersection_count, f->possible_intersections, ((double)f->intersection_count) / f->possible_intersections); fprintf(stderr, "fpi: %f\n", ((double)frames) / f->intersection_count ); frames = 0; tm1.tv_sec = tm2.tv_sec; tm1.tv_usec = tm2.tv_usec; f->intersection_count = f->possible_intersections = 0; } #endif return st->growth_delay; } static void interaggregate_reshape (Display *dpy, Window window, void *closure, unsigned int w, unsigned int h) { } static Bool interaggregate_event (Display *dpy, Window window, void *closure, XEvent *event) { struct state *st = (struct state *) closure; if (screenhack_event_helper (dpy, window, event)) { st->f->height--; /* act like a resize */ return True; } return False; } static void interaggregate_free (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; if (st->f) { free_circles (st->f); if (st->f->off_img) free (st->f->off_img); if (st->f->parsedcolors) free (st->f->parsedcolors); free (st->f); } if (st->fgc) XFreeGC (st->dpy, st->fgc); free (st); } XSCREENSAVER_MODULE ("Interaggregate", interaggregate)