1 /* xscreensaver, Copyright (c) 1992-2013 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 /* Simulation of a pair of quasi-gravitational fields, maybe sorta kinda
13 a little like the strong and weak electromagnetic forces. Derived from
14 a Lispm screensaver by John Pezaris <pz@mit.edu>. Viscosity added by
15 Philip Edward Cutone, III <pc2d+@andrew.cmu.edu>.
19 The simulation started out as a purely accurate gravitational
20 simulation, but, with constant simulation step size, I quickly
21 realized the field being simulated while grossly gravitational
22 was, in fact, non-conservative. It also had the rather annoying
23 behavior of dealing very badly with colliding orbs. Therefore,
24 I implemented a negative-gravity region (with two thresholds; as
25 I read your code, you only implemented one) to prevent orbs from
26 every coming too close together, and added a viscosity factor if
27 the speed of any orb got too fast. This provides a nice stable
28 system with interesting behavior.
30 I had experimented with a number of fields including the van der
31 Waals force (very interesting orbiting behavior) and 1/r^3
32 gravity (not as interesting as 1/r^2). An even normal viscosity
33 (rather than the thresholded version to bleed excess energy) is
34 also not interesting. The 1/r^2, -1/r^2, -10/r^2 thresholds
35 proved not only robust but also interesting -- the orbs never
36 collided and the threshold viscosity fixed the
37 non-conservational problem.
40 > An even normal viscosity (rather than the thresholded version to
41 > bleed excess energy) is also not interesting.
43 unless you make about 200 points.... set the viscosity to about .8
44 and drag the mouse through it. it makes a nice wave which travels
47 And (always the troublemaker) Joe Keane <jgk@jgk.org> sez:
49 Despite what John sez, the field being simulated is always
50 conservative. The real problem is that it uses a simple hack,
51 computing acceleration *based only on the starting position*,
52 instead of a real differential equation solver. Thus you'll
53 always have energy coming out of nowhere, although it's most
54 blatant when balls get close together. If it were done right,
55 you wouldn't need viscosity or artificial limits on how close
58 Matt <straitm@carleton.edu> sez:
60 Added a switch to remove the walls.
62 Added a switch to make the threshold viscosity optional. If
63 nomaxspeed is specified, then balls going really fast do not
64 recieve special treatment.
66 I've made tail mode prettier by eliminating the first erase line
67 that drew from the upper left corner to the starting position of
70 Made the balls in modes other than "balls" bounce exactly at the
71 walls. (Because the graphics for different modes are drawn
72 differently with respect to the "actual" position of the point,
73 they used to be able to run somewhat past the walls, or bounce
76 Added an option to output each ball's speed in the form of a bar
77 graph drawn on the same window as the balls. If only x or y is
78 selected, they will be represented on the appropriate axis down
79 the center of the window. If both are selected, they will both
80 be displayed along the diagonal such that the x and y bars for
81 each point start at the same place. If speed is selected, the
82 speed will be displayed down the left side. */
86 #include "screenhack.h"
89 /* The normal (and max) width for a graph bar */
92 #define min(a,b) ((a)<(b)?(a):(b))
93 #define max(a,b) ((a)>(b)?(a):(b))
97 ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
102 graph_none, graph_x, graph_y, graph_both, graph_speed
131 int point_stack_size, point_stack_fp;
137 Bool no_erase_yet; /* for tail mode fix */
140 int mouse_ball; /* index of ball being dragged, or 0 if none. */
141 unsigned long mouse_pixel;
143 enum object_mode mode;
144 enum graph_mode graph_mode;
146 GC draw_gc, erase_gc;
155 attraction_init (Display *dpy, Window window)
157 struct state *st = (struct state *) calloc (1, sizeof(*st));
159 XWindowAttributes xgwa;
161 int midx, midy, r, vx, vy;
164 char *mode_str, *graph_mode_str;
167 XGetWindowAttributes (dpy, window, &xgwa);
168 st->xlim = xgwa.width;
169 st->ylim = xgwa.height;
170 cmap = xgwa.colormap;
173 st->walls_p = get_boolean_resource (dpy, "walls", "Boolean");
175 /* if there aren't walls, don't set a limit on the radius */
176 r = get_integer_resource (dpy, "radius", "Integer");
177 if (r <= 0 || (r > min (st->xlim/2, st->ylim/2) && st->walls_p) )
178 r = min (st->xlim/2, st->ylim/2) - 50;
180 vx = get_integer_resource (dpy, "vx", "Integer");
181 vy = get_integer_resource (dpy, "vy", "Integer");
183 st->npoints = get_integer_resource (dpy, "points", "Integer");
185 st->npoints = 3 + (random () % 5);
186 st->balls = (struct ball *) malloc (st->npoints * sizeof (struct ball));
188 st->no_erase_yet = 1; /* for tail mode fix */
190 st->segments = get_integer_resource (dpy, "segments", "Integer");
191 if (st->segments < 0) st->segments = 1;
193 st->threshold = get_integer_resource (dpy, "threshold", "Integer");
194 if (st->threshold < 0) st->threshold = 0;
196 st->delay = get_integer_resource (dpy, "delay", "Integer");
197 if (st->delay < 0) st->delay = 0;
199 st->global_size = get_integer_resource (dpy, "size", "Integer");
200 if (st->global_size < 0) st->global_size = 0;
202 st->glow_p = get_boolean_resource (dpy, "glow", "Boolean");
204 st->orbit_p = get_boolean_resource (dpy, "orbit", "Boolean");
206 st->maxspeed_p = get_boolean_resource (dpy, "maxspeed", "Boolean");
208 st->cbounce_p = get_boolean_resource (dpy, "cbounce", "Boolean");
210 st->color_shift = get_integer_resource (dpy, "colorShift", "Integer");
211 if (st->color_shift <= 0) st->color_shift = 5;
213 st->viscosity = get_float_resource (dpy, "viscosity", "Float");
215 mode_str = get_string_resource (dpy, "mode", "Mode");
216 if (! mode_str) st->mode = ball_mode;
217 else if (!strcmp (mode_str, "balls")) st->mode = ball_mode;
218 else if (!strcmp (mode_str, "lines")) st->mode = line_mode;
219 else if (!strcmp (mode_str, "polygons")) st->mode = polygon_mode;
220 else if (!strcmp (mode_str, "tails")) st->mode = tail_mode;
221 else if (!strcmp (mode_str, "splines")) st->mode = spline_mode;
222 else if (!strcmp (mode_str, "filled-splines"))st->mode = spline_filled_mode;
225 "%s: mode must be balls, lines, tails, polygons, splines, or\n\
226 filled-splines, not \"%s\"\n",
231 graph_mode_str = get_string_resource (dpy, "graphmode", "Mode");
232 if (! graph_mode_str) st->graph_mode = graph_none;
233 else if (!strcmp (graph_mode_str, "x")) st->graph_mode = graph_x;
234 else if (!strcmp (graph_mode_str, "y")) st->graph_mode = graph_y;
235 else if (!strcmp (graph_mode_str, "both")) st->graph_mode = graph_both;
236 else if (!strcmp (graph_mode_str, "speed")) st->graph_mode = graph_speed;
237 else if (!strcmp (graph_mode_str, "none")) st->graph_mode = graph_none;
240 "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n",
241 progname, graph_mode_str);
245 /* only allocate memory if it is needed */
246 if(st->graph_mode != graph_none)
248 if(st->graph_mode == graph_x || st->graph_mode == graph_both)
249 st->x_vels = (double *) malloc (st->npoints * sizeof (double));
250 if(st->graph_mode == graph_y || st->graph_mode == graph_both)
251 st->y_vels = (double *) malloc (st->npoints * sizeof (double));
252 if(st->graph_mode == graph_speed)
253 st->speeds = (double *) malloc (st->npoints * sizeof (double));
256 if (st->mode != ball_mode && st->mode != tail_mode) st->glow_p = False;
258 if (st->mode == polygon_mode && st->npoints < 3)
259 st->mode = line_mode;
261 st->ncolors = get_integer_resource (dpy, "colors", "Colors");
262 if (st->ncolors < 2) st->ncolors = 2;
263 if (st->ncolors <= 2) mono_p = True;
274 int H = random() % 360;
277 double V = frand(0.25) + 0.75;
278 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
279 make_color_ramp (xgwa.screen, xgwa.visual, cmap,
280 H, S1, V, H, S2, V, st->colors, &st->ncolors,
285 st->ncolors = st->npoints;
286 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
287 make_random_colormap (xgwa.screen, xgwa.visual, cmap,
288 st->colors, &st->ncolors,
289 True, True, False, True);
295 case spline_filled_mode:
297 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
298 make_smooth_colormap (xgwa.screen, xgwa.visual, cmap,
299 st->colors, &st->ncolors,
307 if (!mono_p && st->ncolors <= 2)
309 if (st->colors) free (st->colors);
315 get_pixel_resource (dpy, cmap, "mouseForeground", "MouseForeground");
318 if (st->mode != ball_mode)
320 int size = (st->segments ? st->segments : 1);
321 st->point_stack_size = size * (st->npoints + 1);
322 st->point_stack = (XPoint *) calloc (st->point_stack_size, sizeof (XPoint));
323 st->point_stack_fp = 0;
326 gcv.line_width = (st->mode == tail_mode
327 ? (st->global_size ? st->global_size : (MAX_SIZE * 2 / 3))
329 gcv.cap_style = (st->mode == tail_mode ? CapRound : CapButt);
332 gcv.foreground = get_pixel_resource(dpy, cmap, "foreground", "Foreground");
334 gcv.foreground = st->colors[st->fg_index].pixel;
335 st->draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
337 gcv.foreground = get_pixel_resource(dpy, cmap, "background", "Background");
338 st->erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
342 jwxyz_XSetAntiAliasing (dpy, st->draw_gc, False);
343 jwxyz_XSetAntiAliasing (dpy, st->erase_gc, False);
347 if (xgwa.width < 100 || xgwa.height < 100) /* tiny windows */
350 /* let's make the balls bigger by default */
351 #define rand_size() (size_scale * (8 + (random () % 7)))
353 if (st->orbit_p && !st->global_size)
354 /* To orbit, all objects must be the same mass, or the math gets
356 st->global_size = rand_size ();
359 th = frand (M_PI+M_PI);
360 for (i = 0; i < st->npoints; i++)
362 int new_size = (st->global_size ? st->global_size : rand_size ());
363 st->balls [i].dx = 0;
364 st->balls [i].dy = 0;
365 st->balls [i].size = new_size;
366 st->balls [i].mass = (new_size * new_size * 10);
367 st->balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / st->npoints) + th);
368 st->balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / st->npoints) + th);
371 st->balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
372 st->balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
374 if (mono_p || st->mode != ball_mode)
375 st->balls [i].pixel_index = -1;
377 st->balls [i].pixel_index = 0;
379 st->balls [i].pixel_index = random() % st->ncolors;
382 /* This lets modes where the points don't really have any size use the whole
383 window. Otherwise, since the points still have a positive size
384 assigned to them, they will be bounced somewhat early. Mass and size are
385 seperate, so this shouldn't cause problems. It's a bit kludgy, tho.
387 if(st->mode == line_mode || st->mode == spline_mode ||
388 st->mode == spline_filled_mode || st->mode == polygon_mode)
390 for(i = 1; i < st->npoints; i++)
392 st->balls[i].size = 0;
400 double v_mult = get_float_resource (dpy, "vMult", "Float");
401 if (v_mult == 0.0) v_mult = 1.0;
403 for (i = 1; i < st->npoints; i++)
405 double _2ipi_n = (2 * i * M_PI / st->npoints);
406 double x = r * cos (_2ipi_n);
407 double y = r * sin (_2ipi_n);
408 double distx = r - x;
409 double dist2 = (distx * distx) + (y * y);
410 double dist = sqrt (dist2);
411 double a1 = ((st->balls[i].mass / dist2) *
412 ((dist < st->threshold) ? -1.0 : 1.0) *
418 /* "domain error: forces on balls too great" */
419 fprintf (stderr, "%s: window too small for these orbit settings.\n",
424 v = sqrt (a * r) * v_mult;
425 for (i = 0; i < st->npoints; i++)
427 double k = ((2 * i * M_PI / st->npoints) + th);
428 st->balls [i].vx = -v * sin (k);
429 st->balls [i].vy = v * cos (k);
433 if (mono_p) st->glow_p = False;
435 XClearWindow (dpy, window);
440 compute_force (struct state *st, int i, double *dx_ret, double *dy_ret)
443 double x_dist, y_dist, dist, dist2;
446 for (j = 0; j < st->npoints; j++)
448 if (i == j) continue;
449 x_dist = st->balls [j].x - st->balls [i].x;
450 y_dist = st->balls [j].y - st->balls [i].y;
451 dist2 = (x_dist * x_dist) + (y_dist * y_dist);
454 if (dist > 0.1) /* the balls are not overlapping */
456 double new_acc = ((st->balls[j].mass / dist2) *
457 ((dist < st->threshold) ? -1.0 : 1.0));
458 double new_acc_dist = new_acc / dist;
459 *dx_ret += new_acc_dist * x_dist;
460 *dy_ret += new_acc_dist * y_dist;
463 { /* the balls are overlapping; move randomly */
464 *dx_ret += (frand (10.0) - 5.0);
465 *dy_ret += (frand (10.0) - 5.0);
471 /* Draws meters along the diagonal for the x velocity */
473 draw_meter_x(Display *dpy, Window window, struct state *st, int i, int alone)
475 XWindowAttributes xgwa;
477 XGetWindowAttributes (dpy, window, &xgwa);
479 /* set the width of the bars to use */
480 if(xgwa.height < BAR_SIZE*st->npoints)
482 y = i*(xgwa.height/st->npoints);
483 h = (xgwa.height/st->npoints) - 2;
507 w1 = (int)(20*st->x_vels[i]);
508 w2 = (int)(20*st->balls[i].vx);
509 st->x_vels[i] = st->balls[i].vx;
519 XDrawRectangle(dpy,window,st->erase_gc,x1+(h+2)/2,y,w1,h);
520 XDrawRectangle(dpy,window,st->draw_gc,x2+(h+2)/2,y,w2,h);
523 /* Draws meters along the diagonal for the y velocity.
524 Is there some way to make draw_meter_x and draw_meter_y
525 one function instead of two without making them completely unreadable?
528 draw_meter_y (Display *dpy, Window window, struct state *st, int i, int alone)
530 XWindowAttributes xgwa;
532 XGetWindowAttributes (dpy, window, &xgwa);
534 if(xgwa.height < BAR_SIZE*st->npoints){ /*needs to be height still */
535 x = i*(xgwa.height/st->npoints);
536 w = (xgwa.height/st->npoints) - 2;
559 h1 = (int)(20*st->y_vels[i]);
560 h2 = (int)(20*st->balls[i].vy);
561 st->y_vels[i] = st->balls[i].vy;
571 XDrawRectangle(dpy,window,st->erase_gc,x,y1+(w+2)/2,w,h1);
572 XDrawRectangle(dpy,window,st->draw_gc,x,y2+(w+2)/2,w,h2);
576 /* Draws meters of the total speed of the balls */
578 draw_meter_speed (Display *dpy, Window window, struct state *st, int i)
580 XWindowAttributes xgwa;
582 XGetWindowAttributes (dpy, window, &xgwa);
584 if(xgwa.height < BAR_SIZE*st->npoints)
586 y = i*(xgwa.height/st->npoints);
587 h = (xgwa.height/st->npoints) - 2;
600 w1 = (int)(5*st->speeds[i]);
601 w2 = (int)(5*(st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx));
602 st->speeds[i] = st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx;
604 XDrawRectangle(dpy,window,st->erase_gc,x1,y,w1,h);
605 XDrawRectangle(dpy,window,st->draw_gc, x2,y,w2,h);
608 /* Returns the position of the mouse relative to the root window.
611 query_mouse (Display *dpy, Window win, int *x, int *y)
613 Window root1, child1;
614 int mouse_x, mouse_y, root_x, root_y;
616 if (XQueryPointer (dpy, win, &root1, &child1,
617 &root_x, &root_y, &mouse_x, &mouse_y, &mask))
630 attraction_draw (Display *dpy, Window window, void *closure)
632 struct state *st = (struct state *) closure;
633 int last_point_stack_fp = st->point_stack_fp;
635 int i, radius = st->global_size/2;
639 if(st->global_size == 0)
640 radius = (MAX_SIZE / 3);
642 if(st->graph_mode != graph_none)
644 if(st->graph_mode == graph_both)
646 for(i = 0; i < st->npoints; i++)
648 draw_meter_x(dpy, window, st, i, 0);
649 draw_meter_y(dpy, window, st, i, 0);
652 else if(st->graph_mode == graph_x)
654 for(i = 0; i < st->npoints; i++)
656 draw_meter_x(dpy, window, st, i, 1);
659 else if(st->graph_mode == graph_y)
661 for(i = 0; i < st->npoints; i++)
663 draw_meter_y(dpy, window, st, i, 1);
666 else if(st->graph_mode == graph_speed)
668 for(i = 0; i < st->npoints; i++)
670 draw_meter_speed(dpy, window, st, i);
676 /* compute the force of attraction/repulsion among all balls */
677 for (i = 0; i < st->npoints; i++)
678 compute_force (st, i, &st->balls[i].dx, &st->balls[i].dy);
680 /* move the balls according to the forces now in effect */
681 for (i = 0; i < st->npoints; i++)
683 double old_x = st->balls[i].x;
684 double old_y = st->balls[i].y;
686 int size = st->balls[i].size;
688 st->balls[i].vx += st->balls[i].dx;
689 st->balls[i].vy += st->balls[i].dy;
691 /* "don't let them get too fast: impose a terminal velocity
692 (actually, make the medium have friction)"
693 Well, what this first step really does is give the medium a
694 viscosity of .9 for balls going over the speed limit. Anyway,
697 if (fabs(st->balls[i].vx) > 10 && st->maxspeed_p)
699 st->balls[i].vx *= 0.9;
702 if (st->viscosity != 1)
704 st->balls[i].vx *= st->viscosity;
707 if (fabs(st->balls[i].vy) > 10 && st->maxspeed_p)
709 st->balls[i].vy *= 0.9;
712 if (st->viscosity != 1)
714 st->balls[i].vy *= st->viscosity;
717 st->balls[i].x += st->balls[i].vx;
718 st->balls[i].y += st->balls[i].vy;
721 /* bounce off the walls if desired
722 note: a ball is actually its upper left corner */
725 if(st->cbounce_p) /* with correct bouncing */
727 /* so long as it's out of range, keep bouncing */
728 /* limit the maximum number to bounce to 4.*/
729 int bounce_allowed = 4;
731 while( bounce_allowed && (
732 (st->balls[i].x >= (st->xlim - st->balls[i].size)) ||
733 (st->balls[i].y >= (st->ylim - st->balls[i].size)) ||
734 (st->balls[i].x <= 0) ||
735 (st->balls[i].y <= 0) )
739 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
741 st->balls[i].x = (2*(st->xlim - st->balls[i].size) - st->balls[i].x);
742 st->balls[i].vx = -st->balls[i].vx;
744 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
746 st->balls[i].y = (2*(st->ylim - st->balls[i].size) - st->balls[i].y);
747 st->balls[i].vy = -st->balls[i].vy;
749 if (st->balls[i].x <= 0)
751 st->balls[i].x = -st->balls[i].x;
752 st->balls[i].vx = -st->balls[i].vx;
754 if (st->balls[i].y <= 0)
756 st->balls[i].y = -st->balls[i].y;
757 st->balls[i].vy = -st->balls[i].vy;
761 else /* with old bouncing */
763 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
765 st->balls[i].x = (st->xlim - st->balls[i].size - 1);
766 if (st->balls[i].vx > 0) /* why is this check here? */
767 st->balls[i].vx = -st->balls[i].vx;
769 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
771 st->balls[i].y = (st->ylim - st->balls[i].size - 1);
772 if (st->balls[i].vy > 0)
773 st->balls[i].vy = -st->balls[i].vy;
775 if (st->balls[i].x <= 0)
778 if (st->balls[i].vx < 0)
779 st->balls[i].vx = -st->balls[i].vx;
781 if (st->balls[i].y <= 0)
784 if (st->balls[i].vy < 0)
785 st->balls[i].vy = -st->balls[i].vy;
790 if (i == st->mouse_ball)
793 query_mouse (dpy, window, &x, &y);
794 if (st->mode == ball_mode)
796 x -= st->balls[i].size / 2;
797 y -= st->balls[i].size / 2;
804 new_x = st->balls[i].x;
805 new_y = st->balls[i].y;
809 if (st->mode == ball_mode)
813 /* make color saturation be related to particle
817 double vx = st->balls [i].dx;
818 double vy = st->balls [i].dy;
819 if (vx < 0) vx = -vx;
820 if (vy < 0) vy = -vy;
822 if (fraction > limit) fraction = limit;
824 s = 1 - (fraction / limit);
825 st->balls[i].pixel_index = (st->ncolors * s);
827 XSetForeground (dpy, st->draw_gc,
830 : st->colors[st->balls[i].pixel_index].pixel));
834 if (st->mode == ball_mode)
836 XFillArc (dpy, window, st->erase_gc, (int) old_x, (int) old_y,
837 size, size, 0, 360*64);
838 XFillArc (dpy, window, st->draw_gc, (int) new_x, (int) new_y,
839 size, size, 0, 360*64);
843 st->point_stack [st->point_stack_fp].x = new_x;
844 st->point_stack [st->point_stack_fp].y = new_y;
845 st->point_stack_fp++;
849 /* draw the lines or polygons after computing all points */
850 if (st->mode != ball_mode)
852 st->point_stack [st->point_stack_fp].x = st->balls [0].x; /* close the polygon */
853 st->point_stack [st->point_stack_fp].y = st->balls [0].y;
854 st->point_stack_fp++;
855 if (st->point_stack_fp == st->point_stack_size)
856 st->point_stack_fp = 0;
857 else if (st->point_stack_fp > st->point_stack_size) /* better be aligned */
861 if (st->color_tick++ == st->color_shift)
864 st->fg_index = (st->fg_index + 1) % st->ncolors;
865 XSetForeground (dpy, st->draw_gc, st->colors[st->fg_index].pixel);
875 if (st->segments > 0)
876 XDrawLines (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
877 st->npoints + 1, CoordModeOrigin);
878 XDrawLines (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
879 st->npoints + 1, CoordModeOrigin);
882 if (st->segments > 0)
883 XFillPolygon (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
884 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
886 XFillPolygon (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
887 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
892 for (i = 0; i < st->npoints; i++)
894 int index = st->point_stack_fp + i;
895 int next_index = (index + (st->npoints + 1)) % st->point_stack_size;
896 if(st->no_erase_yet == 1)
898 if(st->total_ticks >= st->segments)
900 st->no_erase_yet = 0;
901 XDrawLine (dpy, window, st->erase_gc,
902 st->point_stack [index].x + radius,
903 st->point_stack [index].y + radius,
904 st->point_stack [next_index].x + radius,
905 st->point_stack [next_index].y + radius);
910 XDrawLine (dpy, window, st->erase_gc,
911 st->point_stack [index].x + radius,
912 st->point_stack [index].y + radius,
913 st->point_stack [next_index].x + radius,
914 st->point_stack [next_index].y + radius);
916 index = last_point_stack_fp + i;
917 next_index = (index - (st->npoints + 1)) % st->point_stack_size;
918 if (next_index < 0) next_index += st->point_stack_size;
919 if (st->point_stack [next_index].x == 0 &&
920 st->point_stack [next_index].y == 0)
922 XDrawLine (dpy, window, st->draw_gc,
923 st->point_stack [index].x + radius,
924 st->point_stack [index].y + radius,
925 st->point_stack [next_index].x + radius,
926 st->point_stack [next_index].y + radius);
931 case spline_filled_mode:
933 if (! st->spl) st->spl = make_spline (st->npoints);
934 if (st->segments > 0)
936 for (i = 0; i < st->npoints; i++)
938 st->spl->control_x [i] = st->point_stack [st->point_stack_fp + i].x;
939 st->spl->control_y [i] = st->point_stack [st->point_stack_fp + i].y;
941 compute_closed_spline (st->spl);
942 if (st->mode == spline_filled_mode)
943 XFillPolygon (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
944 (st->spl->n_points == 3 ? Convex : Complex),
947 XDrawLines (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
950 for (i = 0; i < st->npoints; i++)
952 st->spl->control_x [i] = st->point_stack [last_point_stack_fp + i].x;
953 st->spl->control_y [i] = st->point_stack [last_point_stack_fp + i].y;
955 compute_closed_spline (st->spl);
956 if (st->mode == spline_filled_mode)
957 XFillPolygon (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
958 (st->spl->n_points == 3 ? Convex : Complex),
961 XDrawLines (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
973 attraction_reshape (Display *dpy, Window window, void *closure,
974 unsigned int w, unsigned int h)
976 struct state *st = (struct state *) closure;
982 attraction_event (Display *dpy, Window window, void *closure, XEvent *event)
984 struct state *st = (struct state *) closure;
986 if (event->xany.type == ButtonPress)
989 if (st->mouse_ball != -1) /* second down-click? drop the ball. */
996 /* When trying to pick up a ball, first look for a click directly
997 inside the ball; but if we don't find it, expand the radius
998 outward until we find something nearby.
1000 int x = event->xbutton.x;
1001 int y = event->xbutton.y;
1002 float max = 10 * (st->global_size ? st->global_size : MAX_SIZE);
1003 float step = max / 100;
1005 for (r2 = step; r2 < max; r2 += step)
1007 for (i = 0; i < st->npoints; i++)
1009 float d = ((st->balls[i].x - x) * (st->balls[i].x - x) +
1010 (st->balls[i].y - y) * (st->balls[i].y - y));
1011 float r = st->balls[i].size;
1023 else if (event->xany.type == ButtonRelease) /* drop the ball */
1025 st->mouse_ball = -1;
1035 attraction_free (Display *dpy, Window window, void *closure)
1037 struct state *st = (struct state *) closure;
1039 if (st->balls) free (st->balls);
1040 if (st->x_vels) free (st->x_vels);
1041 if (st->y_vels) free (st->y_vels);
1042 if (st->speeds) free (st->speeds);
1043 if (st->point_stack) free (st->point_stack);
1044 if (st->colors) free (st->colors);
1045 if (st->spl) free_spline (st->spl);
1051 static const char *attraction_defaults [] = {
1052 ".background: black",
1053 ".foreground: white",
1074 "*mouseForeground: white",
1076 "*ignoreRotation: True",
1081 static XrmOptionDescRec attraction_options [] = {
1082 { "-mode", ".mode", XrmoptionSepArg, 0 },
1083 { "-graphmode", ".graphmode", XrmoptionSepArg, 0 },
1084 { "-colors", ".colors", XrmoptionSepArg, 0 },
1085 { "-points", ".points", XrmoptionSepArg, 0 },
1086 { "-color-shift", ".colorShift", XrmoptionSepArg, 0 },
1087 { "-threshold", ".threshold", XrmoptionSepArg, 0 },
1088 { "-segments", ".segments", XrmoptionSepArg, 0 },
1089 { "-delay", ".delay", XrmoptionSepArg, 0 },
1090 { "-size", ".size", XrmoptionSepArg, 0 },
1091 { "-radius", ".radius", XrmoptionSepArg, 0 },
1092 { "-vx", ".vx", XrmoptionSepArg, 0 },
1093 { "-vy", ".vy", XrmoptionSepArg, 0 },
1094 { "-vmult", ".vMult", XrmoptionSepArg, 0 },
1095 { "-viscosity", ".viscosity", XrmoptionSepArg, 0 },
1096 { "-glow", ".glow", XrmoptionNoArg, "true" },
1097 { "-noglow", ".glow", XrmoptionNoArg, "false" },
1098 { "-orbit", ".orbit", XrmoptionNoArg, "true" },
1099 { "-nowalls", ".walls", XrmoptionNoArg, "false" },
1100 { "-walls", ".walls", XrmoptionNoArg, "true" },
1101 { "-nomaxspeed", ".maxspeed", XrmoptionNoArg, "false" },
1102 { "-maxspeed", ".maxspeed", XrmoptionNoArg, "true" },
1103 { "-correct-bounce", ".cbounce", XrmoptionNoArg, "false" },
1104 { "-fast-bounce", ".cbounce", XrmoptionNoArg, "true" },
1109 XSCREENSAVER_MODULE ("Attraction", attraction)