From d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 16 Oct 2018 10:08:48 +0200 Subject: Original 5.40 --- hacks/attraction.c | 1109 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1109 insertions(+) create mode 100644 hacks/attraction.c (limited to 'hacks/attraction.c') diff --git a/hacks/attraction.c b/hacks/attraction.c new file mode 100644 index 0000000..4fe22c2 --- /dev/null +++ b/hacks/attraction.c @@ -0,0 +1,1109 @@ +/* xscreensaver, Copyright (c) 1992-2013 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. + */ + +/* Simulation of a pair of quasi-gravitational fields, maybe sorta kinda + a little like the strong and weak electromagnetic forces. Derived from + a Lispm screensaver by John Pezaris . Viscosity added by + Philip Edward Cutone, III . + + John sez: + + The simulation started out as a purely accurate gravitational + simulation, but, with constant simulation step size, I quickly + realized the field being simulated while grossly gravitational + was, in fact, non-conservative. It also had the rather annoying + behavior of dealing very badly with colliding orbs. Therefore, + I implemented a negative-gravity region (with two thresholds; as + I read your code, you only implemented one) to prevent orbs from + every coming too close together, and added a viscosity factor if + the speed of any orb got too fast. This provides a nice stable + system with interesting behavior. + + I had experimented with a number of fields including the van der + Waals force (very interesting orbiting behavior) and 1/r^3 + gravity (not as interesting as 1/r^2). An even normal viscosity + (rather than the thresholded version to bleed excess energy) is + also not interesting. The 1/r^2, -1/r^2, -10/r^2 thresholds + proved not only robust but also interesting -- the orbs never + collided and the threshold viscosity fixed the + non-conservational problem. + + Philip sez: + > An even normal viscosity (rather than the thresholded version to + > bleed excess energy) is also not interesting. + + unless you make about 200 points.... set the viscosity to about .8 + and drag the mouse through it. it makes a nice wave which travels + through the field. + + And (always the troublemaker) Joe Keane sez: + + Despite what John sez, the field being simulated is always + conservative. The real problem is that it uses a simple hack, + computing acceleration *based only on the starting position*, + instead of a real differential equation solver. Thus you'll + always have energy coming out of nowhere, although it's most + blatant when balls get close together. If it were done right, + you wouldn't need viscosity or artificial limits on how close + the balls can get. + + Matt sez: + + Added a switch to remove the walls. + + Added a switch to make the threshold viscosity optional. If + nomaxspeed is specified, then balls going really fast do not + recieve special treatment. + + I've made tail mode prettier by eliminating the first erase line + that drew from the upper left corner to the starting position of + each point. + + Made the balls in modes other than "balls" bounce exactly at the + walls. (Because the graphics for different modes are drawn + differently with respect to the "actual" position of the point, + they used to be able to run somewhat past the walls, or bounce + before hitting them.) + + Added an option to output each ball's speed in the form of a bar + graph drawn on the same window as the balls. If only x or y is + selected, they will be represented on the appropriate axis down + the center of the window. If both are selected, they will both + be displayed along the diagonal such that the x and y bars for + each point start at the same place. If speed is selected, the + speed will be displayed down the left side. */ + +#include +#include +#include "screenhack.h" +#include "spline.h" + +/* The normal (and max) width for a graph bar */ +#define BAR_SIZE 11 +#define MAX_SIZE 16 +#define min(a,b) ((a)<(b)?(a):(b)) +#define max(a,b) ((a)>(b)?(a):(b)) + + +enum object_mode { + ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode, + tail_mode +}; + +enum graph_mode { + graph_none, graph_x, graph_y, graph_both, graph_speed +}; + +struct ball { + double x, y; + double vx, vy; + double dx, dy; + double mass; + int size; + int pixel_index; + int hue; +}; + +struct state { + struct ball *balls; + double *x_vels; + double *y_vels; + double *speeds; + int npoints; + int threshold; + int delay; + int global_size; + int segments; + Bool glow_p; + Bool orbit_p; + Bool walls_p; + Bool maxspeed_p; + Bool cbounce_p; + XPoint *point_stack; + int point_stack_size, point_stack_fp; + XColor *colors; + int ncolors; + int fg_index; + int color_shift; + int xlim, ylim; + Bool no_erase_yet; /* for tail mode fix */ + double viscosity; + + int mouse_ball; /* index of ball being dragged, or 0 if none. */ + unsigned long mouse_pixel; + + enum object_mode mode; + enum graph_mode graph_mode; + + GC draw_gc, erase_gc; + + int total_ticks; + int color_tick; + spline *spl; +}; + + +static void * +attraction_init (Display *dpy, Window window) +{ + struct state *st = (struct state *) calloc (1, sizeof(*st)); + int i; + XWindowAttributes xgwa; + XGCValues gcv; + int midx, midy, r, vx, vy; + double th; + Colormap cmap; + char *mode_str, *graph_mode_str; + double size_scale; + + XGetWindowAttributes (dpy, window, &xgwa); + st->xlim = xgwa.width; + st->ylim = xgwa.height; + cmap = xgwa.colormap; + midx = st->xlim/2; + midy = st->ylim/2; + st->walls_p = get_boolean_resource (dpy, "walls", "Boolean"); + + /* if there aren't walls, don't set a limit on the radius */ + r = get_integer_resource (dpy, "radius", "Integer"); + if (r <= 0 || (r > min (st->xlim/2, st->ylim/2) && st->walls_p) ) + r = min (st->xlim/2, st->ylim/2) - 50; + + vx = get_integer_resource (dpy, "vx", "Integer"); + vy = get_integer_resource (dpy, "vy", "Integer"); + + st->npoints = get_integer_resource (dpy, "points", "Integer"); + if (st->npoints < 1) + st->npoints = 3 + (random () % 5); + st->balls = (struct ball *) malloc (st->npoints * sizeof (struct ball)); + + st->no_erase_yet = 1; /* for tail mode fix */ + + st->segments = get_integer_resource (dpy, "segments", "Integer"); + if (st->segments < 0) st->segments = 1; + + st->threshold = get_integer_resource (dpy, "threshold", "Integer"); + if (st->threshold < 0) st->threshold = 0; + + st->delay = get_integer_resource (dpy, "delay", "Integer"); + if (st->delay < 0) st->delay = 0; + + st->global_size = get_integer_resource (dpy, "size", "Integer"); + if (st->global_size < 0) st->global_size = 0; + + st->glow_p = get_boolean_resource (dpy, "glow", "Boolean"); + + st->orbit_p = get_boolean_resource (dpy, "orbit", "Boolean"); + + st->maxspeed_p = get_boolean_resource (dpy, "maxspeed", "Boolean"); + + st->cbounce_p = get_boolean_resource (dpy, "cbounce", "Boolean"); + + st->color_shift = get_integer_resource (dpy, "colorShift", "Integer"); + if (st->color_shift <= 0) st->color_shift = 5; + + st->viscosity = get_float_resource (dpy, "viscosity", "Float"); + + mode_str = get_string_resource (dpy, "mode", "Mode"); + if (! mode_str) st->mode = ball_mode; + else if (!strcmp (mode_str, "balls")) st->mode = ball_mode; + else if (!strcmp (mode_str, "lines")) st->mode = line_mode; + else if (!strcmp (mode_str, "polygons")) st->mode = polygon_mode; + else if (!strcmp (mode_str, "tails")) st->mode = tail_mode; + else if (!strcmp (mode_str, "splines")) st->mode = spline_mode; + else if (!strcmp (mode_str, "filled-splines"))st->mode = spline_filled_mode; + else { + fprintf (stderr, + "%s: mode must be balls, lines, tails, polygons, splines, or\n\ + filled-splines, not \"%s\"\n", + progname, mode_str); + exit (1); + } + + graph_mode_str = get_string_resource (dpy, "graphmode", "Mode"); + if (! graph_mode_str) st->graph_mode = graph_none; + else if (!strcmp (graph_mode_str, "x")) st->graph_mode = graph_x; + else if (!strcmp (graph_mode_str, "y")) st->graph_mode = graph_y; + else if (!strcmp (graph_mode_str, "both")) st->graph_mode = graph_both; + else if (!strcmp (graph_mode_str, "speed")) st->graph_mode = graph_speed; + else if (!strcmp (graph_mode_str, "none")) st->graph_mode = graph_none; + else { + fprintf (stderr, + "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n", + progname, graph_mode_str); + exit (1); + } + + /* only allocate memory if it is needed */ + if(st->graph_mode != graph_none) + { + if(st->graph_mode == graph_x || st->graph_mode == graph_both) + st->x_vels = (double *) malloc (st->npoints * sizeof (double)); + if(st->graph_mode == graph_y || st->graph_mode == graph_both) + st->y_vels = (double *) malloc (st->npoints * sizeof (double)); + if(st->graph_mode == graph_speed) + st->speeds = (double *) malloc (st->npoints * sizeof (double)); + } + + if (st->mode != ball_mode && st->mode != tail_mode) st->glow_p = False; + + if (st->mode == polygon_mode && st->npoints < 3) + st->mode = line_mode; + + st->ncolors = get_integer_resource (dpy, "colors", "Colors"); + if (st->ncolors < 2) st->ncolors = 2; + if (st->ncolors <= 2) mono_p = True; + st->colors = 0; + + if (!mono_p) + { + st->fg_index = 0; + switch (st->mode) + { + case ball_mode: + if (st->glow_p) + { + int H = random() % 360; + double S1 = 0.25; + double S2 = 1.00; + double V = frand(0.25) + 0.75; + st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1)); + make_color_ramp (xgwa.screen, xgwa.visual, cmap, + H, S1, V, H, S2, V, st->colors, &st->ncolors, + False, True, False); + } + else + { + st->ncolors = st->npoints; + st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1)); + make_random_colormap (xgwa.screen, xgwa.visual, cmap, + st->colors, &st->ncolors, + True, True, False, True); + } + break; + case line_mode: + case polygon_mode: + case spline_mode: + case spline_filled_mode: + case tail_mode: + st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1)); + make_smooth_colormap (xgwa.screen, xgwa.visual, cmap, + st->colors, &st->ncolors, + True, False, True); + break; + default: + abort (); + } + } + + if (!mono_p && st->ncolors <= 2) + { + if (st->colors) free (st->colors); + st->colors = 0; + mono_p = True; + } + + st->mouse_pixel = + get_pixel_resource (dpy, cmap, "mouseForeground", "MouseForeground"); + st->mouse_ball = -1; + + if (st->mode != ball_mode) + { + int size = (st->segments ? st->segments : 1); + st->point_stack_size = size * (st->npoints + 1); + st->point_stack = (XPoint *) calloc (st->point_stack_size, sizeof (XPoint)); + st->point_stack_fp = 0; + } + + gcv.line_width = (st->mode == tail_mode + ? (st->global_size ? st->global_size : (MAX_SIZE * 2 / 3)) + : 1); + gcv.cap_style = (st->mode == tail_mode ? CapRound : CapButt); + + if (mono_p) + gcv.foreground = get_pixel_resource(dpy, cmap, "foreground", "Foreground"); + else + gcv.foreground = st->colors[st->fg_index].pixel; + st->draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv); + + gcv.foreground = get_pixel_resource(dpy, cmap, "background", "Background"); + st->erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv); + + +#ifdef HAVE_JWXYZ + jwxyz_XSetAntiAliasing (dpy, st->draw_gc, False); + jwxyz_XSetAntiAliasing (dpy, st->erase_gc, False); +#endif + + size_scale = 3; + if (xgwa.width < 100 || xgwa.height < 100) /* tiny windows */ + size_scale = 0.75; + + /* let's make the balls bigger by default */ +#define rand_size() (size_scale * (8 + (random () % 7))) + + if (st->orbit_p && !st->global_size) + /* To orbit, all objects must be the same mass, or the math gets + really hairy... */ + st->global_size = rand_size (); + + RETRY_NO_ORBIT: + th = frand (M_PI+M_PI); + for (i = 0; i < st->npoints; i++) + { + int new_size = (st->global_size ? st->global_size : rand_size ()); + st->balls [i].dx = 0; + st->balls [i].dy = 0; + st->balls [i].size = new_size; + st->balls [i].mass = (new_size * new_size * 10); + st->balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / st->npoints) + th); + st->balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / st->npoints) + th); + if (! st->orbit_p) + { + st->balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0); + st->balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0); + } + if (mono_p || st->mode != ball_mode) + st->balls [i].pixel_index = -1; + else if (st->glow_p) + st->balls [i].pixel_index = 0; + else + st->balls [i].pixel_index = random() % st->ncolors; + } + + /* This lets modes where the points don't really have any size use the whole + window. Otherwise, since the points still have a positive size + assigned to them, they will be bounced somewhat early. Mass and size are + seperate, so this shouldn't cause problems. It's a bit kludgy, tho. + */ + if(st->mode == line_mode || st->mode == spline_mode || + st->mode == spline_filled_mode || st->mode == polygon_mode) + { + for(i = 1; i < st->npoints; i++) + { + st->balls[i].size = 0; + } + } + + if (st->orbit_p) + { + double a = 0; + double v; + double v_mult = get_float_resource (dpy, "vMult", "Float"); + if (v_mult == 0.0) v_mult = 1.0; + + for (i = 1; i < st->npoints; i++) + { + double _2ipi_n = (2 * i * M_PI / st->npoints); + double x = r * cos (_2ipi_n); + double y = r * sin (_2ipi_n); + double distx = r - x; + double dist2 = (distx * distx) + (y * y); + double dist = sqrt (dist2); + double a1 = ((st->balls[i].mass / dist2) * + ((dist < st->threshold) ? -1.0 : 1.0) * + (distx / dist)); + a += a1; + } + if (a < 0.0) + { + /* "domain error: forces on balls too great" */ + fprintf (stderr, "%s: window too small for these orbit settings.\n", + progname); + st->orbit_p = False; + goto RETRY_NO_ORBIT; + } + v = sqrt (a * r) * v_mult; + for (i = 0; i < st->npoints; i++) + { + double k = ((2 * i * M_PI / st->npoints) + th); + st->balls [i].vx = -v * sin (k); + st->balls [i].vy = v * cos (k); + } + } + + if (mono_p) st->glow_p = False; + + XClearWindow (dpy, window); + return st; +} + +static void +compute_force (struct state *st, int i, double *dx_ret, double *dy_ret) +{ + int j; + double x_dist, y_dist, dist, dist2; + *dx_ret = 0; + *dy_ret = 0; + for (j = 0; j < st->npoints; j++) + { + if (i == j) continue; + x_dist = st->balls [j].x - st->balls [i].x; + y_dist = st->balls [j].y - st->balls [i].y; + dist2 = (x_dist * x_dist) + (y_dist * y_dist); + dist = sqrt (dist2); + + if (dist > 0.1) /* the balls are not overlapping */ + { + double new_acc = ((st->balls[j].mass / dist2) * + ((dist < st->threshold) ? -1.0 : 1.0)); + double new_acc_dist = new_acc / dist; + *dx_ret += new_acc_dist * x_dist; + *dy_ret += new_acc_dist * y_dist; + } + else + { /* the balls are overlapping; move randomly */ + *dx_ret += (frand (10.0) - 5.0); + *dy_ret += (frand (10.0) - 5.0); + } + } +} + + +/* Draws meters along the diagonal for the x velocity */ +static void +draw_meter_x(Display *dpy, Window window, struct state *st, int i, int alone) +{ + XWindowAttributes xgwa; + int x1,x2,y,w1,w2,h; + XGetWindowAttributes (dpy, window, &xgwa); + + /* set the width of the bars to use */ + if(xgwa.height < BAR_SIZE*st->npoints) + { + y = i*(xgwa.height/st->npoints); + h = (xgwa.height/st->npoints) - 2; + } + else + { + y = BAR_SIZE*i; + h = BAR_SIZE - 2; + } + + if(alone) + { + x1 = xgwa.width/2; + x2 = x1; + } + else + { + x1 = i*(h+2); + if(x1 < i) + x1 = i; + x2 = x1; + } + + if(y<1) y=i; + if(h<1) h=1; + + w1 = (int)(20*st->x_vels[i]); + w2 = (int)(20*st->balls[i].vx); + st->x_vels[i] = st->balls[i].vx; + + if (w1<0) { + w1=-w1; + x1=x1-w1; + } + if (w2<0) { + w2=-w2; + x2=x2-w2; + } + XDrawRectangle(dpy,window,st->erase_gc,x1+(h+2)/2,y,w1,h); + XDrawRectangle(dpy,window,st->draw_gc,x2+(h+2)/2,y,w2,h); +} + +/* Draws meters along the diagonal for the y velocity. + Is there some way to make draw_meter_x and draw_meter_y + one function instead of two without making them completely unreadable? +*/ +static void +draw_meter_y (Display *dpy, Window window, struct state *st, int i, int alone) +{ + XWindowAttributes xgwa; + int y1,y2,x,h1,h2,w; + XGetWindowAttributes (dpy, window, &xgwa); + + if(xgwa.height < BAR_SIZE*st->npoints){ /*needs to be height still */ + x = i*(xgwa.height/st->npoints); + w = (xgwa.height/st->npoints) - 2; + } + else{ + x = BAR_SIZE*i; + w = BAR_SIZE - 2; + } + + if(alone) + { + y1 = xgwa.height/2; + y2 = y1; + } + else + { + y1 = i*(w+2); + if(y1 < i) + y1 = i; + y2 = y1; + } + + if(x < 1) x = i; + if(w < 1) w = 1; + + h1 = (int)(20*st->y_vels[i]); + h2 = (int)(20*st->balls[i].vy); + st->y_vels[i] = st->balls[i].vy; + + if (h1<0) { + h1=-h1; + y1=y1-h1; + } + if (h2<0) { + h2=-h2; + y2=y2-h2; + } + XDrawRectangle(dpy,window,st->erase_gc,x,y1+(w+2)/2,w,h1); + XDrawRectangle(dpy,window,st->draw_gc,x,y2+(w+2)/2,w,h2); +} + + +/* Draws meters of the total speed of the balls */ +static void +draw_meter_speed (Display *dpy, Window window, struct state *st, int i) +{ + XWindowAttributes xgwa; + int y,x1,x2,h,w1,w2; + XGetWindowAttributes (dpy, window, &xgwa); + + if(xgwa.height < BAR_SIZE*st->npoints) + { + y = i*(xgwa.height/st->npoints); + h = (xgwa.height/st->npoints) - 2; + } + else{ + y = BAR_SIZE*i; + h = BAR_SIZE - 2; + } + + x1 = 0; + x2 = x1; + + if(y < 1) y = i; + if(h < 1) h = 1; + + w1 = (int)(5*st->speeds[i]); + w2 = (int)(5*(st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx)); + st->speeds[i] = st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx; + + XDrawRectangle(dpy,window,st->erase_gc,x1,y,w1,h); + XDrawRectangle(dpy,window,st->draw_gc, x2,y,w2,h); +} + +/* Returns the position of the mouse relative to the root window. + */ +static void +query_mouse (Display *dpy, Window win, int *x, int *y) +{ + Window root1, child1; + int mouse_x, mouse_y, root_x, root_y; + unsigned int mask; + if (XQueryPointer (dpy, win, &root1, &child1, + &root_x, &root_y, &mouse_x, &mouse_y, &mask)) + { + *x = mouse_x; + *y = mouse_y; + } + else + { + *x = -9999; + *y = -9999; + } +} + +static unsigned long +attraction_draw (Display *dpy, Window window, void *closure) +{ + struct state *st = (struct state *) closure; + int last_point_stack_fp = st->point_stack_fp; + + int i, radius = st->global_size/2; + + st->total_ticks++; + + if(st->global_size == 0) + radius = (MAX_SIZE / 3); + + if(st->graph_mode != graph_none) + { + if(st->graph_mode == graph_both) + { + for(i = 0; i < st->npoints; i++) + { + draw_meter_x(dpy, window, st, i, 0); + draw_meter_y(dpy, window, st, i, 0); + } + } + else if(st->graph_mode == graph_x) + { + for(i = 0; i < st->npoints; i++) + { + draw_meter_x(dpy, window, st, i, 1); + } + } + else if(st->graph_mode == graph_y) + { + for(i = 0; i < st->npoints; i++) + { + draw_meter_y(dpy, window, st, i, 1); + } + } + else if(st->graph_mode == graph_speed) + { + for(i = 0; i < st->npoints; i++) + { + draw_meter_speed(dpy, window, st, i); + } + } + + } + + /* compute the force of attraction/repulsion among all balls */ + for (i = 0; i < st->npoints; i++) + compute_force (st, i, &st->balls[i].dx, &st->balls[i].dy); + + /* move the balls according to the forces now in effect */ + for (i = 0; i < st->npoints; i++) + { + double old_x = st->balls[i].x; + double old_y = st->balls[i].y; + double new_x, new_y; + int size = st->balls[i].size; + + st->balls[i].vx += st->balls[i].dx; + st->balls[i].vy += st->balls[i].dy; + + /* "don't let them get too fast: impose a terminal velocity + (actually, make the medium have friction)" + Well, what this first step really does is give the medium a + viscosity of .9 for balls going over the speed limit. Anyway, + this is now optional + */ + if (fabs(st->balls[i].vx) > 10 && st->maxspeed_p) + { + st->balls[i].vx *= 0.9; + st->balls[i].dx = 0; + } + if (st->viscosity != 1) + { + st->balls[i].vx *= st->viscosity; + } + + if (fabs(st->balls[i].vy) > 10 && st->maxspeed_p) + { + st->balls[i].vy *= 0.9; + st->balls[i].dy = 0; + } + if (st->viscosity != 1) + { + st->balls[i].vy *= st->viscosity; + } + + st->balls[i].x += st->balls[i].vx; + st->balls[i].y += st->balls[i].vy; + + + /* bounce off the walls if desired + note: a ball is actually its upper left corner */ + if(st->walls_p) + { + if(st->cbounce_p) /* with correct bouncing */ + { + /* so long as it's out of range, keep bouncing */ + /* limit the maximum number to bounce to 4.*/ + int bounce_allowed = 4; + + while( bounce_allowed && ( + (st->balls[i].x >= (st->xlim - st->balls[i].size)) || + (st->balls[i].y >= (st->ylim - st->balls[i].size)) || + (st->balls[i].x <= 0) || + (st->balls[i].y <= 0) ) + ) + { + bounce_allowed--; + if (st->balls[i].x >= (st->xlim - st->balls[i].size)) + { + st->balls[i].x = (2*(st->xlim - st->balls[i].size) - st->balls[i].x); + st->balls[i].vx = -st->balls[i].vx; + } + if (st->balls[i].y >= (st->ylim - st->balls[i].size)) + { + st->balls[i].y = (2*(st->ylim - st->balls[i].size) - st->balls[i].y); + st->balls[i].vy = -st->balls[i].vy; + } + if (st->balls[i].x <= 0) + { + st->balls[i].x = -st->balls[i].x; + st->balls[i].vx = -st->balls[i].vx; + } + if (st->balls[i].y <= 0) + { + st->balls[i].y = -st->balls[i].y; + st->balls[i].vy = -st->balls[i].vy; + } + } + } + else /* with old bouncing */ + { + if (st->balls[i].x >= (st->xlim - st->balls[i].size)) + { + st->balls[i].x = (st->xlim - st->balls[i].size - 1); + if (st->balls[i].vx > 0) /* why is this check here? */ + st->balls[i].vx = -st->balls[i].vx; + } + if (st->balls[i].y >= (st->ylim - st->balls[i].size)) + { + st->balls[i].y = (st->ylim - st->balls[i].size - 1); + if (st->balls[i].vy > 0) + st->balls[i].vy = -st->balls[i].vy; + } + if (st->balls[i].x <= 0) + { + st->balls[i].x = 0; + if (st->balls[i].vx < 0) + st->balls[i].vx = -st->balls[i].vx; + } + if (st->balls[i].y <= 0) + { + st->balls[i].y = 0; + if (st->balls[i].vy < 0) + st->balls[i].vy = -st->balls[i].vy; + } + } + } + + if (i == st->mouse_ball) + { + int x, y; + query_mouse (dpy, window, &x, &y); + if (st->mode == ball_mode) + { + x -= st->balls[i].size / 2; + y -= st->balls[i].size / 2; + } + + st->balls[i].x = x; + st->balls[i].y = y; + } + + new_x = st->balls[i].x; + new_y = st->balls[i].y; + + if (!mono_p) + { + if (st->mode == ball_mode) + { + if (st->glow_p) + { + /* make color saturation be related to particle + acceleration. */ + double limit = 0.5; + double s, fraction; + double vx = st->balls [i].dx; + double vy = st->balls [i].dy; + if (vx < 0) vx = -vx; + if (vy < 0) vy = -vy; + fraction = vx + vy; + if (fraction > limit) fraction = limit; + + s = 1 - (fraction / limit); + st->balls[i].pixel_index = (st->ncolors * s); + } + XSetForeground (dpy, st->draw_gc, + (i == st->mouse_ball + ? st->mouse_pixel + : st->colors[st->balls[i].pixel_index].pixel)); + } + } + + if (st->mode == ball_mode) + { + XFillArc (dpy, window, st->erase_gc, (int) old_x, (int) old_y, + size, size, 0, 360*64); + XFillArc (dpy, window, st->draw_gc, (int) new_x, (int) new_y, + size, size, 0, 360*64); + } + else + { + st->point_stack [st->point_stack_fp].x = new_x; + st->point_stack [st->point_stack_fp].y = new_y; + st->point_stack_fp++; + } + } + + /* draw the lines or polygons after computing all points */ + if (st->mode != ball_mode) + { + st->point_stack [st->point_stack_fp].x = st->balls [0].x; /* close the polygon */ + st->point_stack [st->point_stack_fp].y = st->balls [0].y; + st->point_stack_fp++; + if (st->point_stack_fp == st->point_stack_size) + st->point_stack_fp = 0; + else if (st->point_stack_fp > st->point_stack_size) /* better be aligned */ + abort (); + if (!mono_p) + { + if (st->color_tick++ == st->color_shift) + { + st->color_tick = 0; + st->fg_index = (st->fg_index + 1) % st->ncolors; + XSetForeground (dpy, st->draw_gc, st->colors[st->fg_index].pixel); + } + } + } + + switch (st->mode) + { + case ball_mode: + break; + case line_mode: + if (st->segments > 0) + XDrawLines (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp, + st->npoints + 1, CoordModeOrigin); + XDrawLines (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp, + st->npoints + 1, CoordModeOrigin); + break; + case polygon_mode: + if (st->segments > 0) + XFillPolygon (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp, + st->npoints + 1, (st->npoints == 3 ? Convex : Complex), + CoordModeOrigin); + XFillPolygon (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp, + st->npoints + 1, (st->npoints == 3 ? Convex : Complex), + CoordModeOrigin); + break; + case tail_mode: + { + for (i = 0; i < st->npoints; i++) + { + int index = st->point_stack_fp + i; + int next_index = (index + (st->npoints + 1)) % st->point_stack_size; + if(st->no_erase_yet == 1) + { + if(st->total_ticks >= st->segments) + { + st->no_erase_yet = 0; + XDrawLine (dpy, window, st->erase_gc, + st->point_stack [index].x + radius, + st->point_stack [index].y + radius, + st->point_stack [next_index].x + radius, + st->point_stack [next_index].y + radius); + } + } + else + { + XDrawLine (dpy, window, st->erase_gc, + st->point_stack [index].x + radius, + st->point_stack [index].y + radius, + st->point_stack [next_index].x + radius, + st->point_stack [next_index].y + radius); + } + index = last_point_stack_fp + i; + next_index = (index - (st->npoints + 1)) % st->point_stack_size; + if (next_index < 0) next_index += st->point_stack_size; + if (st->point_stack [next_index].x == 0 && + st->point_stack [next_index].y == 0) + continue; + XDrawLine (dpy, window, st->draw_gc, + st->point_stack [index].x + radius, + st->point_stack [index].y + radius, + st->point_stack [next_index].x + radius, + st->point_stack [next_index].y + radius); + } + } + break; + case spline_mode: + case spline_filled_mode: + { + if (! st->spl) st->spl = make_spline (st->npoints); + if (st->segments > 0) + { + for (i = 0; i < st->npoints; i++) + { + st->spl->control_x [i] = st->point_stack [st->point_stack_fp + i].x; + st->spl->control_y [i] = st->point_stack [st->point_stack_fp + i].y; + } + compute_closed_spline (st->spl); + if (st->mode == spline_filled_mode) + XFillPolygon (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points, + (st->spl->n_points == 3 ? Convex : Complex), + CoordModeOrigin); + else + XDrawLines (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points, + CoordModeOrigin); + } + for (i = 0; i < st->npoints; i++) + { + st->spl->control_x [i] = st->point_stack [last_point_stack_fp + i].x; + st->spl->control_y [i] = st->point_stack [last_point_stack_fp + i].y; + } + compute_closed_spline (st->spl); + if (st->mode == spline_filled_mode) + XFillPolygon (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points, + (st->spl->n_points == 3 ? Convex : Complex), + CoordModeOrigin); + else + XDrawLines (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points, + CoordModeOrigin); + } + break; + default: + abort (); + } + + return st->delay; +} + +static void +attraction_reshape (Display *dpy, Window window, void *closure, + unsigned int w, unsigned int h) +{ + struct state *st = (struct state *) closure; + st->xlim = w; + st->ylim = h; +} + +static Bool +attraction_event (Display *dpy, Window window, void *closure, XEvent *event) +{ + struct state *st = (struct state *) closure; + + if (event->xany.type == ButtonPress) + { + int i; + if (st->mouse_ball != -1) /* second down-click? drop the ball. */ + { + st->mouse_ball = -1; + return True; + } + else + { + /* When trying to pick up a ball, first look for a click directly + inside the ball; but if we don't find it, expand the radius + outward until we find something nearby. + */ + int x = event->xbutton.x; + int y = event->xbutton.y; + float max = 10 * (st->global_size ? st->global_size : MAX_SIZE); + float step = max / 100; + float r2; + for (r2 = step; r2 < max; r2 += step) + { + for (i = 0; i < st->npoints; i++) + { + float d = ((st->balls[i].x - x) * (st->balls[i].x - x) + + (st->balls[i].y - y) * (st->balls[i].y - y)); + float r = st->balls[i].size; + if (r2 > r) r = r2; + if (d < r*r) + { + st->mouse_ball = i; + return True; + } + } + } + } + return True; + } + else if (event->xany.type == ButtonRelease) /* drop the ball */ + { + st->mouse_ball = -1; + return True; + } + + + + return False; +} + +static void +attraction_free (Display *dpy, Window window, void *closure) +{ + struct state *st = (struct state *) closure; + + if (st->balls) free (st->balls); + if (st->x_vels) free (st->x_vels); + if (st->y_vels) free (st->y_vels); + if (st->speeds) free (st->speeds); + if (st->point_stack) free (st->point_stack); + if (st->colors) free (st->colors); + if (st->spl) free_spline (st->spl); + + free (st); +} + + +static const char *attraction_defaults [] = { + ".background: black", + ".foreground: white", + "*fpsSolid: true", + "*mode: balls", + "*graphmode: none", + "*points: 0", + "*size: 0", + "*colors: 200", + "*threshold: 200", + "*delay: 10000", + "*glow: false", + "*walls: true", + "*maxspeed: true", + "*cbounce: true", + "*viscosity: 1.0", + "*orbit: false", + "*colorShift: 3", + "*segments: 500", + "*vMult: 0.9", + "*radius: 0", + "*vx: 0", + "*vy: 0", + "*mouseForeground: white", +#ifdef HAVE_MOBILE + "*ignoreRotation: True", +#endif + 0 +}; + +static XrmOptionDescRec attraction_options [] = { + { "-mode", ".mode", XrmoptionSepArg, 0 }, + { "-graphmode", ".graphmode", XrmoptionSepArg, 0 }, + { "-colors", ".colors", XrmoptionSepArg, 0 }, + { "-points", ".points", XrmoptionSepArg, 0 }, + { "-color-shift", ".colorShift", XrmoptionSepArg, 0 }, + { "-threshold", ".threshold", XrmoptionSepArg, 0 }, + { "-segments", ".segments", XrmoptionSepArg, 0 }, + { "-delay", ".delay", XrmoptionSepArg, 0 }, + { "-size", ".size", XrmoptionSepArg, 0 }, + { "-radius", ".radius", XrmoptionSepArg, 0 }, + { "-vx", ".vx", XrmoptionSepArg, 0 }, + { "-vy", ".vy", XrmoptionSepArg, 0 }, + { "-vmult", ".vMult", XrmoptionSepArg, 0 }, + { "-viscosity", ".viscosity", XrmoptionSepArg, 0 }, + { "-glow", ".glow", XrmoptionNoArg, "true" }, + { "-noglow", ".glow", XrmoptionNoArg, "false" }, + { "-orbit", ".orbit", XrmoptionNoArg, "true" }, + { "-nowalls", ".walls", XrmoptionNoArg, "false" }, + { "-walls", ".walls", XrmoptionNoArg, "true" }, + { "-nomaxspeed", ".maxspeed", XrmoptionNoArg, "false" }, + { "-maxspeed", ".maxspeed", XrmoptionNoArg, "true" }, + { "-correct-bounce", ".cbounce", XrmoptionNoArg, "false" }, + { "-fast-bounce", ".cbounce", XrmoptionNoArg, "true" }, + { 0, 0, 0, 0 } +}; + + +XSCREENSAVER_MODULE ("Attraction", attraction) -- cgit v1.2.3-55-g7522